diff --git a/README.rst b/README.rst index 5d43e47..bc3f708 100644 --- a/README.rst +++ b/README.rst @@ -12,7 +12,7 @@ from OMERO as zarr files, according to the spec at https://github.com/ome/omero-ms-zarr/blob/master/spec.md as well as Masks associated with Images. -Images are 5D arrays of shape `(t, c, z, y, x)`. +Images are nD arrays of shape, up to `(t, c, z, y, x)`. Plates are a hierarchy of `plate/row/column/field(image)`. Masks are 2D bitmasks which can exist on muliplte planes of an Image. In `ome-zarr` sets of Masks are collected together into "labels". @@ -20,8 +20,7 @@ In `ome-zarr` sets of Masks are collected together into "labels". It supports export using 2 alternative methods: - By default the OMERO API is used to load planes as numpy arrays - and the zarr file is created from this data. NB: currently, large - tiled images are not supported by this method. + and the zarr file is created from this data. - Alternatively, if you can read directly from the OMERO binary repository and have installed https://github.com/glencoesoftware/bioformats2raw @@ -37,16 +36,19 @@ Images and Plates To export Images or Plates via the OMERO API:: - # Image will be saved in current directory as 1.zarr + # Image will be saved in current directory as 1.ome.zarr $ omero zarr export Image:1 - # Plate will be saved in current directory as 2.zarr + # Plate will be saved in current directory as 2.ome.zarr $ omero zarr export Plate:2 + # Use the Image or Plate 'name' to save e.g. my_image.ome.zarr + $ omero zarr --name_by name export Image:1 + # Specify an output directory $ omero zarr --output /home/user/zarr_files export Image:1 - # By default, a tile size of 1024 is used. Specify values with + # By default, a tile (chunk) size of 1024 is used. Specify values with $ omero zarr export Image:1 --tile_width 256 --tile_height 256 @@ -66,24 +68,28 @@ Masks and Polygons To export Masks or Polygons for an Image or Plate, use the `masks` or `polygons` command:: - # Saved under 1.zarr/labels/0 - 1.zarr/ must already exist + # Saved under 1.ome.zarr/labels/0 + # 1.ome.zarr/ should already exist... $ omero zarr masks Image:1 - # Labels saved under each image. e.g 2.zarr/A/1/0/labels/0 - # Plate should already be exported + # ...or specify path with --source-image + $ omero zarr masks Image:1 --source-image my_image.ome.zarr + + # Labels saved under each image. e.g 2.ome.zarr/A/1/0/labels/0 + # 2.ome.zarr should already be exported or specify path with --source-image $ omero zarr masks Plate:2 - # Saved under zarr_files/1.zarr/labels/0 + # Saved under zarr_files/1.ome.zarr/labels/0 $ omero zarr --output /home/user/zarr_files masks Image:1 # Specify the label-name. (default is '0') - # e.g. Export to 1.zarr/labels/A + # e.g. Export to 1.ome.zarr/labels/A $ omero zarr masks Image:1 --label-name=A # Allow overlapping masks or polygons (overlap will be maximum value of the dtype) $ omero zarr polygons Image:1 --overlaps=dtype_max -The default behaviour is to export all masks or polygons on the Image to a single 5D +The default behaviour is to export all masks or polygons on the Image to a single nD "labeled" zarr array, with a different value for each Shape. An exception will be thrown if any of the masks overlap, unless the `--overlaps` option is used as above. diff --git a/src/omero_zarr/cli.py b/src/omero_zarr/cli.py index ae279b2..c855d8a 100644 --- a/src/omero_zarr/cli.py +++ b/src/omero_zarr/cli.py @@ -278,6 +278,15 @@ def _configure(self, parser: Parser) -> None: default=None, help="Maximum number of workers (only for use with bioformats2raw)", ) + export.add_argument( + "--name_by", + default="id", + choices=["id", "name"], + help=( + "How to name the Image or Plate zarr. Default 'id' is [ID].ome.zarr. " + "'name' is [NAME].ome.zarr" + ), + ) export.add_argument( "object", type=ProxyStringType("Image"), diff --git a/src/omero_zarr/masks.py b/src/omero_zarr/masks.py index c1c0a82..6084686 100644 --- a/src/omero_zarr/masks.py +++ b/src/omero_zarr/masks.py @@ -289,9 +289,9 @@ def save(self, masks: List[omero.model.Shape], name: str) -> None: ignored_dimensions.add(d) if self.plate: - filename = f"{self.plate.id}.zarr" + filename = f"{self.plate.id}.ome.zarr" else: - filename = f"{self.image.id}.zarr" + filename = f"{self.image.id}.ome.zarr" # Verify that we are linking this mask to a real ome-zarr source_image = self.source_image diff --git a/src/omero_zarr/raw_pixels.py b/src/omero_zarr/raw_pixels.py index c49d987..af7073b 100644 --- a/src/omero_zarr/raw_pixels.py +++ b/src/omero_zarr/raw_pixels.py @@ -41,12 +41,29 @@ from .util import marshal_axes, marshal_transformations, open_store, print_status -def image_to_zarr(image: omero.gateway.ImageWrapper, args: argparse.Namespace) -> None: +def sanitize_name(zarr_name: str) -> str: + # Avoids re.compile errors when writing Zarr data with the named root + # https://github.com/ome/omero-cli-zarr/pull/147#issuecomment-1669075660 + return zarr_name.replace("[", "(").replace("]", ")") + + +def get_zarr_name( + obj: omero.gateway.BlitzObjectWrapper, args: argparse.Namespace +) -> str: target_dir = args.output + name_by = args.name_by + if name_by == "name": + obj_name = sanitize_name(obj.name) + name = os.path.join(target_dir, "%s.ome.zarr" % obj_name) + else: + name = os.path.join(target_dir, "%s.ome.zarr" % obj.id) + return name + + +def image_to_zarr(image: omero.gateway.ImageWrapper, args: argparse.Namespace) -> None: tile_width = args.tile_width tile_height = args.tile_height - - name = os.path.join(target_dir, "%s.zarr" % image.id) + name = get_zarr_name(image, args) print(f"Exporting to {name} ({VERSION})") store = open_store(name) root = open_group(store) @@ -243,9 +260,8 @@ def plate_to_zarr(plate: omero.gateway._PlateWrapper, args: argparse.Namespace) n_cols = gs["columns"] n_fields = plate.getNumberOfFields() total = n_rows * n_cols * (n_fields[1] - n_fields[0] + 1) + name = get_zarr_name(plate, args) - target_dir = args.output - name = os.path.join(target_dir, "%s.zarr" % plate.id) store = open_store(name) print(f"Exporting to {name} ({VERSION})") root = open_group(store) @@ -296,6 +312,8 @@ def plate_to_zarr(plate: omero.gateway._PlateWrapper, args: argparse.Namespace) print_status(int(t0), int(time.time()), count, total) # Update plate_metadata after each Well + if len(well_paths) == 0: + continue write_plate_metadata( root, row_names, diff --git a/src/omero_zarr/util.py b/src/omero_zarr/util.py index 0a4e382..647e2bc 100644 --- a/src/omero_zarr/util.py +++ b/src/omero_zarr/util.py @@ -32,7 +32,7 @@ def print_status(t0: int, t: int, count: int, total: int) -> None: """ percent_done = float(count) * 100 / total dt = t - t0 - if dt > 0: + if dt > 0 and count > 0: rate = float(count) / (t - t0) eta_f = float(total - count) / rate eta = time.strftime("%H:%M:%S", time.gmtime(eta_f))