mozarrt 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mozarrt/__init__.py +3 -0
- mozarrt/cli.py +18 -0
- mozarrt/collect.py +220 -0
- mozarrt/folder.py +86 -0
- mozarrt/mobie_collection.py +30 -0
- mozarrt-0.1.0.dist-info/METADATA +12 -0
- mozarrt-0.1.0.dist-info/RECORD +9 -0
- mozarrt-0.1.0.dist-info/WHEEL +4 -0
- mozarrt-0.1.0.dist-info/entry_points.txt +2 -0
mozarrt/__init__.py
ADDED
mozarrt/cli.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from cyclopts import App
|
|
2
|
+
|
|
3
|
+
from mozarrt.collect import create_plate_project, folder, plate
|
|
4
|
+
from mozarrt.folder import project
|
|
5
|
+
|
|
6
|
+
app = App()
|
|
7
|
+
plate_app = App(
|
|
8
|
+
name="plate",
|
|
9
|
+
help="Actions that take an HCS plate OME-Zarr as input",
|
|
10
|
+
)
|
|
11
|
+
folder_app = App(
|
|
12
|
+
name="folder",
|
|
13
|
+
help="Actions that take a folder with OME-Zarr datasets as input",
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
folder_app.command(project)
|
|
17
|
+
app.command(plate_app)
|
|
18
|
+
app.command(folder_app)
|
mozarrt/collect.py
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
from cyclopts.types import ExistingDirectory
|
|
3
|
+
from loguru import logger
|
|
4
|
+
from natsort import natsorted
|
|
5
|
+
from ngio import OmeZarrContainer, open_ome_zarr_container, open_ome_zarr_plate
|
|
6
|
+
from ngio.hcs import OmeZarrPlate
|
|
7
|
+
from mobiedantic import Project, Dataset
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from mozarrt.mobie_collection import MoBIECollectionEntry, collection_dataframe
|
|
11
|
+
|
|
12
|
+
def _create_intensities_entry(
|
|
13
|
+
uri: str,
|
|
14
|
+
name: str,
|
|
15
|
+
channel: int,
|
|
16
|
+
grid: str,
|
|
17
|
+
display: str,
|
|
18
|
+
# grid_position: tuple[int, int],
|
|
19
|
+
) -> MoBIECollectionEntry:
|
|
20
|
+
return MoBIECollectionEntry(
|
|
21
|
+
uri=uri,
|
|
22
|
+
name=name,
|
|
23
|
+
type="intensities",
|
|
24
|
+
format="OmeZarr",
|
|
25
|
+
channel=channel,
|
|
26
|
+
contrast_limits=(0, 255),
|
|
27
|
+
view="all",
|
|
28
|
+
display=display,
|
|
29
|
+
grid=grid,
|
|
30
|
+
# grid_position=grid_position,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
def _create_labels_entry(
|
|
34
|
+
uri: str,
|
|
35
|
+
name: str,
|
|
36
|
+
channel: int,
|
|
37
|
+
grid: str,
|
|
38
|
+
display: str,
|
|
39
|
+
# grid_position: tuple[int, int],
|
|
40
|
+
) -> MoBIECollectionEntry:
|
|
41
|
+
return MoBIECollectionEntry(
|
|
42
|
+
uri=uri,
|
|
43
|
+
name=name,
|
|
44
|
+
type="labels",
|
|
45
|
+
format="OmeZarr",
|
|
46
|
+
channel=channel,
|
|
47
|
+
view="all",
|
|
48
|
+
grid=grid,
|
|
49
|
+
display=display,
|
|
50
|
+
# grid_position=grid_position,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def _create_spots_entry(
|
|
54
|
+
uri: str,
|
|
55
|
+
name: str,
|
|
56
|
+
grid: str,
|
|
57
|
+
display: str,
|
|
58
|
+
# grid_position: tuple[int, int],
|
|
59
|
+
) -> MoBIECollectionEntry:
|
|
60
|
+
return MoBIECollectionEntry(
|
|
61
|
+
uri=uri,
|
|
62
|
+
name=name,
|
|
63
|
+
type="spots",
|
|
64
|
+
view="all",
|
|
65
|
+
grid=grid,
|
|
66
|
+
display=display,
|
|
67
|
+
# grid_position=grid_position,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def plate(
|
|
71
|
+
plate_zarr_path: ExistingDirectory,
|
|
72
|
+
output_directory: ExistingDirectory,
|
|
73
|
+
/,
|
|
74
|
+
):
|
|
75
|
+
# Open plate zarr container
|
|
76
|
+
plate = open_ome_zarr_plate(plate_zarr_path)
|
|
77
|
+
|
|
78
|
+
mobie_collection_entries: list[MoBIECollectionEntry] = []
|
|
79
|
+
|
|
80
|
+
sub_path = "0"
|
|
81
|
+
|
|
82
|
+
for well_path, well in plate.get_wells().items():
|
|
83
|
+
logger.info(f"Processing well: {well_path}")
|
|
84
|
+
assert sub_path in well.paths()
|
|
85
|
+
# For each well, generate MoBIECollectionEntry
|
|
86
|
+
well_image = well.get_image(sub_path).get_image()
|
|
87
|
+
logger.info(well_image.channels_meta)
|
|
88
|
+
for ch_idx, channel in enumerate(well_image.channels_meta.channels):
|
|
89
|
+
logger.info(f" Channel: {channel.label}")
|
|
90
|
+
entry = _create_intensities_entry(
|
|
91
|
+
uri=f"{plate_zarr_path}/{well_path}/{sub_path}",
|
|
92
|
+
name=f"{well_path}_c{ch_idx}",
|
|
93
|
+
channel=ch_idx,
|
|
94
|
+
display=channel.label,
|
|
95
|
+
grid=f"{ch_idx}_grid",
|
|
96
|
+
# grid_position=(0, 0), # TODO compute position from well_path
|
|
97
|
+
)
|
|
98
|
+
mobie_collection_entries.append(entry)
|
|
99
|
+
|
|
100
|
+
df = collection_dataframe(mobie_collection_entries)
|
|
101
|
+
output_path = Path(output_directory) / "mobie_collection.csv"
|
|
102
|
+
df.to_csv(output_path, index=False)
|
|
103
|
+
|
|
104
|
+
def folder(
|
|
105
|
+
input_directory: ExistingDirectory,
|
|
106
|
+
output_directory: ExistingDirectory,
|
|
107
|
+
/,
|
|
108
|
+
*,
|
|
109
|
+
intensity_paths: list[str] | None = None,
|
|
110
|
+
label_paths: list[str] | None = None,
|
|
111
|
+
):
|
|
112
|
+
mobie_collection_entries: list[MoBIECollectionEntry] = []
|
|
113
|
+
# Loop over all (.zarr) directories in input_directory
|
|
114
|
+
for zarr_dir in Path(input_directory).rglob("*.zarr"):
|
|
115
|
+
# Open zarr container
|
|
116
|
+
container = open_ome_zarr_container(zarr_dir)
|
|
117
|
+
logger.info(f"Processing {zarr_dir}")
|
|
118
|
+
image = container.get_image()
|
|
119
|
+
logger.info(image)
|
|
120
|
+
logger.info(image.path)
|
|
121
|
+
logger.info(image.channels_meta)
|
|
122
|
+
for channel in image.channel_labels:
|
|
123
|
+
logger.info(channel)
|
|
124
|
+
logger.info(image.get_channel_idx(channel))
|
|
125
|
+
for channel in image.channels_meta.channels:
|
|
126
|
+
logger.info(channel)
|
|
127
|
+
logger.info(channel.label)
|
|
128
|
+
logger.info(channel.channel_visualisation.start)
|
|
129
|
+
# Append an intensities entry for each channel
|
|
130
|
+
# for channel_index in range(image.get_channel_count()):
|
|
131
|
+
# entry = _create_intensities_entry(
|
|
132
|
+
# uri=str(zarr_dir.resolve()), # TODO path to image
|
|
133
|
+
# name=f"{zarr_dir.stem}_c{channel_index}",
|
|
134
|
+
# channel=channel_index,
|
|
135
|
+
# grid=f"channel_{channel_index}_grid",
|
|
136
|
+
# grid_position=(0, 0), # TODO compute position from filename/folder content
|
|
137
|
+
# )
|
|
138
|
+
# mobie_collection_entries.append(entry)
|
|
139
|
+
|
|
140
|
+
# df = collection_dataframe(mobie_collection_entries)
|
|
141
|
+
# output_path = output_directory / "mobie_collection.csv"
|
|
142
|
+
# df.to_csv(output_path, index=False)
|
|
143
|
+
|
|
144
|
+
def create_plate_project(
|
|
145
|
+
plate_zarr_path: ExistingDirectory,
|
|
146
|
+
output_directory: ExistingDirectory,
|
|
147
|
+
/,
|
|
148
|
+
) -> Project:
|
|
149
|
+
plate: OmeZarrPlate = open_ome_zarr_plate(plate_zarr_path)
|
|
150
|
+
logger.info(f"Creating MoBIE Project for plate at {plate_zarr_path}")
|
|
151
|
+
# Initialize Project
|
|
152
|
+
project: Project = Project(output_directory)
|
|
153
|
+
project.initialize_model(
|
|
154
|
+
description="Test project",
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Create Dataset for plate
|
|
158
|
+
plate_dataset: Dataset = project.new_dataset(
|
|
159
|
+
# name=plate.meta.plate.name,
|
|
160
|
+
name=plate_zarr_path.name,
|
|
161
|
+
)
|
|
162
|
+
plate_dataset.initialize_with_paths(
|
|
163
|
+
path_dict={},
|
|
164
|
+
is2d=True,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
plate_dataset.save()
|
|
168
|
+
|
|
169
|
+
# From the first well, get subpaths and channel names
|
|
170
|
+
# Then, for each subpath and each channel, create sources and merged grid views
|
|
171
|
+
first_well = plate.get_well(row=plate.rows[0], column=plate.columns[0])
|
|
172
|
+
path_names = first_well.paths()
|
|
173
|
+
logger.info(f"Path names in first well: {path_names}")
|
|
174
|
+
first_well_image = first_well.get_image(path_names[0]).get_image()
|
|
175
|
+
channel_names = [
|
|
176
|
+
channel.label for channel in first_well_image.channels_meta.channels
|
|
177
|
+
]
|
|
178
|
+
logger.info(f"Channel names: {channel_names}")
|
|
179
|
+
|
|
180
|
+
# format: sources[sub_path][channel_name] = pathdict (name: Path)
|
|
181
|
+
sources = defaultdict(lambda: defaultdict(dict))
|
|
182
|
+
sources_per_well = defaultdict(list)
|
|
183
|
+
|
|
184
|
+
for well_path in plate.wells_paths():
|
|
185
|
+
logger.info(f"Processing well: {well_path}")
|
|
186
|
+
# add sources for each subpath and channel
|
|
187
|
+
for sub_path in path_names:
|
|
188
|
+
for channel in channel_names:
|
|
189
|
+
logger.info(f" Channel: {channel}")
|
|
190
|
+
source_path = plate_zarr_path / well_path / sub_path
|
|
191
|
+
source_name = f"{well_path.replace("/", "")}_{channel}"
|
|
192
|
+
sources[sub_path][channel][source_name] = source_path
|
|
193
|
+
sources_per_well[well_path].append(source_name)
|
|
194
|
+
|
|
195
|
+
for sub_path, channel_dict in sources.items():
|
|
196
|
+
for channel_index, channel_name in enumerate(channel_names):
|
|
197
|
+
logger.info(f"Adding source for subpath {sub_path}, channel {channel_name}...")
|
|
198
|
+
plate_dataset.add_sources(
|
|
199
|
+
path_dict=channel_dict[channel_name],
|
|
200
|
+
channel_index=channel_index,
|
|
201
|
+
data_format="ome.zarr",
|
|
202
|
+
)
|
|
203
|
+
plate_dataset.add_merged_grid(
|
|
204
|
+
name=f"merged_grid_{sub_path}_{channel_name}",
|
|
205
|
+
sources=list(channel_dict[channel_name]),
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# Add region view containing all wells
|
|
209
|
+
plate_dataset.add_region_view(
|
|
210
|
+
name="all_wells",
|
|
211
|
+
map_of_sources=sources_per_well,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
plate_dataset.save()
|
|
216
|
+
project.save()
|
|
217
|
+
# add sources for each well and channel
|
|
218
|
+
# add merged grid view for each channel, containing all well sources for that channel
|
|
219
|
+
# add sources for labels
|
|
220
|
+
|
mozarrt/folder.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
from cyclopts.types import ExistingDirectory
|
|
3
|
+
from loguru import logger
|
|
4
|
+
from mobiedantic import Project, Dataset
|
|
5
|
+
from natsort import natsorted
|
|
6
|
+
from ngio import open_ome_zarr_container
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from ngio import OmeZarrContainer, Image
|
|
12
|
+
|
|
13
|
+
def project(
|
|
14
|
+
input_directory: ExistingDirectory,
|
|
15
|
+
output_directory: ExistingDirectory,
|
|
16
|
+
/,
|
|
17
|
+
*,
|
|
18
|
+
description: str | None = None,
|
|
19
|
+
):
|
|
20
|
+
project: Project = Project(output_directory)
|
|
21
|
+
project.initialize_model(
|
|
22
|
+
description=description or "Folder Project",
|
|
23
|
+
)
|
|
24
|
+
dataset: Dataset = project.new_dataset(
|
|
25
|
+
name=input_directory.name,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Create sources for each zarr (and each channel within it)
|
|
29
|
+
# then add merged grid views for each channel
|
|
30
|
+
|
|
31
|
+
zarr_dirs = natsorted(Path(input_directory).glob("*.zarr"))
|
|
32
|
+
first_zarr: OmeZarrContainer = open_ome_zarr_container(zarr_dirs[0])
|
|
33
|
+
first_zarr_image: Image = first_zarr.get_image()
|
|
34
|
+
|
|
35
|
+
# Determine if data is 2D based on number of spatial axes (excluding channel dimension)
|
|
36
|
+
axes_names = first_zarr_image.axes
|
|
37
|
+
# Count spatial axes (z, y, x) - typically 2D has [y, x] and 3D has [z, y, x]
|
|
38
|
+
is2d = "z" not in axes_names
|
|
39
|
+
logger.info(f"Axes: {axes_names}, is2D: {is2d}")
|
|
40
|
+
|
|
41
|
+
dataset.initialize_with_paths(
|
|
42
|
+
path_dict={},
|
|
43
|
+
is2d=is2d,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
channel_names = [
|
|
47
|
+
channel.label for channel in first_zarr_image.channels_meta.channels
|
|
48
|
+
]
|
|
49
|
+
logger.info(f"Channel names: {channel_names}")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# format: sources[channel_name] = pathdict (name: Path)
|
|
53
|
+
sources = defaultdict(dict)
|
|
54
|
+
position_map = defaultdict(list)
|
|
55
|
+
|
|
56
|
+
for zarr_dir in zarr_dirs:
|
|
57
|
+
logger.info(f"Processing {zarr_dir}")
|
|
58
|
+
position: OmeZarrContainer = open_ome_zarr_container(zarr_dir)
|
|
59
|
+
logger.info(position.meta)
|
|
60
|
+
for channel_name in channel_names:
|
|
61
|
+
logger.info(f" Channel: {channel_name}")
|
|
62
|
+
source_path = zarr_dir
|
|
63
|
+
source_name = f"{zarr_dir.stem}_{channel_name}"
|
|
64
|
+
sources[channel_name][source_name] = source_path
|
|
65
|
+
position_map[zarr_dir.stem].append(source_name)
|
|
66
|
+
|
|
67
|
+
for channel_index, channel_name in enumerate(channel_names):
|
|
68
|
+
logger.info(f"Adding source for channel {channel_name}...")
|
|
69
|
+
dataset.add_sources(
|
|
70
|
+
path_dict=sources[channel_name],
|
|
71
|
+
channel_index=channel_index,
|
|
72
|
+
data_format="ome.zarr",
|
|
73
|
+
)
|
|
74
|
+
dataset.add_merged_grid(
|
|
75
|
+
name=f"merged_grid_{channel_name}",
|
|
76
|
+
sources=list(sources[channel_name]),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Add region view containing all positions
|
|
80
|
+
dataset.add_region_view(
|
|
81
|
+
name="all_positions",
|
|
82
|
+
map_of_sources=position_map,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
dataset.save()
|
|
86
|
+
project.save()
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
@dataclass
|
|
5
|
+
class MoBIECollectionEntry:
|
|
6
|
+
uri: str
|
|
7
|
+
name: str | None = None
|
|
8
|
+
type: Literal["intensities", "labels", "spots"] | None = None
|
|
9
|
+
channel: int | None = None
|
|
10
|
+
color: str | None = None
|
|
11
|
+
blend: Literal["sum", "alpha"] | None = None
|
|
12
|
+
affine: tuple[float] | None = None
|
|
13
|
+
tps: str | None = None
|
|
14
|
+
view: str | None = None
|
|
15
|
+
exclusive: bool | None = None
|
|
16
|
+
group: str | None = None
|
|
17
|
+
contrast_limits: tuple[int, int] | None = None
|
|
18
|
+
grid: str | None = None # name of the grid
|
|
19
|
+
grid_position: tuple[int, int] | None = None
|
|
20
|
+
display: str | None = None
|
|
21
|
+
format: Literal["OmeZarr"] | None = None
|
|
22
|
+
spot_radius: float | None = None
|
|
23
|
+
bounding_box: str | None = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def collection_dataframe(entries: list[MoBIECollectionEntry]):
|
|
27
|
+
"""Convert a list of MoBIECollectionEntry to a pandas DataFrame."""
|
|
28
|
+
import pandas as pd
|
|
29
|
+
df = pd.DataFrame(entries)
|
|
30
|
+
return df
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mozarrt
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MoBIE OME-Zarr Tools
|
|
5
|
+
Author-email: Jan Eglinger <jan.eglinger@fmi.ch>
|
|
6
|
+
Requires-Python: >=3.12
|
|
7
|
+
Requires-Dist: cyclopts<5,>=4.5.0
|
|
8
|
+
Requires-Dist: loguru<0.8,>=0.7.3
|
|
9
|
+
Requires-Dist: mobiedantic<0.5,>=0.4.1
|
|
10
|
+
Requires-Dist: natsort<9,>=8.4.0
|
|
11
|
+
Requires-Dist: ngio<0.6,>=0.5.3
|
|
12
|
+
Requires-Dist: pandas<3,>=2.3.3
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
mozarrt/__init__.py,sha256=zumS9pWC3YakVXRQI4CSH-if4W_qq2s7yDJv-1emQE0,133
|
|
2
|
+
mozarrt/cli.py,sha256=LgjzmQI4htOIK5kyqWTKlPm20cxO9S5dfXjLY918A6o,422
|
|
3
|
+
mozarrt/collect.py,sha256=gKt7LdVoL85VqqUsbjTavNcfd-ziYnoRMJ-Ws4T0jY4,7590
|
|
4
|
+
mozarrt/folder.py,sha256=SEWirMFxD4RlxjZLx6h2kcsujYnRG6S4Y8zwNlJAJXw,2826
|
|
5
|
+
mozarrt/mobie_collection.py,sha256=gHmwSiKsma0r5Ru37MeRGbqljWD7YVzyyBkCmOMzGfA,977
|
|
6
|
+
mozarrt-0.1.0.dist-info/METADATA,sha256=HlvEHlgNdZcpYJCo8BrdnMAGUAnNfQ34SpVUbcovlpc,358
|
|
7
|
+
mozarrt-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
8
|
+
mozarrt-0.1.0.dist-info/entry_points.txt,sha256=VC78V3AHovyTKycOoSZG9qbTal6t_I6I3cQAJH71qEE,44
|
|
9
|
+
mozarrt-0.1.0.dist-info/RECORD,,
|