Single Images Tutorial¶
This tutorial shows how to convert microscopy images into standalone OME-Zarr datasets
(without an HCS plate structure) using ome-zarr-converters-tools.
Use this approach when your data doesn't follow a plate layout -- for example, individual tissue scans, brain slices, or any dataset where a well-plate hierarchy doesn't apply.
The input to the library is a pandas DataFrame describing your tiles. You can build this DataFrame from any source (CSV, database, custom parsing, etc.).
Dataset: We reuse images from the cardiomyocyte differentiation dataset:
- 1 image ("cardiomyocyte_scan")
- 2 fields of view (FOVs)
- 2 Z-slices per FOV
- 1 channel (DAPI)
Step 1: Prepare the tile DataFrame¶
The DataFrame must contain one row per tile. For single images, the columns are:
Tile position and size columns¶
| Column | Description |
|---|---|
file_path |
Path to the raw image file (relative to the resource directory, or absolute) |
fov_name |
Field-of-view identifier (tiles with the same fov_name are stitched together) |
start_x, start_y |
XY position of this tile (in the coordinate system specified by AcquisitionDetails) |
start_z |
Z position (slice index or physical position) |
start_c |
Channel index (0-based) |
start_t |
Time-point index |
length_x, length_y |
Tile dimensions in pixels |
length_z |
Number of Z slices in this tile (usually 1) |
length_c |
Number of channels in this tile (usually 1) |
length_t |
Number of time points in this tile (usually 1) |
Single image column¶
| Column | Description |
|---|---|
image_path |
Name of the output OME-Zarr dataset (e.g., "brain_scan" becomes brain_scan.zarr) |
1.1 Load the metadata¶
Let's load the example CSV and inspect the DataFrame.
import warnings
import pandas as pd
# Suppress warnings for cleaner documentation output.
# Do not use this in production code.
warnings.filterwarnings("ignore")
tiles_table = pd.read_csv("../examples/single_acquisitions/tiles.csv")
print("Columns:", list(tiles_table.columns))
tiles_table
Columns: ['file_path', 'image_path', 'fov_name', 'start_x', 'start_y', 'start_z', 'start_c', 'start_t', 'length_x', 'length_y', 'length_z', 'length_c', 'length_t']
| file_path | image_path | fov_name | start_x | start_y | start_z | start_c | start_t | length_x | length_y | length_z | length_c | length_t | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 20200812-CardiomyocyteDifferentiation14-Cycle1... | cardiomyocyte_scan | FOV_1 | 10.0 | 10.0 | 0.0 | 0 | 0 | 2560 | 2160 | 1 | 1 | 1 |
| 1 | 20200812-CardiomyocyteDifferentiation14-Cycle1... | cardiomyocyte_scan | FOV_1 | 10.1 | 10.1 | 1.0 | 0 | 0 | 2560 | 2160 | 1 | 1 | 1 |
| 2 | 20200812-CardiomyocyteDifferentiation14-Cycle1... | cardiomyocyte_scan | FOV_2 | 1000.0 | 1000.0 | 0.0 | 0 | 0 | 2560 | 2160 | 1 | 1 | 1 |
| 3 | 20200812-CardiomyocyteDifferentiation14-Cycle1... | cardiomyocyte_scan | FOV_2 | 1000.0 | 1000.1 | 1.0 | 0 | 0 | 2560 | 2160 | 1 | 1 | 1 |
Note the image_path column -- this defines the output dataset name.
There are no row/column columns since we're not using a plate layout.
Define the acquisition details.
from ome_zarr_converters_tools import AcquisitionDetails, ChannelInfo
acq = AcquisitionDetails(
channels=[ChannelInfo(channel_label="DAPI", wavelength_id="405")],
pixelsize=0.65,
z_spacing=5.0,
t_spacing=1.0,
axes=["t", "c", "z", "y", "x"],
start_x_coo="world",
start_y_coo="world",
start_z_coo="pixel",
start_t_coo="pixel",
)
acq
AcquisitionDetails(start_x_coo='world', start_y_coo='world', start_z_coo='pixel', start_t_coo='pixel', length_x_coo='pixel', length_y_coo='pixel', length_z_coo='pixel', length_t_coo='pixel', pixelsize=0.65, z_spacing=5.0, t_spacing=1.0, channels=[ChannelInfo(channel_label='DAPI', wavelength_id='405', colors=<DefaultColors.blue: 'Blue (0000FF)'>)], axes=['t', 'c', 'z', 'y', 'x'], data_type=None, condition_table_path=None, stage_corrections=StageOrientation(flip_x=False, flip_y=False, swap_xy=False))
Step 2: Parse tiles from the DataFrame¶
Use single_images_from_dataframe() instead of hcs_images_from_dataframe().
This creates Tile objects with a SingleImage collection type.
from ome_zarr_converters_tools.core import single_images_from_dataframe
tiles = single_images_from_dataframe(
tiles_table=tiles_table,
acquisition_details=acq,
)
print(f"Number of tiles: {len(tiles)}")
print(f"Collection type: {type(tiles[0].collection).__name__}")
print(f"Image path: {tiles[0].collection.image_path}")
tiles[0]
Number of tiles: 4 Collection type: SingleImage Image path: cardiomyocyte_scan
Tile(fov_name='FOV_1', start_x=10.0, start_y=10.0, start_z=0.0, start_c=0, start_t=0.0, length_x=2560.0, length_y=2160.0, length_z=1.0, length_c=1, length_t=1.0, attributes={}, collection=SingleImage(image_path='cardiomyocyte_scan'), image_loader=DefaultImageLoader(file_path='20200812-CardiomyocyteDifferentiation14-Cycle1_B03_T0001F001L01A01Z01C01.png'), acquisition_details=AcquisitionDetails(start_x_coo='world', start_y_coo='world', start_z_coo='pixel', start_t_coo='pixel', length_x_coo='pixel', length_y_coo='pixel', length_z_coo='pixel', length_t_coo='pixel', pixelsize=0.65, z_spacing=5.0, t_spacing=1.0, channels=[ChannelInfo(channel_label='DAPI', wavelength_id='405', colors=<DefaultColors.blue: 'Blue (0000FF)'>)], axes=['t', 'c', 'z', 'y', 'x'], data_type=None, condition_table_path=None, stage_corrections=StageOrientation(flip_x=False, flip_y=False, swap_xy=False)))
Notice the collection type is SingleImage instead of ImageInPlate. The image_path value ("cardiomyocyte_scan") will become the output Zarr name: cardiomyocyte_scan.zarr.
Step 3: Aggregate tiles into TiledImages¶
Same pipeline as for HCS plates. The resource parameter points to the directory containing the image files.
from ome_zarr_converters_tools import ConverterOptions, tiles_aggregation_pipeline
data_dir = "../examples/hcs_plate/data"
opts = ConverterOptions()
tiled_images = tiles_aggregation_pipeline(
tiles=tiles,
converter_options=opts,
resource=data_dir,
)
print(f"Number of TiledImages: {len(tiled_images)}")
for ti in tiled_images:
print(
f" Path: {ti.path}, regions: {len(ti.regions)}, FOVs: {len(ti.group_by_fov())}"
)
--------------------------------------------------------------------------- NotImplementedError Traceback (most recent call last) Cell In[4], line 6 2 3 data_dir = "../examples/hcs_plate/data" 4 opts = ConverterOptions() 5 ----> 6 tiled_images = tiles_aggregation_pipeline( 7 tiles=tiles, 8 converter_options=opts, 9 resource=data_dir, File /opt/hostedtoolcache/Python/3.13.12/x64/lib/python3.13/site-packages/ome_zarr_converters_tools/pipelines/_tiles_aggregation_pipeline.py:45, in tiles_aggregation_pipeline(tiles, converter_options, filters, validators, resource) 43 if filters is not None: 44 tiles = apply_filter_pipeline(tiles, filters_config=filters) ---> 45 tiled_images = tiled_image_from_tiles( 46 tiles=tiles, 47 converter_options=converter_options, 48 resource=resource, 49 ) 50 if validators is not None: 51 tiled_images = apply_validator_pipeline( 52 tiled_images, validators_config=validators 53 ) File /opt/hostedtoolcache/Python/3.13.12/x64/lib/python3.13/site-packages/ome_zarr_converters_tools/core/_tile_to_tiled_images.py:32, in tiled_image_from_tiles(tiles, converter_options, resource) 30 if len(tiles) == 0: 31 raise ValueError("No tiles provided to build TiledImage.") ---> 32 data_type = tiles[0].find_data_type(resource=resource) 33 for tile in tiles: 34 if not split_tiles: File /opt/hostedtoolcache/Python/3.13.12/x64/lib/python3.13/site-packages/ome_zarr_converters_tools/core/_tile.py:151, in Tile.find_data_type(self, resource) 149 if self.acquisition_details.data_type is not None: 150 return self.acquisition_details.data_type --> 151 return self.image_loader.find_data_type(resource) File /opt/hostedtoolcache/Python/3.13.12/x64/lib/python3.13/site-packages/ome_zarr_converters_tools/models/_loader.py:27, in ImageLoaderInterface.find_data_type(self, resource) 25 def find_data_type(self, resource: Any = None) -> str: 26 """Find the data type of the image data.""" ---> 27 return str(self.load_data(resource).dtype) File /opt/hostedtoolcache/Python/3.13.12/x64/lib/python3.13/site-packages/ome_zarr_converters_tools/models/_loader.py:57, in DefaultImageLoader.load_data(self, resource) 55 return self.load_tiff(path) 56 elif suffix in ["png", "jpg", "jpeg", "bmp"]: ---> 57 return self.load_png(path) 58 elif suffix == "npy": 59 return self.load_npy(path) File /opt/hostedtoolcache/Python/3.13.12/x64/lib/python3.13/site-packages/ome_zarr_converters_tools/models/_loader.py:73, in DefaultImageLoader.load_png(self, path) 72 def load_png(self, path: str) -> np.ndarray: ---> 73 fs = filesystem_for_url(path, error_msg_prefix="Loading image") 74 with fs.open(path, "rb") as f: 75 return np.array(Image.open(f)) File /opt/hostedtoolcache/Python/3.13.12/x64/lib/python3.13/site-packages/ome_zarr_converters_tools/models/_url_utils.py:53, in filesystem_for_url(url, error_msg_prefix) 51 url_type = find_url_type(url) 52 if url_type == UrlType.NOT_SUPPORTED: ---> 53 raise NotImplementedError( 54 f"{error_msg_prefix} for URL {url} " 55 f"with detected type {url_type} is not implemented yet." 56 ) 57 return fsspec.filesystem(url_type.value) NotImplementedError: Loading image for URL ../examples/hcs_plate/data/20200812-CardiomyocyteDifferentiation14-Cycle1_B03_T0001F001L01A01Z01C01.png with detected type UrlType.NOT_SUPPORTED is not implemented yet.
Step 4: Register and write OME-Zarr¶
For single images, there is no plate structure to set up. We go directly to the registration pipeline and writing step.
import tempfile
from ome_zarr_converters_tools.models import (
AlignmentCorrections,
OverwriteMode,
TilingMode,
WriterMode,
)
from ome_zarr_converters_tools.pipelines import (
build_default_registration_pipeline,
tiled_image_creation_pipeline,
)
pipeline = build_default_registration_pipeline(
alignment_corrections=AlignmentCorrections(),
tiling_mode=TilingMode.AUTO,
)
zarr_dir = tempfile.mkdtemp(prefix="tutorial_images_")
for tiled_image in tiled_images:
zarr_url = f"{zarr_dir}/{tiled_image.path}"
omezarr = tiled_image_creation_pipeline(
zarr_url=zarr_url,
tiled_image=tiled_image,
registration_pipeline=pipeline,
converter_options=opts,
writer_mode=WriterMode.BY_FOV,
overwrite_mode=OverwriteMode.OVERWRITE,
resource=data_dir,
)
print(f"Written: {zarr_url}")
--------------------------------------------------------------------------- ImportError Traceback (most recent call last) Cell In[5], line 3 1 import tempfile 2 ----> 3 from ome_zarr_converters_tools.models import ( 4 AlignmentCorrections, 5 OverwriteMode, 6 TilingMode, ImportError: cannot import name 'AlignmentCorrections' from 'ome_zarr_converters_tools.models' (/opt/hostedtoolcache/Python/3.13.12/x64/lib/python3.13/site-packages/ome_zarr_converters_tools/models/__init__.py)
Step 5: Verify the result¶
img = omezarr.get_image()
data = img.get_array()
print(f"Shape (t, c, z, y, x): {data.shape}")
print(f"Channels: {img.channel_labels}")
print(f"Tables: {omezarr.list_tables()}")
--------------------------------------------------------------------------- NameError Traceback (most recent call last) Cell In[6], line 1 ----> 1 img = omezarr.get_image() 2 data = img.get_array() 3 4 print(f"Shape (t, c, z, y, x): {data.shape}") NameError: name 'omezarr' is not defined
Cleanup¶
import shutil
shutil.rmtree(zarr_dir, ignore_errors=True)
print("Cleaned up tutorial output.")
--------------------------------------------------------------------------- NameError Traceback (most recent call last) Cell In[7], line 3 1 import shutil 2 ----> 3 shutil.rmtree(zarr_dir, ignore_errors=True) 4 print("Cleaned up tutorial output.") NameError: name 'zarr_dir' is not defined