Skip to content

Commit 0eaeab4

Browse files
authored
Merge pull request #1063 from fractal-analytics-platform/1061_apply_registration_fix
Fix table copying in apply_registration_to_image task
2 parents 998d408 + 1fadb06 commit 0eaeab4

17 files changed

Lines changed: 384 additions & 212 deletions

CHANGELOG.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
**Note**: Numbers like (\#123) point to closed Pull Requests on the fractal-tasks-core repository.
22

3-
# Unreleased
3+
# 2.1.0
4+
* Tasks:
5+
* Change default table backends to better fit each task's data (\#1063):
6+
* `measure_features`: default `table_backend` is now `"parquet"` (was `"anndata"`).
7+
* `import_ome_zarr`: default `table_backend` for `image_ROI_table`/`grid_ROI_table` is now `"csv"` (was `"anndata"`).
8+
* `threshold_segmentation`'s `CreateMaskingRoiTable`: default `table_backend` is now `"csv"` (was `"anndata"`).
9+
* `projection`/`compute_projection_hcs`: confirmed (no code change needed) that the projected image's ROI tables already preserve the input image's table backend, and added regression tests for this.
10+
* Refactor `import_ome_zarr` task interface (\#1063):
11+
* `update_omero_metadata`, `add_image_roi_table`, and `add_grid_roi_table` now default to `False` (previously `True`), so ROI-table/metadata creation is now opt-in.
12+
* All parameters after `zarr_name` are now grouped into a single `advanced_options: AdvancedOptions` argument.
13+
* `add_grid_roi_table`/`grid_y_shape`/`grid_x_shape` are replaced by `advanced_options.grid_roi_table: GridRoiTable | None`; set it to build a `grid_ROI_table`, leave it `None` to skip it.
14+
* Fix table copying in `apply_registration_to_image` task: Use the existing table backend for the newly written table, perform table writing outside the try/except loop to catch reading race conditions and provide reading exception in the Runtime Error (\#1063).
15+
* Chore:
16+
* Handle deprecation warnings in ngio (\#1063).
417
* Documentation:
518
* Remove `extra_javascript` from docs (\#1055).
619
* Move from `mkdocs` to `zensical` (\#1060).

fractal_tasks_core/__FRACTAL_MANIFEST__.json

Lines changed: 87 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -418,8 +418,8 @@
418418
"type": "string"
419419
},
420420
"table_backend": {
421-
"default": "anndata",
422-
"description": "Backend to use for storing the masking ROI table. Options are \"anndata\", \"json\",\n\"csv\", and \"parquet\".",
421+
"default": "csv",
422+
"description": "Backend to use for storing the masking ROI table. Options are \"anndata\", \"json\",\n\"csv\", and \"parquet\". Defaults to \"csv\".",
423423
"enum": [
424424
"anndata",
425425
"json",
@@ -961,8 +961,8 @@
961961
"type": "boolean"
962962
},
963963
"table_backend": {
964-
"default": "anndata",
965-
"description": "Table backend to use for the output table. Defaults to \"anndata\".",
964+
"default": "parquet",
965+
"description": "Table backend to use for the output table. Defaults to \"parquet\".",
966966
"enum": [
967967
"anndata",
968968
"json",
@@ -1122,7 +1122,7 @@
11221122
"level_path": null,
11231123
"use_scaling": true,
11241124
"use_cache": true,
1125-
"table_backend": "anndata"
1125+
"table_backend": "parquet"
11261126
},
11271127
"title": "Advanced Options",
11281128
"description": "Advanced options for feature measurement."
@@ -1458,6 +1458,77 @@
14581458
"type": "converter_non_parallel",
14591459
"executable_non_parallel": "import_ome_zarr.py",
14601460
"args_schema_non_parallel": {
1461+
"$defs": {
1462+
"AdvancedOptions": {
1463+
"description": "Advanced options for importing an OME-Zarr.",
1464+
"properties": {
1465+
"update_omero_metadata": {
1466+
"default": false,
1467+
"description": "Whether to update Omero-channels metadata with channel labels and\nwavelength ids to make them compatible with some downstream Fractal tasks.",
1468+
"title": "Update Omero Metadata",
1469+
"type": "boolean"
1470+
},
1471+
"add_image_roi_table": {
1472+
"default": false,
1473+
"description": "Whether to add an `image_ROI_table` table to each image, with a single ROI\ncovering the whole image.",
1474+
"title": "Add Image Roi Table",
1475+
"type": "boolean"
1476+
},
1477+
"grid_roi_table": {
1478+
"anyOf": [
1479+
{
1480+
"$ref": "#/$defs/GridRoiTable"
1481+
},
1482+
{
1483+
"type": "null"
1484+
}
1485+
],
1486+
"default": null,
1487+
"description": "If set, add a `grid_ROI_table` table to each image, with the image split\ninto a rectangular grid of ROIs of the given shape. If `None`, no\n`grid_ROI_table` is created.",
1488+
"title": "Grid_Roi_Table"
1489+
},
1490+
"table_backend": {
1491+
"default": "csv",
1492+
"description": "Table backend to use for the new ROI tables. Defaults to \"csv\".",
1493+
"enum": [
1494+
"anndata",
1495+
"json",
1496+
"csv",
1497+
"parquet"
1498+
],
1499+
"title": "Table Backend",
1500+
"type": "string"
1501+
},
1502+
"overwrite": {
1503+
"default": false,
1504+
"description": "Whether new ROI tables (added when `add_image_roi_table=True` and/or\n`grid_roi_table` is not `None`) can overwrite existing ones.",
1505+
"title": "Overwrite",
1506+
"type": "boolean"
1507+
}
1508+
},
1509+
"title": "AdvancedOptions",
1510+
"type": "object"
1511+
},
1512+
"GridRoiTable": {
1513+
"description": "Configuration for building a `grid_ROI_table`.",
1514+
"properties": {
1515+
"grid_y_shape": {
1516+
"default": 2,
1517+
"description": "Number of ROIs along the Y axis. The image is split into a `grid_y_shape` by\n`grid_x_shape` grid of ROIs (e.g. the default 2 by 2 produces 4 ROIs).",
1518+
"title": "Grid Y Shape",
1519+
"type": "integer"
1520+
},
1521+
"grid_x_shape": {
1522+
"default": 2,
1523+
"description": "Number of ROIs along the X axis. The image is split into a `grid_y_shape` by\n`grid_x_shape` grid of ROIs (e.g. the default 2 by 2 produces 4 ROIs).",
1524+
"title": "Grid X Shape",
1525+
"type": "integer"
1526+
}
1527+
},
1528+
"title": "GridRoiTable",
1529+
"type": "object"
1530+
}
1531+
},
14611532
"additionalProperties": false,
14621533
"properties": {
14631534
"zarr_dir": {
@@ -1470,53 +1541,17 @@
14701541
"type": "string",
14711542
"description": "The OME-Zarr name, without its parent folder. The parent folder is provided by zarr_dir; e.g. `zarr_name=\"array.zarr\"`, if the OME-Zarr path is in `/zarr_dir/array.zarr`."
14721543
},
1473-
"update_omero_metadata": {
1474-
"default": true,
1475-
"title": "Update Omero Metadata",
1476-
"type": "boolean",
1477-
"description": "Whether to update Omero-channels metadata, to make them Fractal-compatible."
1478-
},
1479-
"add_image_roi_table": {
1480-
"default": true,
1481-
"title": "Add Image Roi Table",
1482-
"type": "boolean",
1483-
"description": "Whether to add a `image_ROI_table` table to each image, with a single ROI covering the whole image."
1484-
},
1485-
"add_grid_roi_table": {
1486-
"default": true,
1487-
"title": "Add Grid Roi Table",
1488-
"type": "boolean",
1489-
"description": "Whether to add a `grid_ROI_table` table to each image, with the image split into a rectangular grid of ROIs."
1490-
},
1491-
"grid_y_shape": {
1492-
"default": 2,
1493-
"title": "Grid Y Shape",
1494-
"type": "integer",
1495-
"description": "Number of ROIs along the Y axis of `grid_ROI_table`. The image is split into a `grid_y_shape` by `grid_x_shape` grid of ROIs (e.g. the default 2 by 2 produces 4 ROIs)."
1496-
},
1497-
"grid_x_shape": {
1498-
"default": 2,
1499-
"title": "Grid X Shape",
1500-
"type": "integer",
1501-
"description": "Number of ROIs along the X axis of `grid_ROI_table`. The image is split into a `grid_y_shape` by `grid_x_shape` grid of ROIs (e.g. the default 2 by 2 produces 4 ROIs)."
1502-
},
1503-
"table_backend": {
1504-
"default": "anndata",
1505-
"enum": [
1506-
"anndata",
1507-
"json",
1508-
"csv",
1509-
"parquet"
1510-
],
1511-
"title": "Table Backend",
1512-
"type": "string",
1513-
"description": "Backend to use for the new ROI tables. Defaults to \"anndata\"."
1514-
},
1515-
"overwrite": {
1516-
"default": false,
1517-
"title": "Overwrite",
1518-
"type": "boolean",
1519-
"description": "Whether new ROI tables (added when `add_image_roi_table` and/or `add_grid_roi_table` are `True`) can overwrite existing ones."
1544+
"advanced_options": {
1545+
"$ref": "#/$defs/AdvancedOptions",
1546+
"default": {
1547+
"update_omero_metadata": false,
1548+
"add_image_roi_table": false,
1549+
"grid_roi_table": null,
1550+
"table_backend": "csv",
1551+
"overwrite": false
1552+
},
1553+
"title": "Advanced Options",
1554+
"description": "Advanced options for importing an OME-Zarr, including whether to add `image_ROI_table`/`grid_ROI_table` tables, whether to update Omero-channels metadata, the table backend to use, and whether new tables can overwrite existing ones."
15201555
}
15211556
},
15221557
"required": [
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Copyright 2022-2026 (C) BioVisionCenter, University of Zurich
2+
"""Pydantic models for the import_ome_zarr task."""
3+
4+
from pydantic import BaseModel
5+
6+
from fractal_tasks_core._utils import AVAILABLE_TABLE_BACKENDS
7+
8+
9+
class GridRoiTable(BaseModel):
10+
"""Configuration for building a `grid_ROI_table`."""
11+
12+
grid_y_shape: int = 2
13+
"""
14+
Number of ROIs along the Y axis. The image is split into a `grid_y_shape` by
15+
`grid_x_shape` grid of ROIs (e.g. the default 2 by 2 produces 4 ROIs).
16+
"""
17+
18+
grid_x_shape: int = 2
19+
"""
20+
Number of ROIs along the X axis. The image is split into a `grid_y_shape` by
21+
`grid_x_shape` grid of ROIs (e.g. the default 2 by 2 produces 4 ROIs).
22+
"""
23+
24+
25+
class AdvancedOptions(BaseModel):
26+
"""Advanced options for importing an OME-Zarr."""
27+
28+
update_omero_metadata: bool = False
29+
"""
30+
Whether to update Omero-channels metadata with channel labels and
31+
wavelength ids to make them compatible with some downstream Fractal tasks.
32+
"""
33+
34+
add_image_roi_table: bool = False
35+
"""
36+
Whether to add an `image_ROI_table` table to each image, with a single ROI
37+
covering the whole image.
38+
"""
39+
40+
grid_roi_table: GridRoiTable | None = None
41+
"""
42+
If set, add a `grid_ROI_table` table to each image, with the image split
43+
into a rectangular grid of ROIs of the given shape. If `None`, no
44+
`grid_ROI_table` is created.
45+
"""
46+
47+
table_backend: AVAILABLE_TABLE_BACKENDS = "csv"
48+
"""
49+
Table backend to use for the new ROI tables. Defaults to "csv".
50+
"""
51+
52+
overwrite: bool = False
53+
"""
54+
Whether new ROI tables (added when `add_image_roi_table=True` and/or
55+
`grid_roi_table` is not `None`) can overwrite existing ones.
56+
"""

fractal_tasks_core/_measure_features_utils.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from pydantic import BaseModel, Field
1313
from skimage import measure
1414

15-
from fractal_tasks_core._utils import AVAILABLE_TABLE_BACKENDS, DEFAULT_TABLE_BACKEND
15+
from fractal_tasks_core._utils import AVAILABLE_TABLE_BACKENDS
1616

1717

1818
class ShapeFeatures(BaseModel):
@@ -155,9 +155,9 @@ class AdvancedOptions(BaseModel):
155155
but can also increase memory usage. Defaults to True.
156156
"""
157157

158-
table_backend: AVAILABLE_TABLE_BACKENDS = DEFAULT_TABLE_BACKEND
158+
table_backend: AVAILABLE_TABLE_BACKENDS = "parquet"
159159
"""
160-
Table backend to use for the output table. Defaults to "anndata".
160+
Table backend to use for the output table. Defaults to "parquet".
161161
"""
162162

163163

fractal_tasks_core/_threshold_segmentation_utils.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from skimage.filters import threshold_otsu
1111
from skimage.measure import label
1212

13-
from fractal_tasks_core._utils import AVAILABLE_TABLE_BACKENDS, DEFAULT_TABLE_BACKEND
13+
from fractal_tasks_core._utils import AVAILABLE_TABLE_BACKENDS
1414

1515
logger = logging.getLogger("threshold_segmentation_task_utils")
1616

@@ -32,10 +32,10 @@ class CreateMaskingRoiTable(BaseModel):
3232
"{output_label_name}", which will be replaced by the name of the label image used
3333
for segmentation.
3434
"""
35-
table_backend: AVAILABLE_TABLE_BACKENDS = DEFAULT_TABLE_BACKEND
35+
table_backend: AVAILABLE_TABLE_BACKENDS = "csv"
3636
"""
3737
Backend to use for storing the masking ROI table. Options are "anndata", "json",
38-
"csv", and "parquet".
38+
"csv", and "parquet". Defaults to "csv".
3939
"""
4040

4141
def get_table_name(self, output_label_name: str) -> str:

fractal_tasks_core/_utils.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from typing import Literal, TypeAlias
66

77
AVAILABLE_TABLE_BACKENDS: TypeAlias = Literal["anndata", "json", "csv", "parquet"]
8-
DEFAULT_TABLE_BACKEND: AVAILABLE_TABLE_BACKENDS = "anndata"
98

109

1110
@dataclass

fractal_tasks_core/apply_registration_to_image.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -254,23 +254,38 @@ def apply_registration_to_image(
254254
for table_name, source in tables_to_copy.items():
255255
logger.info(f"Copying table: {table_name}")
256256
# Retry loop to guard against race conditions (see issue #516)
257+
last_exception: Exception | None = None
257258
for attempt in range(max_retries):
258259
try:
259260
table = source.get_table(table_name)
260-
new_ome_zarr.add_table(name=table_name, table=table, overwrite=True)
261261
break
262-
except Exception:
262+
except Exception as e:
263+
last_exception = e
263264
logger.debug(
264265
f"Table {table_name} not found in attempt {attempt}. "
265266
f"Waiting {sleep_time} seconds before trying again."
266267
)
267268
time.sleep(sleep_time)
268269
else:
269270
raise RuntimeError(
270-
f"Table {table_name} not found after {max_retries} attempts. "
271-
"Check whether this table actually exists. If it does, "
272-
"this may be a race condition issue."
271+
f"Table {table_name} could not be loaded after "
272+
f"{max_retries} attempts. "
273+
f"The original error was: {last_exception}"
274+
) from last_exception
275+
# Writing table outside of the retry loop intentionally. We only
276+
# want to catch loading race conditions above, not real writing
277+
# errors.
278+
# Only overwrite the backend if it is set before (which should
279+
# always be the case for loaded tables)
280+
if table.backend_name:
281+
new_ome_zarr.add_table(
282+
name=table_name,
283+
table=table,
284+
backend=table.backend_name,
285+
overwrite=True,
273286
)
287+
else:
288+
new_ome_zarr.add_table(name=table_name, table=table, overwrite=True)
274289

275290
####################
276291
# Clean up Zarr file

0 commit comments

Comments
 (0)