Skip to content

Commit f506717

Browse files
authored
Merge pull request #34 from fractal-analytics-platform/33_generalize_2d_to_3d_task
Update 2D to 3D task: allow no labels to be copied & copy all table types
2 parents 5908d4e + 505b6bb commit f506717

File tree

4 files changed

+113
-79
lines changed

4 files changed

+113
-79
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ authors = [
2727
# Required Python version and dependencies
2828
requires-python = ">=3.11"
2929
dependencies = [
30-
"ngio>=0.2.3, <0.3.0",
30+
"ngio>=0.2.8, <0.3.0",
3131
"fractal-task-tools==0.0.12",
3232
]
3333

src/fractal_helper_tasks/__FRACTAL_MANIFEST__.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,7 @@
131131
}
132132
},
133133
"required": [
134-
"zarr_url",
135-
"label_name"
134+
"zarr_url"
136135
],
137136
"type": "object",
138137
"title": "Convert2dSegmentationTo3d"

src/fractal_helper_tasks/convert_2D_segmentation_to_3D.py

Lines changed: 71 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
@validate_call
1515
def convert_2D_segmentation_to_3D(
1616
zarr_url: str,
17-
label_name: str,
17+
label_name: Optional[str] = None,
1818
level: str = "0",
1919
tables_to_copy: Optional[list[str]] = None,
2020
new_label_name: Optional[str] = None,
@@ -96,10 +96,7 @@ def convert_2D_segmentation_to_3D(
9696
if image_suffix_3D_to_add:
9797
zarr_3D_url += image_suffix_3D_to_add
9898

99-
if new_label_name is None:
100-
new_label_name = label_name
101-
if new_table_names is None:
102-
new_table_names = tables_to_copy
99+
ome_zarr_container_2d = ngio.open_ome_zarr_container(zarr_url)
103100

104101
try:
105102
ome_zarr_container_3d = ngio.open_ome_zarr_container(zarr_3D_url)
@@ -109,71 +106,82 @@ def convert_2D_segmentation_to_3D(
109106
f"suffix (set to {plate_suffix})."
110107
) from e
111108

112-
logger.info(
113-
f"Copying {label_name} from {zarr_url} to {zarr_3D_url} as "
114-
f"{new_label_name}."
115-
)
116-
117-
# 1) Load a 2D label image
118-
ome_zarr_container_2d = ngio.open_ome_zarr_container(zarr_url)
119-
label_img = ome_zarr_container_2d.get_label(label_name, path=level)
109+
if new_label_name is None:
110+
new_label_name = label_name
111+
if new_table_names is None:
112+
new_table_names = tables_to_copy
120113

121-
if not label_img.is_2d:
122-
raise ValueError(
123-
f"Label image {label_name} is not 2D. It has a shape of "
124-
f"{label_img.shape} and the axes "
125-
f"{label_img.axes_mapper.on_disk_axes_names}."
114+
if label_name:
115+
logger.info(
116+
f"Copying {label_name} from {zarr_url} to {zarr_3D_url} as "
117+
f"{new_label_name}."
126118
)
127119

128-
chunks = list(label_img.chunks)
129-
label_dask = label_img.get_array(mode="dask")
120+
# 1) Load a 2D label image
121+
label_img = ome_zarr_container_2d.get_label(label_name, path=level)
130122

131-
# 2) Set up the 3D label image
132-
ref_image_3d = ome_zarr_container_3d.get_image(
133-
pixel_size=label_img.pixel_size,
134-
)
135-
136-
z_index = label_img.axes_mapper.get_index("z")
137-
y_index = label_img.axes_mapper.get_index("y")
138-
x_index = label_img.axes_mapper.get_index("x")
139-
z_index_3d_reference = ref_image_3d.axes_mapper.get_index("z")
140-
if z_chunks:
141-
chunks[z_index] = z_chunks
142-
else:
143-
chunks[z_index] = ref_image_3d.chunks[z_index_3d_reference]
144-
chunks = tuple(chunks)
123+
if not label_img.is_2d:
124+
raise ValueError(
125+
f"Label image {label_name} is not 2D. It has a shape of "
126+
f"{label_img.shape} and the axes "
127+
f"{label_img.axes_mapper.on_disk_axes_names}."
128+
)
145129

146-
nb_z_planes = ref_image_3d.shape[z_index_3d_reference]
130+
chunks = list(label_img.chunks)
131+
label_dask = label_img.get_array(mode="dask")
147132

148-
shape_3d = (nb_z_planes, label_img.shape[y_index], label_img.shape[x_index])
133+
# 2) Set up the 3D label image
134+
ref_image_3d = ome_zarr_container_3d.get_image(
135+
pixel_size=label_img.pixel_size,
136+
)
149137

150-
pixel_size = label_img.pixel_size
151-
pixel_size.z = ref_image_3d.pixel_size.z
152-
axes_names = label_img.axes_mapper.on_disk_axes_names
138+
z_index = label_img.axes_mapper.get_index("z")
139+
y_index = label_img.axes_mapper.get_index("y")
140+
x_index = label_img.axes_mapper.get_index("x")
141+
z_index_3d_reference = ref_image_3d.axes_mapper.get_index("z")
142+
if z_chunks:
143+
chunks[z_index] = z_chunks
144+
else:
145+
chunks[z_index] = ref_image_3d.chunks[z_index_3d_reference]
146+
chunks = tuple(chunks)
147+
148+
nb_z_planes = ref_image_3d.shape[z_index_3d_reference]
149+
150+
shape_3d = (nb_z_planes, label_img.shape[y_index], label_img.shape[x_index])
151+
152+
pixel_size = label_img.pixel_size
153+
pixel_size.z = ref_image_3d.pixel_size.z
154+
axes_names = label_img.axes_mapper.on_disk_axes_names
155+
156+
z_extent = nb_z_planes * pixel_size.z
157+
158+
new_label_container = ome_zarr_container_3d.derive_label(
159+
name=new_label_name,
160+
ref_image=ref_image_3d,
161+
shape=shape_3d,
162+
pixel_size=pixel_size,
163+
axes_names=axes_names,
164+
chunks=chunks,
165+
dtype=label_img.dtype,
166+
overwrite=overwrite,
167+
)
153168

154-
z_extent = nb_z_planes * pixel_size.z
169+
# 3) Create the 3D stack of the label image
170+
label_img_3D = da.stack([label_dask.squeeze()] * nb_z_planes)
155171

156-
new_label_container = ome_zarr_container_3d.derive_label(
157-
name=new_label_name,
158-
ref_image=ref_image_3d,
159-
shape=shape_3d,
160-
pixel_size=pixel_size,
161-
axes_names=axes_names,
162-
chunks=chunks,
163-
dtype=label_img.dtype,
164-
overwrite=overwrite,
165-
)
172+
# 4) Save changed label image to OME-Zarr
173+
new_label_container.set_array(label_img_3D, axes_order="zyx")
166174

167-
# 3) Create the 3D stack of the label image
168-
label_img_3D = da.stack([label_dask.squeeze()] * nb_z_planes)
175+
logger.info(f"Saved {new_label_name} to 3D Zarr at full resolution")
176+
# 5) Build pyramids for label image
177+
new_label_container.consolidate()
178+
logger.info(f"Built a pyramid for the {new_label_name} label image")
169179

170-
# 4) Save changed label image to OME-Zarr
171-
new_label_container.set_array(label_img_3D, axes_order="zyx")
172-
173-
logger.info(f"Saved {new_label_name} to 3D Zarr at full resolution")
174-
# 5) Build pyramids for label image
175-
new_label_container.consolidate()
176-
logger.info(f"Built a pyramid for the {new_label_name} label image")
180+
else:
181+
logger.info(
182+
"No label_name provided, skipping label image conversion. "
183+
"Only tables will be copied."
184+
)
177185

178186
# 6) Copy tables
179187
if tables_to_copy:
@@ -183,6 +191,7 @@ def convert_2D_segmentation_to_3D(
183191
f"Table {table_name} not found in 2D OME-Zarr {zarr_url}."
184192
)
185193
table = ome_zarr_container_2d.get_table(table_name)
194+
print(table.type())
186195
if table.type() == "roi_table" or table.type() == "masking_roi_table":
187196
for roi in table.rois():
188197
roi.z_length = z_extent
@@ -191,21 +200,15 @@ def convert_2D_segmentation_to_3D(
191200
table=table,
192201
overwrite=overwrite,
193202
)
194-
elif table.type() == "feature_table":
195-
# For some reason, I need to load the table explicitly before
196-
# I can write it again
197-
# FIXME
203+
else:
204+
# Added to avoid an KeyError: 'obs' that occurs for some
205+
# AnnData tables otherwise
198206
table.dataframe # noqa #B018
199207
ome_zarr_container_3d.add_table(
200208
name=new_table_names[i],
201209
table=table,
202210
overwrite=overwrite,
203211
)
204-
else:
205-
logger.warning(
206-
f"Table {table_name} was not copied over. Tables of type "
207-
f"{table.type()} are not supported by this task so far."
208-
)
209212

210213
logger.info("Finished 2D to 3D conversion")
211214

tests/test_convert_2d_to_3d_segmentation.py

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44

55
import ngio
66
import numpy as np
7+
import pandas as pd
78
import pytest
9+
from ngio.images.label import build_masking_roi_table
10+
from ngio.tables import GenericTable
811

912
from fractal_helper_tasks.convert_2D_segmentation_to_3D import (
1013
convert_2D_segmentation_to_3D,
@@ -190,9 +193,7 @@ def test_2d_to_3d_real_data(tmp_zenodo_zarr: list[str]):
190193

191194
# Create a masking roi table in the 2D image
192195
ome_zarr_2d = ngio.open_ome_zarr_container(zarr_url)
193-
masking_roi_table = ome_zarr_2d.get_masked_image("nuclei").build_image_roi_table(
194-
name=tables_to_copy[0]
195-
)
196+
masking_roi_table = build_masking_roi_table(ome_zarr_2d.get_label(name=label_name))
196197

197198
ome_zarr_2d.add_table(
198199
name=tables_to_copy[0],
@@ -210,13 +211,44 @@ def test_2d_to_3d_real_data(tmp_zenodo_zarr: list[str]):
210211
ome_zarr_3d = ngio.open_ome_zarr_container(zarr_3D_label_url)
211212
label_img_3d = ome_zarr_3d.get_label(name=label_name).get_array(mode="dask")
212213
assert label_img_3d.shape == (2, 540, 1280)
214+
assert (
215+
ome_zarr_3d.list_tables()
216+
== ["FOV_ROI_table", "well_ROI_table"] + tables_to_copy # noqa RUF005
217+
)
218+
219+
220+
def test_2d_to_3d_real_data_no_label_copy(tmp_zenodo_zarr: list[str]):
221+
print(tmp_zenodo_zarr)
222+
zarr_url = f"{tmp_zenodo_zarr[1]}/B/03/0"
223+
tables_to_copy = ["generic_table", "nuclei"]
213224

214-
# for table_name in roi_table_names:
215-
# roi_table = ome_zarr_3d.get_roi_table(name=table_name)
216-
# assert roi_table is not None
217-
# assert isinstance(roi_table, zarr.core.Array)
225+
# Create a masking roi table in the 2D image
226+
ome_zarr_2d = ngio.open_ome_zarr_container(zarr_url)
227+
228+
# Add a generic table to be copied over
229+
generic_table = GenericTable(
230+
dataframe=pd.DataFrame({"col1": [1, 2], "col2": [3, 4]})
231+
)
218232

233+
ome_zarr_2d.add_table(
234+
name=tables_to_copy[0],
235+
table=generic_table,
236+
)
237+
238+
convert_2D_segmentation_to_3D(
239+
zarr_url=zarr_url,
240+
label_name=None,
241+
tables_to_copy=tables_to_copy,
242+
)
243+
244+
zarr_3D_label_url = f"{tmp_zenodo_zarr[0]}/B/03/0"
245+
# Check that the label has been copied correctly
246+
ome_zarr_3d = ngio.open_ome_zarr_container(zarr_3D_label_url)
247+
assert len(ome_zarr_3d.list_labels()) == 0
248+
assert (
249+
ome_zarr_3d.list_tables()
250+
== ["FOV_ROI_table", "well_ROI_table"] + tables_to_copy # noqa RUF005
251+
)
219252

220-
# TODO: Add a feature table & have it copied over
221253

222254
# TODO: Test table content more carefully

0 commit comments

Comments
 (0)