Skip to content

Commit

Permalink
build again have fuuun
Browse files Browse the repository at this point in the history
  • Loading branch information
jhnnsrs committed Apr 21, 2023
1 parent f1f8b5b commit a095e0e
Show file tree
Hide file tree
Showing 5 changed files with 333 additions and 27 deletions.
30 changes: 30 additions & 0 deletions graphql/stage.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
fragment ExportStage on Stage {
name
positions {
name
id
omeros {
acquisitionDate
representation {
store
name
id
fileOrigins {
id
file
}
derived(flatten: 4) {
id
store
name
}
}
}
}
}

query GetExportStage($id: ID!) {
stage(id: $id) {
...ExportStage
}
}
211 changes: 211 additions & 0 deletions gucker/api/schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
from rath.scalars import ID
from mikro.funcs import execute, aexecute
from mikro.traits import Position, Representation, Omero, Stage
from enum import Enum
from mikro.scalars import Store, File
from datetime import datetime
from pydantic import BaseModel, Field
from typing import Optional, Literal, Tuple
from mikro.rath import MikroRath


class ExportStageFragmentPositionsOmerosRepresentationFileorigins(BaseModel):
typename: Optional[Literal["OmeroFile"]] = Field(alias="__typename", exclude=True)
id: ID
file: Optional[File]
"The file"

class Config:
frozen = True


class ExportStageFragmentPositionsOmerosRepresentationDerived(
Representation, BaseModel
):
"""A Representation is 5-dimensional representation of an image
Mikro stores each image as sa 5-dimensional representation. The dimensions are:
- t: time
- c: channel
- z: z-stack
- x: x-dimension
- y: y-dimension
This ensures a unified api for all images, regardless of their original dimensions. Another main
determining factor for a representation is its variety:
A representation can be a raw image representating voxels (VOXEL)
or a segmentation mask representing instances of a class. (MASK)
It can also representate a human perception of the image (RGB) or a human perception of the mask (RGBMASK)
# Meta
Meta information is stored in the omero field which gives access to the omero-meta data. Refer to the omero documentation for more information.
#Origins and Derivations
Images can be filtered, which means that a new representation is created from the other (original) representations. This new representation is then linked to the original representations. This way, we can always trace back to the original representation.
Both are encapsulaed in the origins and derived fields.
Representations belong to *one* sample. Every transaction to our image data is still part of the original acuqistion, so also filtered images are refering back to the sample
Each iamge has also a name, which is used to identify the image. The name is unique within a sample.
File and Rois that are used to create images are saved in the file origins and roi origins repectively.
"""

typename: Optional[Literal["Representation"]] = Field(
alias="__typename", exclude=True
)
id: ID
store: Optional[Store]
name: Optional[str]
"Cleartext name"

class Config:
frozen = True


class ExportStageFragmentPositionsOmerosRepresentation(Representation, BaseModel):
"""A Representation is 5-dimensional representation of an image
Mikro stores each image as sa 5-dimensional representation. The dimensions are:
- t: time
- c: channel
- z: z-stack
- x: x-dimension
- y: y-dimension
This ensures a unified api for all images, regardless of their original dimensions. Another main
determining factor for a representation is its variety:
A representation can be a raw image representating voxels (VOXEL)
or a segmentation mask representing instances of a class. (MASK)
It can also representate a human perception of the image (RGB) or a human perception of the mask (RGBMASK)
# Meta
Meta information is stored in the omero field which gives access to the omero-meta data. Refer to the omero documentation for more information.
#Origins and Derivations
Images can be filtered, which means that a new representation is created from the other (original) representations. This new representation is then linked to the original representations. This way, we can always trace back to the original representation.
Both are encapsulaed in the origins and derived fields.
Representations belong to *one* sample. Every transaction to our image data is still part of the original acuqistion, so also filtered images are refering back to the sample
Each iamge has also a name, which is used to identify the image. The name is unique within a sample.
File and Rois that are used to create images are saved in the file origins and roi origins repectively.
"""

