Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 96 additions & 5 deletions src/iohub/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
from iohub import __version__, open_ome_zarr
from iohub.cli.parsing import input_position_dirpaths
from iohub.convert import TIFFConverter
from iohub.core.ozx import pack_ozx, summarize_ozx
from iohub.reader import print_info
from iohub.rename_wells import rename_wells

VERSION = __version__

_DATASET_PATH = click.Path(exists=True, file_okay=False, resolve_path=True, path_type=pathlib.Path)
# Like _DATASET_PATH but also accepts files (e.g. ``.ozx`` archives).
_OME_ZARR_PATH = click.Path(exists=True, resolve_path=True, path_type=pathlib.Path)


@click.group()
Expand All @@ -26,7 +29,7 @@ def cli():
"files",
nargs=-1,
required=True,
type=_DATASET_PATH,
type=_OME_ZARR_PATH,
)
@click.option(
"--verbose",
Expand All @@ -35,11 +38,11 @@ def cli():
help="Show usage guide to open dataset in Python and full tree for HCS Plates in OME-Zarr",
)
def info(files, verbose):
"""View basic metadata of a list of FILES.
"""View metadata for one or more FILES.

Supported formats are Micro-Manager-acquired TIFF datasets
(single-page TIFF, multi-page OME-TIFF, NDTIFF)
and OME-Zarr (v0.1 linear HCS layout and all v0.4 layouts).
Supports Micro-Manager TIFF datasets (multi-page OME-TIFF, NDTIFF),
OME-Zarr directory stores (v0.4 and v0.5), and RFC-9 zipped
OME-Zarr archives (``.ozx``).
"""
for file in files:
click.echo(f"Reading file:\t {file}")
Expand Down Expand Up @@ -195,3 +198,91 @@ def rename_wells_command(zarrfile, csvfile):
```
"""
rename_wells(zarrfile, csvfile)


def _echo_ozx_summary(verb: str, path: pathlib.Path) -> None:
"""Print the one-liner ``ozx pack`` shows on success."""
s = summarize_ozx(path)
click.echo(f"{verb}: {path} (ome version={s.version}, jsonFirst={s.json_first})")


@cli.group(name="ozx")
@click.help_option("-h", "--help")
def ozx_group():
"""RFC-9 zipped OME-Zarr (``.ozx``) operations.

A ``.ozx`` is a single-file OME-Zarr archive (ZIP_STORED + ZIP64)
designed for distribution - S3, AWS Open Data, HPC↔compute-cluster
transfers - not as a live mutable store. One file to upload,
download, and serve.

Use ``ozx pack`` to bundle a directory store for export and
``ozx info`` to inspect an archive's RFC-9 properties. ``iohub
info`` works on ``.ozx`` paths directly for the usual FOV summary.
"""


@ozx_group.command(name="pack")
@click.help_option("-h", "--help")
@click.option(
"-i",
"--input",
"src",
required=True,
type=click.Path(exists=True, file_okay=False, dir_okay=True, resolve_path=True, path_type=pathlib.Path),
help="Input OME-Zarr directory store.",
)
@click.option(
"-o",
"--output",
"dst",
required=True,
type=click.Path(exists=False, resolve_path=True, path_type=pathlib.Path),
help="Output .ozx path (must not exist).",
)
@click.option(
"--version",
"ome_version",
default=None,
help="OME-NGFF version to record. Sniffed from source if omitted.",
)
def ozx_pack(src, dst, ome_version):
"""Pack an OME-Zarr directory into an RFC-9 ``.ozx`` archive.

Writes a ZIP_STORED + ZIP64 archive in one pass with entries in BFS
order - root ``zarr.json`` first, other ``zarr.json`` next, chunks
last - and ``jsonFirst:true`` in the archive comment. HTTP-range
readers can then fetch all metadata in one contiguous Range request
on cold opens, the win that matters for S3 and AWS Open Data.
"""
out = pack_ozx(src, dst, version=ome_version)
_echo_ozx_summary("packed", out)


@ozx_group.command(name="info")
@click.help_option("-h", "--help")
@click.argument(
"path",
type=click.Path(exists=True, dir_okay=False, resolve_path=True, path_type=pathlib.Path),
)
def ozx_info(path):
"""Print RFC-9 archive metadata: version, ``jsonFirst``, entry count, size.

Use it to sanity-check an archive before publishing (``jsonFirst``
should be ``True`` after ``iohub ozx pack``) or to inspect archives
received from collaborators or downloaded from public datasets.
"""
import zipfile

# One zip open for the namelist, one for the comment.
with zipfile.ZipFile(path) as zf:
names = zf.namelist()
summary = summarize_ozx(path)
n_entries = len(names)
n_meta = sum(1 for n in names if n.rsplit("/", 1)[-1] == "zarr.json")
n_dupes = n_entries - len(set(names))
click.echo(f"path: {path}")
click.echo(f"size: {path.stat().st_size:,} bytes")
click.echo(f"entries: {n_entries:,} ({n_meta} zarr.json, {n_dupes} duplicate names)")
click.echo(f"ome version: {summary.version}")
click.echo(f"jsonFirst: {summary.json_first}")
23 changes: 23 additions & 0 deletions src/iohub/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@
PathNormalizationError,
StoreOpenError,
)
from iohub.core.ozx import (
OZX_EXTENSION,
OzxStore,
OzxSummary,
is_ozx_path,
pack_ozx,
read_ozx_comment,
read_ozx_json_first,
read_ozx_version,
summarize_ozx,
write_ozx_comment,
)
from iohub.core.protocol import (
ArrayBackend,
ArrayIO,
Expand All @@ -37,6 +49,7 @@
from iohub.core.utils import normalize_path, pad_shape

__all__ = [
"OZX_EXTENSION",
# Types
"AccessMode",
"ArrayBackend",
Expand All @@ -55,6 +68,9 @@
# Arrays
"NGFFArray",
"NGFFVersion",
# OZX (RFC-9 zipped OME-Zarr)
"OzxStore",
"OzxSummary",
"PathNormalizationError",
"StoreOpenError",
"StorePath",
Expand All @@ -65,12 +81,19 @@
# Registry
"available_implementations",
"get_implementation",
"is_ozx_path",
"ngff_version_for_format",
# Utils
"normalize_path",
"pack_ozx",
"pad_shape",
"read_ozx_comment",
"read_ozx_json_first",
"read_ozx_version",
"register_implementation",
"set_default_implementation",
"summarize_ozx",
"write_ozx_comment",
# Compat
"zarr_format_for_version",
]
Loading
Loading