Skip to content

Commit b7fc222

Browse files
committed
ROI update (closes #112, closes #113):
* Do not store pixel sizes in the FOV-ROI table; * Always parse pixel sizes from the zattrs file, when needed; * Add level arg to extract_zyx_pixel_sizes_from_zattrs.
1 parent 3623dc0 commit b7fc222

5 files changed

+180
-57
lines changed

fractal/tasks/create_zarr_structure.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -186,9 +186,6 @@ def create_zarr_structure(
186186
site_metadata, total_files = parse_yokogawa_metadata(
187187
mrf_path, mlf_path
188188
)
189-
# FIXME: hardcoded
190-
image_size = {"x": 2560, "y": 2160}
191-
well_size_z = 10
192189

193190
# Extract pixel sizes
194191
pixel_size_z = site_metadata["pixel_size_z"][0]
@@ -348,8 +345,6 @@ def create_zarr_structure(
348345
# Prepare and write anndata table of FOV ROIs
349346
FOV_ROIs_table = prepare_FOV_ROI_table(
350347
site_metadata.loc[f"{row+column}"],
351-
image_size=image_size,
352-
well_size_z=well_size_z,
353348
)
354349
group_tables = group_FOV.create_group("tables/") # noqa: F841
355350
write_elem(group_tables, "FOV_ROI_table", FOV_ROIs_table)

fractal/tasks/illumination_correction.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222

2323
from fractal.tasks.lib_pyramid_creation import write_pyramid
2424
from fractal.tasks.lib_regions_of_interest import convert_ROI_table_to_indices
25+
from fractal.tasks.lib_regions_of_interest import (
26+
extract_zyx_pixel_sizes_from_zattrs,
27+
)
2528
from fractal.tasks.lib_regions_of_interest import (
2629
split_3D_indices_into_z_layers,
2730
)
@@ -135,6 +138,7 @@ def illumination_correction(
135138
newzarrurl += "/"
136139

137140
# Hard-coded values for the image size
141+
# FIXME can we parse this from somewhere?
138142
img_size_y = 2160
139143
img_size_x = 2560
140144

@@ -192,9 +196,15 @@ def illumination_correction(
192196
# Read FOV ROIs
193197
FOV_ROI_table = ad.read_zarr(f"{zarrurl}tables/FOV_ROI_table")
194198

199+
# Read pixel sizes from zattrs file
200+
pixel_sizes_zyx = extract_zyx_pixel_sizes_from_zattrs(zarrurl + ".zattrs")
201+
195202
# Create list of indices for 3D FOVs spanning the entire Z direction
196203
list_indices = convert_ROI_table_to_indices(
197-
FOV_ROI_table, level=0, coarsening_xy=coarsening_xy
204+
FOV_ROI_table,
205+
level=0,
206+
coarsening_xy=coarsening_xy,
207+
pixel_sizes_zyx=pixel_sizes_zyx,
198208
)
199209

200210
# Create the final list of single-Z-layer FOVs
@@ -216,8 +226,11 @@ def illumination_correction(
216226
data_zyx_new = da.empty_like(data_zyx)
217227

218228
for indices in list_indices:
229+
219230
s_z, e_z, s_y, e_y, s_x, e_x = indices[:]
220231
shape = [e_z - s_z, e_y - s_y, e_x - s_x]
232+
if min(shape) == 0:
233+
raise Exception(f"ERROR: ROI indices lead to shape {shape}")
221234
new_img = delayed_correct(
222235
data_zyx[s_z:e_z, s_y:e_y, s_x:e_x],
223236
illum_img,

fractal/tasks/image_labeling.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import time
1818
from concurrent.futures import ThreadPoolExecutor
1919

20-
import anndata as ad
2120
import dask
2221
import dask.array as da
2322
import numpy as np
@@ -26,6 +25,9 @@
2625
from cellpose import models
2726

2827
from fractal.tasks.lib_pyramid_creation import write_pyramid
28+
from fractal.tasks.lib_regions_of_interest import (
29+
extract_zyx_pixel_sizes_from_zattrs,
30+
)
2931

3032

3133
def segment_FOV(
@@ -127,23 +129,18 @@ def image_labeling(
127129
do_3D = data_zyx.shape[0] > 1
128130
if do_3D:
129131
if anisotropy is None:
130-
131-
adata = ad.read_zarr(f"{zarrurl}tables/FOV_ROI_table")
132-
pixel_size_x = adata[:, "pixel_size_x"].X[0, 0]
133-
pixel_size_y = adata[:, "pixel_size_y"].X[0, 0]
134-
pixel_size_z = adata[:, "pixel_size_z"].X[0, 0]
132+
# Read pixel sizes from zattrs file
133+
pxl_zyx = extract_zyx_pixel_sizes_from_zattrs(
134+
zarrurl + ".zattrs", level=labeling_level
135+
)
136+
pixel_size_z, pixel_size_y, pixel_size_x = pxl_zyx[:]
135137
if not np.allclose(pixel_size_x, pixel_size_y):
136138
raise Exception(
137139
"ERROR: XY anisotropy detected\n"
138140
f"pixel_size_x={pixel_size_x}\n"
139141
f"pixel_size_y={pixel_size_y}"
140142
)
141143
anisotropy = pixel_size_z / pixel_size_x
142-
if labeling_level > 0:
143-
raise NotImplementedError(
144-
"TODO: fix automatic anisotropy "
145-
"detection for higher levels"
146-
)
147144

148145
# Check model_type
149146
if model_type not in ["nuclei", "cyto2", "cyto"]:

fractal/tasks/lib_regions_of_interest.py

Lines changed: 146 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import json
12
import math
2-
from typing import Dict
33
from typing import List
4+
from typing import Tuple
45
from typing import Union
56

67
import anndata as ad
@@ -10,24 +11,21 @@
1011

1112
def prepare_FOV_ROI_table(
1213
df: pd.DataFrame,
13-
image_size: Union[Dict, None] = None,
14-
well_size_z: int = None,
1514
) -> ad.AnnData:
1615

17-
if image_size is None:
18-
raise Exception("Missing image_size arg in prepare_ROIs_table")
19-
if well_size_z is None:
20-
raise Exception("Missing well_size_z arg in prepare_ROIs_table")
16+
for mu in ["x", "y", "z"]:
2117

22-
# Reset reference values for coordinates
23-
df["x_micrometer"] -= df["x_micrometer"].min()
24-
df["y_micrometer"] -= df["y_micrometer"].min()
25-
df["z_micrometer"] -= df["z_micrometer"].min()
18+
# Reset reference values for coordinates
19+
df[f"{mu}_micrometer"] -= df[f"{mu}_micrometer"].min()
2620

27-
# Obtain box size in physical units
28-
df["len_x_micrometer"] = image_size["x"] * df["pixel_size_x"]
29-
df["len_y_micrometer"] = image_size["y"] * df["pixel_size_y"]
30-
df["len_z_micrometer"] = well_size_z * df["pixel_size_z"]
21+
# Obtain box size in physical units
22+
df[f"len_{mu}_micrometer"] = df[f"{mu}_pixel"] * df[f"pixel_size_{mu}"]
23+
24+
# Remove information about pixel sizes in physical units
25+
df.drop(f"pixel_size_{mu}", inplace=True, axis=1)
26+
27+
# Remove information about array size in pixels
28+
df.drop(f"{mu}_pixel", inplace=True, axis=1)
3129

3230
# Remove unused column
3331
df.drop("bit_depth", inplace=True, axis=1)
@@ -52,22 +50,17 @@ def prepare_FOV_ROI_table(
5250
return adata
5351

5452

55-
def convert_FOV_ROIs_3D_to_2D(adata: ad.AnnData = None) -> ad.AnnData:
53+
def convert_FOV_ROIs_3D_to_2D(
54+
adata: ad.AnnData = None, pixel_size_z: float = None
55+
) -> ad.AnnData:
5656

57-
# FIXME: use this piece of code (or similar) in tests
58-
"""
59-
adata = ad.AnnData(X=np.zeros((2, 3)), dtype=int)
60-
adata.obs_names = ["FOV1", "FOV2"]
61-
adata.var_names = ["z", "len_z", "pixel_size_z"]
62-
adata[:, "z"].X = [[2], [4]]
63-
adata[:, "len_z"].X = [[5], [7]]
64-
adata[:, "pixel_size_z"].X = [[2], [2]]
65-
"""
57+
if pixel_size_z is None:
58+
raise Exception("Missing pixel_size_z in convert_FOV_ROIs_3D_to_2D")
6659

6760
# Compress a 3D stack of images to a single Z plane,
6861
# with thickness equal to pixel_size_z
6962
df = adata.to_df()
70-
df["len_z_micrometer"] = df["pixel_size_z"]
63+
df["len_z_micrometer"] = pixel_size_z
7164

7265
# Assign dtype explicitly, to avoid
7366
# >> UserWarning: X converted to numpy array with dtype float64
@@ -88,10 +81,13 @@ def convert_ROI_table_to_indices(
8881
ROI: ad.AnnData,
8982
level: int = 0,
9083
coarsening_xy: int = 2,
91-
) -> List[List]:
84+
pixel_sizes_zyx: Union[List[float], Tuple[float]] = None,
85+
) -> List[List[int]]:
9286

9387
list_indices = []
9488

89+
pixel_size_z, pixel_size_y, pixel_size_x = pixel_sizes_zyx
90+
9591
for FOV in sorted(ROI.obs_names):
9692

9793
# Extract data from anndata table
@@ -101,9 +97,6 @@ def convert_ROI_table_to_indices(
10197
len_x_micrometer = ROI[FOV, "len_x_micrometer"].X[0, 0]
10298
len_y_micrometer = ROI[FOV, "len_y_micrometer"].X[0, 0]
10399
len_z_micrometer = ROI[FOV, "len_z_micrometer"].X[0, 0]
104-
pixel_size_x = ROI[FOV, "pixel_size_x"].X[0, 0]
105-
pixel_size_y = ROI[FOV, "pixel_size_y"].X[0, 0]
106-
pixel_size_z = ROI[FOV, "pixel_size_z"].X[0, 0]
107100

108101
# Set pyramid-level pixel sizes
109102
prefactor = coarsening_xy**level
@@ -151,7 +144,10 @@ def split_3D_indices_into_z_layers(
151144

152145

153146
def _inspect_ROI_table(
154-
path: str = None, level: int = 0, coarsening_xy: int = 2
147+
path: str = None,
148+
level: int = 0,
149+
coarsening_xy: int = 2,
150+
pixel_sizes_zyx=[1.0, 0.1625, 0.1625],
155151
) -> None:
156152

157153
adata = ad.read_zarr(path)
@@ -161,7 +157,10 @@ def _inspect_ROI_table(
161157
print()
162158

163159
list_indices = convert_ROI_table_to_indices(
164-
adata, level=level, coarsening_xy=coarsening_xy
160+
adata,
161+
level=level,
162+
coarsening_xy=coarsening_xy,
163+
pixel_sizes_zyx=pixel_sizes_zyx,
165164
)
166165

167166
list_indices = split_3D_indices_into_z_layers(list_indices)
@@ -174,10 +173,120 @@ def _inspect_ROI_table(
174173
print()
175174

176175

176+
def temporary_test():
177+
178+
pixel_size_z = 1.0
179+
pixel_size_y = 0.1625
180+
pixel_size_x = 0.1625
181+
182+
df = pd.DataFrame(np.zeros((2, 10)), dtype=int)
183+
df.index = ["FOV1", "FOV2"]
184+
df.columns = [
185+
"x_micrometer",
186+
"y_micrometer",
187+
"z_micrometer",
188+
"x_pixel",
189+
"y_pixel",
190+
"z_pixel",
191+
"pixel_size_x",
192+
"pixel_size_y",
193+
"pixel_size_z",
194+
"bit_depth",
195+
]
196+
df["x_micrometer"] = [0.0, 416.0]
197+
df["y_micrometer"] = [0.0, 0.0]
198+
df["z_micrometer"] = [0.0, 0.0]
199+
df["x_pixel"] = [2560, 2560]
200+
df["y_pixel"] = [2160, 2160]
201+
df["z_pixel"] = [5, 5]
202+
df["pixel_size_x"] = [pixel_size_x] * 2
203+
df["pixel_size_y"] = [pixel_size_y] * 2
204+
df["pixel_size_z"] = [pixel_size_z] * 2
205+
df["bit_depth"] = [16.0, 16.0]
206+
207+
print("DataFrame")
208+
print(df)
209+
print()
210+
211+
adata = prepare_FOV_ROI_table(df)
212+
213+
print("AnnData table")
214+
print(adata.var_names)
215+
print(adata.obs_names)
216+
print(adata.X)
217+
print()
218+
219+
print("Indices 3D")
220+
pixel_sizes_zyx = [pixel_size_z, pixel_size_y, pixel_size_x]
221+
list_indices = convert_ROI_table_to_indices(
222+
adata, level=0, coarsening_xy=2, pixel_sizes_zyx=pixel_sizes_zyx
223+
)
224+
for indices in list_indices:
225+
print(indices)
226+
print()
227+
228+
print("Indices 3D / split")
229+
list_indices = split_3D_indices_into_z_layers(list_indices)
230+
for indices in list_indices:
231+
print(indices)
232+
print()
233+
234+
print("Indices 2D")
235+
adata = convert_FOV_ROIs_3D_to_2D(adata, pixel_size_z)
236+
list_indices = convert_ROI_table_to_indices(
237+
adata, level=0, coarsening_xy=2, pixel_sizes_zyx=pixel_sizes_zyx
238+
)
239+
for indices in list_indices:
240+
print(indices)
241+
print()
242+
243+
244+
def extract_zyx_pixel_sizes_from_zattrs(zattrs_path: str, level: int = 0):
245+
with open(zattrs_path, "r") as jsonfile:
246+
zattrs = json.load(jsonfile)
247+
248+
try:
249+
250+
# Identify multiscales
251+
multiscales = zattrs["multiscales"]
252+
253+
# Check that there is a single multiscale
254+
if len(multiscales) > 1:
255+
raise Exception(f"ERROR: There are {len(multiscales)} multiscales")
256+
257+
# Check that there are no datasets-global transformations
258+
if "coordinateTransformations" in multiscales[0].keys():
259+
raise Exception(
260+
"ERROR: coordinateTransformations at the multiscales "
261+
"level are not currently supported"
262+
)
263+
264+
# Identify all datasets (AKA pyramid levels)
265+
datasets = multiscales[0]["datasets"]
266+
267+
# Select highest-resolution dataset
268+
transformations = datasets[level]["coordinateTransformations"]
269+
for t in transformations:
270+
if t["type"] == "scale":
271+
return t["scale"]
272+
raise Exception(
273+
"ERROR:"
274+
f" no scale transformation found for level {level}"
275+
f" in {zattrs_path}"
276+
)
277+
278+
except KeyError as e:
279+
raise KeyError(
280+
"extract_zyx_pixel_sizes_from_zattrs failed, for {zattrs_path}\n",
281+
e,
282+
)
283+
284+
177285
if __name__ == "__main__":
178-
import sys
286+
# import sys
287+
# args = sys.argv[1:]
288+
# types = [str, int, int]
289+
# args = [types[ix](x) for ix, x in enumerate(args)]
290+
# _inspect_ROI_table(*args)
179291

180-
args = sys.argv[1:]
181-
types = [str, int, int]
182-
args = [types[ix](x) for ix, x in enumerate(args)]
183-
_inspect_ROI_table(*args)
292+
temporary_test()

0 commit comments

Comments
 (0)