typename: Optional[Literal["Representation"]] = Field(
alias="__typename", exclude=True
)
store: Optional[Store]
name: Optional[str]
"Cleartext name"
id: ID
file_origins: Tuple[
ExportStageFragmentPositionsOmerosRepresentationFileorigins, ...
] = Field(alias="fileOrigins")
derived: Optional[
Tuple[Optional[ExportStageFragmentPositionsOmerosRepresentationDerived], ...]
]
"Derived Images from this Image"

class Config:
frozen = True


class ExportStageFragmentPositionsOmeros(Omero, BaseModel):
"""Omero is a through model that stores the real world context of an image
This means that it stores the position (corresponding to the relative displacement to
a stage (Both are models)), objective and other meta data of the image.
"""

typename: Optional[Literal["Omero"]] = Field(alias="__typename", exclude=True)
acquisition_date: Optional[datetime] = Field(alias="acquisitionDate")
representation: ExportStageFragmentPositionsOmerosRepresentation

class Config:
frozen = True


class ExportStageFragmentPositions(Position, BaseModel):
"""The relative position of a sample on a microscope stage"""

typename: Optional[Literal["Position"]] = Field(alias="__typename", exclude=True)
name: str
"The name of the possition"
id: ID
omeros: Optional[Tuple[Optional[ExportStageFragmentPositionsOmeros], ...]]
"Associated images through Omero"

class Config:
frozen = True


class ExportStageFragment(Stage, BaseModel):
typename: Optional[Literal["Stage"]] = Field(alias="__typename", exclude=True)
name: str
"The name of the stage"
positions: Tuple[ExportStageFragmentPositions, ...]

class Config:
frozen = True


class GetExportStageQuery(BaseModel):
stage: Optional[ExportStageFragment]
'Get a single experiment by ID"\n \n Returns a single experiment by ID. If the user does not have access\n to the experiment, an error will be raised.\n \n '

class Arguments(BaseModel):
id: ID

class Meta:
document = "fragment ExportStage on Stage {\n name\n positions {\n name\n id\n omeros {\n acquisitionDate\n representation {\n store\n name\n id\n fileOrigins {\n id\n file\n }\n derived(flatten: 4) {\n id\n store\n name\n }\n }\n }\n }\n}\n\nquery GetExportStage($id: ID!) {\n stage(id: $id) {\n ...ExportStage\n }\n}"


async def aget_export_stage(
id: ID, rath: MikroRath = None
) -> Optional[ExportStageFragment]:
"""GetExportStage
stage: An Stage is a set of positions that share a common space on a microscope and can
be use to translate.
Arguments:
id (ID): id
rath (mikro.rath.MikroRath, optional): The mikro rath client
Returns:
Optional[ExportStageFragment]"""
return (await aexecute(GetExportStageQuery, {"id": id}, rath=rath)).stage


def get_export_stage(id: ID, rath: MikroRath = None) -> Optional[ExportStageFragment]:
"""GetExportStage
stage: An Stage is a set of positions that share a common space on a microscope and can
be use to translate.
Arguments:
id (ID): id
rath (mikro.rath.MikroRath, optional): The mikro rath client
Returns:
Optional[ExportStageFragment]"""
return execute(GetExportStageQuery, {"id": id}, rath=rath).stage
78 changes: 71 additions & 7 deletions gucker/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
from arkitekt.builders import publicqt
from arkitekt import log
import logging

from gucker.api.schema import get_export_stage
from mikro import Stage, Image
import tifffile

logger = logging.getLogger(__name__)

Expand All @@ -46,6 +48,7 @@ def __init__(self, **kwargs) -> None:
self.setStyleSheet("background-color: #1e1e1e; color: #ffffff;")
self.settings = QtCore.QSettings("Gucker", "gg")
self.base_dir = self.settings.value("base_dir", "")
self.export_dir = self.settings.value("export_dir", "")

