55from iohub import __version__ , open_ome_zarr
66from iohub .cli .parsing import input_position_dirpaths
77from iohub .convert import TIFFConverter
8+ from iohub .core .ozx import pack_ozx , summarize_ozx
89from iohub .reader import print_info
910from iohub .rename_wells import rename_wells
1011
1112VERSION = __version__
1213
1314_DATASET_PATH = click .Path (exists = True , file_okay = False , resolve_path = True , path_type = pathlib .Path )
15+ # Like _DATASET_PATH but also accepts files (e.g. ``.ozx`` archives).
16+ _OME_ZARR_PATH = click .Path (exists = True , resolve_path = True , path_type = pathlib .Path )
1417
1518
1619@click .group ()
@@ -26,7 +29,7 @@ def cli():
2629 "files" ,
2730 nargs = - 1 ,
2831 required = True ,
29- type = _DATASET_PATH ,
32+ type = _OME_ZARR_PATH ,
3033)
3134@click .option (
3235 "--verbose" ,
@@ -35,11 +38,11 @@ def cli():
3538 help = "Show usage guide to open dataset in Python and full tree for HCS Plates in OME-Zarr" ,
3639)
3740def info (files , verbose ):
38- """View basic metadata of a list of FILES.
41+ """View metadata for one or more FILES.
3942
40- Supported formats are Micro-Manager-acquired TIFF datasets
41- (single-page TIFF, multi-page OME-TIFF, NDTIFF)
42- and OME-Zarr (v0.1 linear HCS layout and all v0.4 layouts ).
43+ Supports Micro-Manager TIFF datasets (multi-page OME-TIFF, NDTIFF),
44+ OME-Zarr directory stores (v0.4 and v0.5), and RFC-9 zipped
45+ OME-Zarr archives (``.ozx`` ).
4346 """
4447 for file in files :
4548 click .echo (f"Reading file:\t { file } " )
@@ -195,3 +198,91 @@ def rename_wells_command(zarrfile, csvfile):
195198 ```
196199 """
197200 rename_wells (zarrfile , csvfile )
201+
202+
203+ def _echo_ozx_summary (verb : str , path : pathlib .Path ) -> None :
204+ """Print the one-liner ``ozx pack`` shows on success."""
205+ s = summarize_ozx (path )
206+ click .echo (f"{ verb } : { path } (ome version={ s .version } , jsonFirst={ s .json_first } )" )
207+
208+
209+ @cli .group (name = "ozx" )
210+ @click .help_option ("-h" , "--help" )
211+ def ozx_group ():
212+ """RFC-9 zipped OME-Zarr (``.ozx``) operations.
213+
214+ A ``.ozx`` is a single-file OME-Zarr archive (ZIP_STORED + ZIP64)
215+ designed for distribution - S3, AWS Open Data, HPC↔compute-cluster
216+ transfers - not as a live mutable store. One file to upload,
217+ download, and serve.
218+
219+ Use ``ozx pack`` to bundle a directory store for export and
220+ ``ozx info`` to inspect an archive's RFC-9 properties. ``iohub
221+ info`` works on ``.ozx`` paths directly for the usual FOV summary.
222+ """
223+
224+
225+ @ozx_group .command (name = "pack" )
226+ @click .help_option ("-h" , "--help" )
227+ @click .option (
228+ "-i" ,
229+ "--input" ,
230+ "src" ,
231+ required = True ,
232+ type = click .Path (exists = True , file_okay = False , dir_okay = True , resolve_path = True , path_type = pathlib .Path ),
233+ help = "Input OME-Zarr directory store." ,
234+ )
235+ @click .option (
236+ "-o" ,
237+ "--output" ,
238+ "dst" ,
239+ required = True ,
240+ type = click .Path (exists = False , resolve_path = True , path_type = pathlib .Path ),
241+ help = "Output .ozx path (must not exist)." ,
242+ )
243+ @click .option (
244+ "--version" ,
245+ "ome_version" ,
246+ default = None ,
247+ help = "OME-NGFF version to record. Sniffed from source if omitted." ,
248+ )
249+ def ozx_pack (src , dst , ome_version ):
250+ """Pack an OME-Zarr directory into an RFC-9 ``.ozx`` archive.
251+
252+ Writes a ZIP_STORED + ZIP64 archive in one pass with entries in BFS
253+ order - root ``zarr.json`` first, other ``zarr.json`` next, chunks
254+ last - and ``jsonFirst:true`` in the archive comment. HTTP-range
255+ readers can then fetch all metadata in one contiguous Range request
256+ on cold opens, the win that matters for S3 and AWS Open Data.
257+ """
258+ out = pack_ozx (src , dst , version = ome_version )
259+ _echo_ozx_summary ("packed" , out )
260+
261+
262+ @ozx_group .command (name = "info" )
263+ @click .help_option ("-h" , "--help" )
264+ @click .argument (
265+ "path" ,
266+ type = click .Path (exists = True , dir_okay = False , resolve_path = True , path_type = pathlib .Path ),
267+ )
268+ def ozx_info (path ):
269+ """Print RFC-9 archive metadata: version, ``jsonFirst``, entry count, size.
270+
271+ Use it to sanity-check an archive before publishing (``jsonFirst``
272+ should be ``True`` after ``iohub ozx pack``) or to inspect archives
273+ received from collaborators or downloaded from public datasets.
274+ """
275+ import zipfile
276+
277+ # One zip open for the namelist, one for the comment.
278+ with zipfile .ZipFile (path ) as zf :
279+ names = zf .namelist ()
280+ summary = summarize_ozx (path )
281+ n_entries = len (names )
282+ n_meta = sum (1 for n in names if n .rsplit ("/" , 1 )[- 1 ] == "zarr.json" )
283+ n_dupes = n_entries - len (set (names ))
284+ click .echo (f"path: { path } " )
285+ click .echo (f"size: { path .stat ().st_size :,} bytes" )
286+ click .echo (f"entries: { n_entries :,} ({ n_meta } zarr.json, { n_dupes } duplicate names)" )
287+ click .echo (f"ome version: { summary .version } " )
288+ click .echo (f"jsonFirst: { summary .json_first } " )
0 commit comments