self.is_watching.connect(self.is_watching_changed)
self.is_uploading.connect(self.is_uploading_changed)
Expand All @@ -70,6 +73,8 @@ def __init__(self, **kwargs) -> None:
)
self.button = QtWidgets.QPushButton("Select Directory to watch")
self.button.clicked.connect(self.on_base_dir)
self.exportbutton = QtWidgets.QPushButton("Select Directory to export")
self.exportbutton.clicked.connect(self.on_export_dir)

if self.base_dir == "":
self.button.setText("Select Watching Folder")
Expand All @@ -86,28 +91,53 @@ def __init__(self, **kwargs) -> None:
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.center_label)
layout.addWidget(self.button)
layout.addWidget(self.exportbutton)
layout.addWidget(self.magic_bar)
self.centralWidget.setLayout(layout)
self.setCentralWidget(self.centralWidget)

# self.app.rekuest.register(on_provide=self.on_stream_provide)(self.stream_folder)
self.app.rekuest.register()(self.stream_files)
self.app.rekuest.register()(self.export_stage)
self.setWindowTitle("Gucker")

def on_base_dir(self):
self.base_dir = QtWidgets.QFileDialog.getExistingDirectory(
self, "Select Folder"
self, "Select Stream Folder"
)
if self.base_dir:
self.button.setText(f"Selected {self.base_dir}")
self.magic_bar.magicb.setText("Select Folder first")
self.settings.setValue("base_dir", self.base_dir)
self.magic_bar.magicb.setDisabled(False)
self.button.setText(f"Selected {self.base_dir}")
else:
self.button.setText("Select Watching Folder")
self.settings.setValue("base_dir", "")

self.check_folders_sane()

def check_folders_sane(self):
if not self.base_dir:
self.statusBar.showMessage("Select a folder to watch first")
self.magic_bar.magicb.setDisabled(True)
return False
if not self.export_dir:
self.statusBar.showMessage("Select a folder to export first")
self.magic_bar.magicb.setDisabled(True)
self.magic_bar.magicb.setText("Select Folder first")
return False

self.statusBar.showMessage("All set ready to go")
self.magic_bar.magicb.setDisabled(False)
return True

def on_export_dir(self):
self.export_dir = QtWidgets.QFileDialog.getExistingDirectory(
self, "Select Export Folder"
)
if self.export_dir:
self.settings.setValue("export_dir", self.export_dir)
self.exportbutton.setText(f"Selected {self.export_dir}")
else:
self.exportbutton.setText("Select Export Folder")

self.check_folders_sane()

def is_watching_changed(self, select) -> None:
if select:
Expand Down Expand Up @@ -199,6 +229,40 @@ def stream_files(

self.is_watching.emit(False)

def export_representation(self, representation: Image, dir: str) -> None:
tifffile.imsave(
os.path.join(dir, f"ID({representation.id}) {representation.name}.tiff"),
representation.data,
)

def export_stage(self, stage: Stage) -> None:
"""Export Stage
Exports the stage to the export directory
Args:
stage (Stage): The stage to export
"""
assert self.export_dir, "No export directory selected"
export_stage = get_export_stage(stage)
print(export_stage)

stage_dir = os.path.join(self.export_dir, f"ID({stage.id}) {export_stage.name}")
os.makedirs(stage_dir, exist_ok=True)
for item in export_stage.positions:
pos_dir = os.path.join(stage_dir, f"ID({item.id}) {item.name}")
os.makedirs(pos_dir, exist_ok=True)
for image in item.omeros:
image_dir = os.path.join(
pos_dir,
f"ID({image.representation.id}) {image.representation.name} {image.acquisition_date}",
)
os.makedirs(image_dir, exist_ok=True)
self.export_representation(image.representation, image_dir)

for file in image.representation.derived:
self.export_representation(file, image_dir)


def main(**kwargs) -> None:
"""Entrypoint for the application"""
Expand Down
Loading

0 comments on commit a095e0e

Please sign in to comment.