diff --git a/api/core/index.html b/api/core/index.html index ad7e8ea..8fb5f8d 100644 --- a/api/core/index.html +++ b/api/core/index.html @@ -1507,7 +1507,7 @@
Images image.axes_names=['c', 'z', 'y', 'x'] image.pixel_size=PixelSize(x=0.1625, y=0.1625, z=1.0, unit=micrometer) image.channel_labels=['DAPI', 'nanog', 'Lamin B1'] -image.dimensions=<ngio.core.dimensions.Dimensions object at 0x7f06fcdbdd60> +image.dimensions=<ngio.core.dimensions.Dimensions object at 0x7f65aed0d400> @@ -2087,7 +2087,7 @@
NGIO is a Python library to streamline OME-Zarr image analysis workflows.
Main Goals:
To get started, check out the Getting Started guide.
"},{"location":"#ngio-is-under-active-development","title":"\ud83d\udea7 Ngio is Under active Development \ud83d\udea7","text":""},{"location":"#roadmap","title":"Roadmap","text":"Feature Status ETA Description Metadata Handling \u2705 Read, Write, Validate OME-Zarr Metadata (0.4 supported, 0.5 ready) OME-Zarr Validation \u2705 Validate OME-Zarr files for compliance with the OME-Zarr Specification + Compliance between Metadata and Data Base Image Handling \u2705 Load data from OME-Zarr files, retrieve basic metadata, and write data ROI Handling \u2705 Common ROI models Label Handling \u2705 Mid-September Based on Image Handling Table Validation \u2705 Mid-September Validate Table fractal V1 + Compliance between Metadata and Data Table Handling \u2705 Mid-September Read, Write ROI, Features, and Masked Tables Basic Iterators Ongoing End-September Read and Write Iterators for common access patterns Base Documentation \u2705 End-September API Documentation and Examples Beta Ready Testing \u2705 End-September Beta Testing; Library is ready for testing, but the API is not stable Mask Iterators Ongoing October Iterators over Masked Tables Advanced Iterators Not started October Iterators for advanced access patterns Parallel Iterators Not started End of the Year Concurrent Iterators for parallel read and write Full Documentation Not started End of the Year Complete Documentation Release 1.0 (Commitment to API) Not started End of the Year API is stable; breaking changes will be avoided"},{"location":"#contributors","title":"Contributors","text":"ngio
is developed at the BioVisionCenter at the University of Zurich. The main contributors are: @lorenzocerrone, @jluethi.
ngio
is released according to the BSD-3-Clause License. See LICENSE
Warning
The library is still in development and is not yet stable. The API is subject to change, bugs and breaking changes are expected.
Warning
The documentation is still under development. It is not yet complete and may contain errors and inaccuracies.
"},{"location":"getting-started/#installation","title":"Installation","text":"Currently, ngio
is not available on PyPI. You can install it from the source code.
pip install \"ngio[v2]\"\n
The v2
extra installs the latest version of zarr-python
from the v2
branch. ngio
is currently not completely compatible with the v3
release of zarr-python
.
ngio
API Overview","text":"ngio
implements an abstract object base API for handling OME-Zarr files. The three main objects are NgffImage
, Image
(Label
), and ROITables
.
NgffImage
is the main entry point to the library. It is used to open an OME-Zarr Image and manage its metadata. This object can not be used to access the data directly. but it can be used to access and create the Image
, Label
, and Tables
objects. Moreover it can be used to derive a new Ngff
images based on the current one.Image
and Label
are used to access \"ImageLike\" objects. They are the main objects to access the data in the OME-Zarr file, manage the metadata, and write data.ROITables
can be used to access specific region of interest in the image. They are tightly integrated with the Image
and Label
objects.Currently, the library is not yet stable. However, you can see some example usage in our demo notebooks.
ngio.core
","text":"Core classes for the ngio library.
"},{"location":"api/core/#ngffimage","title":"NGFFImage","text":""},{"location":"api/core/#ngio.core.NgffImage","title":"ngio.core.NgffImage
","text":"A class to handle OME-NGFF images.
Source code inngio/core/ngff_image.py
class NgffImage:\n \"\"\"A class to handle OME-NGFF images.\"\"\"\n\n def __init__(self, store: StoreLike) -> None:\n \"\"\"Initialize the NGFFImage in read mode.\"\"\"\n self.store = store\n self.group = open_group_wrapper(store=store, mode=\"r+\")\n self._image_meta = get_ngff_image_meta_handler(\n self.group, meta_mode=\"image\", cache=False\n )\n\n self.table = TableGroup(self.group)\n self.label = LabelGroup(self.group, image_ref=self.get_image())\n\n @property\n def image_meta(self) -> ImageMeta:\n \"\"\"Get the image metadata.\"\"\"\n return self._image_meta.load_meta()\n\n @property\n def num_levels(self) -> int:\n \"\"\"Get the number of levels in the image.\"\"\"\n return self.image_meta.num_levels\n\n @property\n def levels_paths(self) -> list[str]:\n \"\"\"Get the paths of the levels in the image.\"\"\"\n return self.image_meta.levels_paths\n\n def get_image(\n self,\n *,\n path: str | None = None,\n pixel_size: PixelSize | None = None,\n highest_resolution: bool = True,\n ) -> Image:\n \"\"\"Get an image handler for the given level.\n\n Args:\n path (str | None, optional): The path to the level.\n pixel_size (tuple[float, ...] | list[float] | None, optional): The pixel\n size of the level.\n highest_resolution (bool, optional): Whether to get the highest\n resolution level\n\n Returns:\n ImageHandler: The image handler.\n \"\"\"\n if path is not None or pixel_size is not None:\n highest_resolution = False\n\n return Image(\n store=self.group,\n path=path,\n pixel_size=pixel_size,\n highest_resolution=highest_resolution,\n )\n\n def _update_omero_window(self) -> None:\n \"\"\"Update the OMERO window.\"\"\"\n meta = self.image_meta\n image = self.get_image(highest_resolution=True)\n max_dtype = np.iinfo(image.array.dtype).max\n start, end = image.dask_array.min().compute(), image.dask_array.max().compute()\n\n channel_list = meta.omero.channels\n\n new_channel_list = []\n for channel in channel_list:\n channel.extra_fields[\"window\"] = {\n \"start\": start,\n \"end\": end,\n \"min\": 0,\n \"max\": max_dtype,\n }\n new_channel_list.append(channel)\n\n meta.omero.channels = new_channel_list\n self._image_meta.write_meta(meta)\n\n def derive_new_image(\n self,\n store: StoreLike,\n name: str,\n overwrite: bool = True,\n **kwargs,\n ) -> \"NgffImage\":\n \"\"\"Derive a new image from the current image.\n\n Args:\n store (StoreLike): The store to create the new image in.\n name (str): The name of the new image.\n overwrite (bool): Whether to overwrite the image if it exists\n **kwargs: Additional keyword arguments.\n Follow the same signature as `create_empty_ome_zarr_image`.\n\n Returns:\n NgffImage: The new image.\n \"\"\"\n image_0 = self.get_image(highest_resolution=True)\n\n default_kwargs = {\n \"store\": store,\n \"shape\": image_0.on_disk_shape,\n \"chunks\": image_0.array.chunks,\n \"dtype\": image_0.array.dtype,\n \"on_disk_axis\": image_0.dataset.on_disk_axes_names,\n \"pixel_sizes\": image_0.pixel_size,\n \"xy_scaling_factor\": self.image_meta.xy_scaling_factor,\n \"z_scaling_factor\": self.image_meta.z_scaling_factor,\n \"time_spacing\": image_0.dataset.time_spacing,\n \"time_units\": image_0.dataset.time_axis_unit,\n \"num_levels\": self.num_levels,\n \"name\": name,\n \"channel_labels\": image_0.channel_labels,\n \"channel_wavelengths\": None,\n \"channel_kwargs\": None,\n \"omero_kwargs\": None,\n \"overwrite\": overwrite,\n \"version\": self.image_meta.version,\n }\n\n default_kwargs.update(kwargs)\n\n create_empty_ome_zarr_image(\n **default_kwargs,\n )\n return NgffImage(store=store)\n
"},{"location":"api/core/#ngio.core.NgffImage.image_meta","title":"image_meta: ImageMeta
property
","text":"Get the image metadata.
"},{"location":"api/core/#ngio.core.NgffImage.levels_paths","title":"levels_paths: list[str]
property
","text":"Get the paths of the levels in the image.
"},{"location":"api/core/#ngio.core.NgffImage.num_levels","title":"num_levels: int
property
","text":"Get the number of levels in the image.
"},{"location":"api/core/#ngio.core.NgffImage.__init__","title":"__init__(store: StoreLike) -> None
","text":"Initialize the NGFFImage in read mode.
Source code inngio/core/ngff_image.py
def __init__(self, store: StoreLike) -> None:\n \"\"\"Initialize the NGFFImage in read mode.\"\"\"\n self.store = store\n self.group = open_group_wrapper(store=store, mode=\"r+\")\n self._image_meta = get_ngff_image_meta_handler(\n self.group, meta_mode=\"image\", cache=False\n )\n\n self.table = TableGroup(self.group)\n self.label = LabelGroup(self.group, image_ref=self.get_image())\n
"},{"location":"api/core/#ngio.core.NgffImage.derive_new_image","title":"derive_new_image(store: StoreLike, name: str, overwrite: bool = True, **kwargs) -> NgffImage
","text":"Derive a new image from the current image.
Parameters:
store
(StoreLike
) \u2013 The store to create the new image in.
name
(str
) \u2013 The name of the new image.
overwrite
(bool
, default: True
) \u2013 Whether to overwrite the image if it exists
**kwargs
\u2013 Additional keyword arguments. Follow the same signature as create_empty_ome_zarr_image
.
Returns:
NgffImage
( NgffImage
) \u2013 The new image.
ngio/core/ngff_image.py
def derive_new_image(\n self,\n store: StoreLike,\n name: str,\n overwrite: bool = True,\n **kwargs,\n) -> \"NgffImage\":\n \"\"\"Derive a new image from the current image.\n\n Args:\n store (StoreLike): The store to create the new image in.\n name (str): The name of the new image.\n overwrite (bool): Whether to overwrite the image if it exists\n **kwargs: Additional keyword arguments.\n Follow the same signature as `create_empty_ome_zarr_image`.\n\n Returns:\n NgffImage: The new image.\n \"\"\"\n image_0 = self.get_image(highest_resolution=True)\n\n default_kwargs = {\n \"store\": store,\n \"shape\": image_0.on_disk_shape,\n \"chunks\": image_0.array.chunks,\n \"dtype\": image_0.array.dtype,\n \"on_disk_axis\": image_0.dataset.on_disk_axes_names,\n \"pixel_sizes\": image_0.pixel_size,\n \"xy_scaling_factor\": self.image_meta.xy_scaling_factor,\n \"z_scaling_factor\": self.image_meta.z_scaling_factor,\n \"time_spacing\": image_0.dataset.time_spacing,\n \"time_units\": image_0.dataset.time_axis_unit,\n \"num_levels\": self.num_levels,\n \"name\": name,\n \"channel_labels\": image_0.channel_labels,\n \"channel_wavelengths\": None,\n \"channel_kwargs\": None,\n \"omero_kwargs\": None,\n \"overwrite\": overwrite,\n \"version\": self.image_meta.version,\n }\n\n default_kwargs.update(kwargs)\n\n create_empty_ome_zarr_image(\n **default_kwargs,\n )\n return NgffImage(store=store)\n
"},{"location":"api/core/#ngio.core.NgffImage.get_image","title":"get_image(*, path: str | None = None, pixel_size: PixelSize | None = None, highest_resolution: bool = True) -> Image
","text":"Get an image handler for the given level.
Parameters:
path
(str | None
, default: None
) \u2013 The path to the level.
pixel_size
(tuple[float, ...] | list[float] | None
, default: None
) \u2013 The pixel size of the level.
highest_resolution
(bool
, default: True
) \u2013 Whether to get the highest resolution level
Returns:
ImageHandler
( Image
) \u2013 The image handler.
ngio/core/ngff_image.py
def get_image(\n self,\n *,\n path: str | None = None,\n pixel_size: PixelSize | None = None,\n highest_resolution: bool = True,\n) -> Image:\n \"\"\"Get an image handler for the given level.\n\n Args:\n path (str | None, optional): The path to the level.\n pixel_size (tuple[float, ...] | list[float] | None, optional): The pixel\n size of the level.\n highest_resolution (bool, optional): Whether to get the highest\n resolution level\n\n Returns:\n ImageHandler: The image handler.\n \"\"\"\n if path is not None or pixel_size is not None:\n highest_resolution = False\n\n return Image(\n store=self.group,\n path=path,\n pixel_size=pixel_size,\n highest_resolution=highest_resolution,\n )\n
"},{"location":"notebooks/basic_usage/","title":"Basic Usage","text":"In\u00a0[1]: Copied! from ngio.core import NgffImage\nngff_image = NgffImage(\"../../data/20200812-CardiomyocyteDifferentiation14-Cycle1_mip.zarr/B/03/0\")\nfrom ngio.core import NgffImage ngff_image = NgffImage(\"../../data/20200812-CardiomyocyteDifferentiation14-Cycle1_mip.zarr/B/03/0\")
The ngff_image
contains several attributes and methods to interact with the OME-NGFF
(OME-Zarr
) file on the storage.
# Explore object metadata\nprint(\"Levels: \", ngff_image.levels_paths)\nprint(\"Num Levels: \", ngff_image.num_levels)\n# Explore object metadata print(\"Levels: \", ngff_image.levels_paths) print(\"Num Levels: \", ngff_image.num_levels)
Levels: ['0', '1', '2', '3', '4']\nNum Levels: 5\n
Get a single level
of the image pyramid as Image
(to know more about the Image
class, please refer to the Image notebook The Image
object is the main object to interact with the image. It contains methods to interact with the image data and metadata.
from ngio.ngff_meta import PixelSize\n\n# 1. Get image from highest resolution (default)\nimage = ngff_image.get_image()\nprint(f\"{image.shape=}\")\nprint(f\"{image.pixel_size=}\")\n\n# 2. Get image from a specific level using the path keyword\nprint(f\"{image.shape=}\")\nprint(f\"{image.pixel_size=}\")\n\n# 3. Get image from a specific pixel size using the pixel_size keyword\nimage = ngff_image.get_image(pixel_size=PixelSize(x=0.65, y=0.65, z=1))\nprint(f\"{image.shape=}\")\nprint(f\"{image.pixel_size=}\")\nfrom ngio.ngff_meta import PixelSize # 1. Get image from highest resolution (default) image = ngff_image.get_image() print(f\"{image.shape=}\") print(f\"{image.pixel_size=}\") # 2. Get image from a specific level using the path keyword print(f\"{image.shape=}\") print(f\"{image.pixel_size=}\") # 3. Get image from a specific pixel size using the pixel_size keyword image = ngff_image.get_image(pixel_size=PixelSize(x=0.65, y=0.65, z=1)) print(f\"{image.shape=}\") print(f\"{image.pixel_size=}\")
image.shape=(3, 1, 4320, 5120)\nimage.pixel_size=PixelSize(x=0.1625, y=0.1625, z=1.0, unit=micrometer)\nimage.shape=(3, 1, 4320, 5120)\nimage.pixel_size=PixelSize(x=0.1625, y=0.1625, z=1.0, unit=micrometer)\nimage.shape=(3, 1, 1080, 1280)\nimage.pixel_size=PixelSize(x=0.65, y=0.65, z=1.0, unit=micrometer)\nIn\u00a0[4]: Copied!
print(\"List of Labels: \", ngff_image.label.list())\n\nlabel_nuclei = ngff_image.label.get(\"nuclei\", path=\"0\")\nprint(f\"{label_nuclei.shape=}\")\nprint(f\"{label_nuclei.pixel_size=}\")\nprint(\"List of Labels: \", ngff_image.label.list()) label_nuclei = ngff_image.label.get(\"nuclei\", path=\"0\") print(f\"{label_nuclei.shape=}\") print(f\"{label_nuclei.pixel_size=}\")
List of Labels: ['nuclei', 'wf_2_labels', 'wf_3_labels', 'wf_4_labels']\nlabel_nuclei.shape=(1, 4320, 5120)\nlabel_nuclei.pixel_size=PixelSize(x=0.1625, y=0.1625, z=1.0, unit=micrometer)\nIn\u00a0[5]: Copied!
print(\"List of Tables: \", ngff_image.table.list())\nprint(\" - Feature tables: \", ngff_image.table.list(type='feature_table'))\nprint(\" - Roi tables: \", ngff_image.table.list(type='roi_table'))\nprint(\" - Masking Roi tables: \", ngff_image.table.list(type='masking_roi_table'))\nprint(\"List of Tables: \", ngff_image.table.list()) print(\" - Feature tables: \", ngff_image.table.list(type='feature_table')) print(\" - Roi tables: \", ngff_image.table.list(type='roi_table')) print(\" - Masking Roi tables: \", ngff_image.table.list(type='masking_roi_table'))
List of Tables: ['FOV_ROI_table', 'nuclei_ROI_table', 'well_ROI_table', 'regionprops_DAPI', 'nuclei_measurements_wf3', 'nuclei_measurements_wf4', 'nuclei_lamin_measurements_wf4']\n - Feature tables: ['regionprops_DAPI', 'nuclei_measurements_wf3', 'nuclei_measurements_wf4', 'nuclei_lamin_measurements_wf4']\n - Roi tables: ['FOV_ROI_table', 'well_ROI_table']\n - Masking Roi tables: ['nuclei_ROI_table']\nIn\u00a0[6]: Copied!
# Loading a table\nfeature_table = ngff_image.table.get(\"regionprops_DAPI\")\nfeature_table.table\n# Loading a table feature_table = ngff_image.table.get(\"regionprops_DAPI\") feature_table.table
/opt/hostedtoolcache/Python/3.12.6/x64/lib/python3.12/site-packages/anndata/_core/aligned_df.py:68: ImplicitModificationWarning: Transforming to str index.\n warnings.warn(\"Transforming to str index.\", ImplicitModificationWarning)\nOut[6]: area bbox_area equivalent_diameter max_intensity mean_intensity min_intensity standard_deviation_intensity label 1 2120.0 2655.0 15.938437 476.0 278.635864 86.0 54.343792 2 327.0 456.0 8.547709 604.0 324.162079 118.0 90.847092 3 1381.0 1749.0 13.816510 386.0 212.682114 60.0 50.169601 4 2566.0 3588.0 16.985800 497.0 251.731491 61.0 53.307186 5 4201.0 5472.0 20.019413 466.0 223.862885 51.0 56.719025 ... ... ... ... ... ... ... ... 3002 1026.0 1288.0 12.513618 589.0 308.404480 132.0 64.681778 3003 859.0 1080.0 11.794101 400.0 270.349243 107.0 49.040470 3004 508.0 660.0 9.899693 314.0 205.043304 82.0 33.249981 3005 369.0 440.0 8.899028 376.0 217.970184 82.0 50.978519 3006 278.0 330.0 8.097459 339.0 217.996399 100.0 38.510067
3006 rows \u00d7 7 columns
In\u00a0[7]: Copied!# Loading a roi table\nroi_table = ngff_image.table.get(\"FOV_ROI_table\")\n\nprint(f\"{roi_table.list_field_indexes=}\")\nprint(f\"{roi_table.get_roi(\"FOV_1\")=}\")\n\nroi_table.table\n# Loading a roi table roi_table = ngff_image.table.get(\"FOV_ROI_table\") print(f\"{roi_table.list_field_indexes=}\") print(f\"{roi_table.get_roi(\"FOV_1\")=}\") roi_table.table
roi_table.list_field_indexes=['FOV_1', 'FOV_2', 'FOV_3', 'FOV_4']\nroi_table.get_roi(\"FOV_1\")=WorldCooROI(x_length=416.0, y_length=351.0, z_length=1.0, x=0.0, y=0.0, z=0.0)\nOut[7]: x_micrometer y_micrometer z_micrometer len_x_micrometer len_y_micrometer len_z_micrometer x_micrometer_original y_micrometer_original FieldIndex FOV_1 0.0 0.0 0.0 416.0 351.0 1.0 -1448.300049 -1517.699951 FOV_2 416.0 0.0 0.0 416.0 351.0 1.0 -1032.300049 -1517.699951 FOV_3 0.0 351.0 0.0 416.0 351.0 1.0 -1448.300049 -1166.699951 FOV_4 416.0 351.0 0.0 416.0 351.0 1.0 -1032.300049 -1166.699951 In\u00a0[8]: Copied!
new_ngff_image = ngff_image.derive_new_image(\"../../data/new_ome.zarr\", name=\"new_image\")\n\nprint(f\"{new_ngff_image.store=}\")\nprint(f\"{new_ngff_image.levels_paths=}\")\nprint(f\"{new_ngff_image.num_levels=}\")\nnew_ngff_image = ngff_image.derive_new_image(\"../../data/new_ome.zarr\", name=\"new_image\") print(f\"{new_ngff_image.store=}\") print(f\"{new_ngff_image.levels_paths=}\") print(f\"{new_ngff_image.num_levels=}\")
new_ngff_image.store='../../data/new_ome.zarr'\nnew_ngff_image.levels_paths=['0', '1', '2', '3', '4']\nnew_ngff_image.num_levels=5\nIn\u00a0[\u00a0]: Copied!
\n"},{"location":"notebooks/basic_usage/#basic-usage","title":"Basic Usage\u00b6","text":"
In this notebook we will show how to use the 'ngffImage' class to manage a OME-NGFF image.
For this example we will use a small example image that can be downloaded from the following link: example ome-zarr
"},{"location":"notebooks/basic_usage/#ngffimage","title":"NgffImage\u00b6","text":"The NgffImage
provides a high-level interface to read, write and manipulate NGFF images. A NgffImage
can be created from a storelike object (e.g. a path to a directory or a zarr store) or from a zarr.Group
object.
The NgffImage
can also be used to load labels from a OME-NGFF
file.
The NgffImage
can also be used to load tables from a OME-NGFF
file.
ngio
supports three types of tables:
features table
A simple table to store features associated with a label.roi table
A table to store regions of interest.masking roi tables
A table to store single objects bounding boxes associated with a label.When processing an image, it is often useful to derive a new image from the original image. The NgffImage
class provides a method to derive a new image from the original image. When deriving a new image, a new NgffImage
object is created with the same metadata as the original image. Optionally the user can specify different metadata for the new image(.e.g. different channels names).
import matplotlib.pyplot as plt\n\nfrom ngio.core.ngff_image import NgffImage\n\nngff_image = NgffImage(\"../../data/20200812-CardiomyocyteDifferentiation14-Cycle1_mip.zarr/B/03/0\")\nimport matplotlib.pyplot as plt from ngio.core.ngff_image import NgffImage ngff_image = NgffImage(\"../../data/20200812-CardiomyocyteDifferentiation14-Cycle1_mip.zarr/B/03/0\") In\u00a0[2]: Copied!
image = ngff_image.get_image()\n\nprint(\"Image information:\")\nprint(f\"{image.shape=}\")\nprint(f\"{image.axes_names=}\")\nprint(f\"{image.pixel_size=}\")\nprint(f\"{image.channel_labels=}\")\nprint(f\"{image.dimensions=}\")\nimage = ngff_image.get_image() print(\"Image information:\") print(f\"{image.shape=}\") print(f\"{image.axes_names=}\") print(f\"{image.pixel_size=}\") print(f\"{image.channel_labels=}\") print(f\"{image.dimensions=}\")
Image information:\nimage.shape=(3, 1, 4320, 5120)\nimage.axes_names=['c', 'z', 'y', 'x']\nimage.pixel_size=PixelSize(x=0.1625, y=0.1625, z=1.0, unit=micrometer)\nimage.channel_labels=['DAPI', 'nanog', 'Lamin B1']\nimage.dimensions=<ngio.core.dimensions.Dimensions object at 0x7f06fcdbdd60>\n
The Image
object created is a lazy object, meaning that the image is not loaded into memory until it is needed. To get the image data from disk we can use the .array
attribute or we can get it as a dask.array
object using the .dask_array
attribute.
# Get image as a zarr.Array\narray = image.array\nprint(f\"{array}\")\n\ndask_array = image.dask_array\ndask_array\n# Get image as a zarr.Array array = image.array print(f\"{array}\") dask_array = image.dask_array dask_array
<zarr.core.Array '/0' (3, 1, 4320, 5120) uint16>\nOut[3]: Array Chunk Bytes 126.56 MiB 10.55 MiB Shape (3, 1, 4320, 5120) (1, 1, 2160, 2560) Dask graph 12 chunks in 2 graph layers Data type uint16 numpy.ndarray 3 1 5120 4320 1
Note, directly accessing the .array
or .dask_array
attributes will load the image as stored in the file.
Since in principle the images can have different axes order. A safer way to access the image data is to use the .get_data()
method, which will return the image data in canonical order (TCZYX).
image_numpy = image.get_data(c=0, x=slice(0, 250), y=slice(0, 250), preserve_dimensions=False, mode=\"numpy\")\n\nprint(f\"{image_numpy.shape=}\")\nimage_numpy = image.get_data(c=0, x=slice(0, 250), y=slice(0, 250), preserve_dimensions=False, mode=\"numpy\") print(f\"{image_numpy.shape=}\")
image_numpy.shape=(1, 250, 250)\nIn\u00a0[5]: Copied!
roi_table = ngff_image.table.get(\"FOV_ROI_table\")\nroi = roi_table.get_roi(\"FOV_1\")\nprint(f\"{roi=}\")\n\nimage_roi_1 = image.get_data_from_roi(roi=roi, c=0, preserve_dimensions=True, mode=\"dask\")\nimage_roi_1\nroi_table = ngff_image.table.get(\"FOV_ROI_table\") roi = roi_table.get_roi(\"FOV_1\") print(f\"{roi=}\") image_roi_1 = image.get_data_from_roi(roi=roi, c=0, preserve_dimensions=True, mode=\"dask\") image_roi_1
roi=WorldCooROI(x_length=416.0, y_length=351.0, z_length=1.0, x=0.0, y=0.0, z=0.0)\nOut[5]: Array Chunk Bytes 10.55 MiB 10.55 MiB Shape (1, 1, 2160, 2560) (1, 1, 2160, 2560) Dask graph 1 chunks in 3 graph layers Data type uint16 numpy.ndarray 1 1 2560 2160 1
The roi object can is defined in physical coordinates, and can be used to extract the region of interest from the image or label at any resolution.
In\u00a0[6]: Copied!image_2 = ngff_image.get_image(path=\"2\")\n# Two images at different resolutions\nprint(f\"{image.pixel_size=}\")\nprint(f\"{image_2.pixel_size=}\")\n\n# Get roi for higher resolution image\nimage_1_roi_1 = image.get_data_from_roi(roi=roi, c=0, preserve_dimensions=False)\n\n# Get roi for lower resolution image\nimage_2_roi_1 = image_2.get_data_from_roi(roi=roi, c=0, preserve_dimensions=False)\n\n# Plot the two images side by side\nfig, axs = plt.subplots(1, 2, figsize=(10, 5))\naxs[0].imshow(image_1_roi_1[0], cmap=\"gray\")\naxs[1].imshow(image_2_roi_1[0], cmap=\"gray\")\nplt.show()\nimage_2 = ngff_image.get_image(path=\"2\") # Two images at different resolutions print(f\"{image.pixel_size=}\") print(f\"{image_2.pixel_size=}\") # Get roi for higher resolution image image_1_roi_1 = image.get_data_from_roi(roi=roi, c=0, preserve_dimensions=False) # Get roi for lower resolution image image_2_roi_1 = image_2.get_data_from_roi(roi=roi, c=0, preserve_dimensions=False) # Plot the two images side by side fig, axs = plt.subplots(1, 2, figsize=(10, 5)) axs[0].imshow(image_1_roi_1[0], cmap=\"gray\") axs[1].imshow(image_2_roi_1[0], cmap=\"gray\") plt.show()
image.pixel_size=PixelSize(x=0.1625, y=0.1625, z=1.0, unit=micrometer)\nimage_2.pixel_size=PixelSize(x=0.65, y=0.65, z=1.0, unit=micrometer)\nIn\u00a0[7]: Copied!
import numpy as np\n\n# Get a small slice of the image\nsmall_slice = image.get_data(x=slice(1000, 2000), y=slice(1000, 2000))\n\n# Set the sample slice to zeros\nzeros_slice = np.zeros_like(small_slice)\nimage.set_data(patch=zeros_slice, x=slice(1000, 2000), y=slice(1000, 2000))\n\n\n# Load the image from disk and show the edited image\nnuclei = ngff_image.label.get(\"nuclei\")\nfig, axs = plt.subplots(1, 2, figsize=(10, 5))\naxs[0].imshow(image.array[0, 0], cmap=\"gray\")\naxs[1].imshow(nuclei.array[0])\nfor ax in axs:\n ax.axis(\"off\")\nplt.tight_layout()\nplt.show()\n\n# Add back the original slice to the image\nimage.set_data(patch=small_slice, x=slice(1000, 2000), y=slice(1000, 2000))\nimport numpy as np # Get a small slice of the image small_slice = image.get_data(x=slice(1000, 2000), y=slice(1000, 2000)) # Set the sample slice to zeros zeros_slice = np.zeros_like(small_slice) image.set_data(patch=zeros_slice, x=slice(1000, 2000), y=slice(1000, 2000)) # Load the image from disk and show the edited image nuclei = ngff_image.label.get(\"nuclei\") fig, axs = plt.subplots(1, 2, figsize=(10, 5)) axs[0].imshow(image.array[0, 0], cmap=\"gray\") axs[1].imshow(nuclei.array[0]) for ax in axs: ax.axis(\"off\") plt.tight_layout() plt.show() # Add back the original slice to the image image.set_data(patch=small_slice, x=slice(1000, 2000), y=slice(1000, 2000)) In\u00a0[8]: Copied!
# Create a a new label object and set it to a simple segmentation\nnew_label = ngff_image.label.derive(\"new_label\", overwrite=True)\n\nsimple_segmentation = image.array[0] > 100\nnew_label.array[...] = simple_segmentation\n\n# make a subplot with two image show side by side\nfig, axs = plt.subplots(1, 2, figsize=(10, 5))\naxs[0].imshow(image.array[0, 0], cmap=\"gray\")\naxs[1].imshow(new_label.array[0], cmap=\"gray\")\nfor ax in axs:\n ax.axis(\"off\")\nplt.tight_layout()\nplt.show()\n# Create a a new label object and set it to a simple segmentation new_label = ngff_image.label.derive(\"new_label\", overwrite=True) simple_segmentation = image.array[0] > 100 new_label.array[...] = simple_segmentation # make a subplot with two image show side by side fig, axs = plt.subplots(1, 2, figsize=(10, 5)) axs[0].imshow(image.array[0, 0], cmap=\"gray\") axs[1].imshow(new_label.array[0], cmap=\"gray\") for ax in axs: ax.axis(\"off\") plt.tight_layout() plt.show() In\u00a0[9]: Copied!
label_0 = ngff_image.label.get(\"new_label\", path=\"0\")\nlabel_2 = ngff_image.label.get(\"new_label\", path=\"2\")\n\nlabel_before_consolidation = label_2.array[...]\n\n# Consolidate the label\nlabel_0.consolidate()\n\nlabel_after_consolidation = label_2.array[...]\n\n\n# make a subplot with two image show side by side\nfig, axs = plt.subplots(1, 2, figsize=(10, 5))\naxs[0].imshow(label_before_consolidation[0], cmap=\"gray\")\naxs[1].imshow(label_after_consolidation[0], cmap=\"gray\")\nfor ax in axs:\n ax.axis(\"off\")\nplt.tight_layout()\nplt.show()\nlabel_0 = ngff_image.label.get(\"new_label\", path=\"0\") label_2 = ngff_image.label.get(\"new_label\", path=\"2\") label_before_consolidation = label_2.array[...] # Consolidate the label label_0.consolidate() label_after_consolidation = label_2.array[...] # make a subplot with two image show side by side fig, axs = plt.subplots(1, 2, figsize=(10, 5)) axs[0].imshow(label_before_consolidation[0], cmap=\"gray\") axs[1].imshow(label_after_consolidation[0], cmap=\"gray\") for ax in axs: ax.axis(\"off\") plt.tight_layout() plt.show() In\u00a0[10]: Copied!
import numpy as np\nimport pandas as pd\n\nprint(f\"List of feature table: {ngff_image.table.list(type='feature_table')}\")\n\n\nnuclei = ngff_image.label.get('nuclei')\n\n# Create a table with random features for each nuclei in each ROI\nlist_of_records = []\nfor roi in roi_table.list_rois:\n nuclei_in_roi = nuclei.get_data_from_roi(roi, mode='numpy')\n for nuclei_id in np.unique(nuclei_in_roi)[1:]:\n list_of_records.append(\n {\"label\": nuclei_id,\n \"feat1\": np.random.rand(),\n \"feat2\": np.random.rand(),\n \"ROI\": roi.infos.get(\"field_index\")}\n )\n\nfeat_df = pd.DataFrame.from_records(list_of_records)\n\n# Create a new feature table\nfeat_table = ngff_image.table.new(name='new_feature_table',\n region='../nuclei',\n table_type='feature_table',\n overwrite=True)\n\nprint(f\"New list of feature table: {ngff_image.table.list(type='feature_table')}\")\nfeat_table.table = feat_df\nfeat_table.write()\n\nfeat_table.table\nimport numpy as np import pandas as pd print(f\"List of feature table: {ngff_image.table.list(type='feature_table')}\") nuclei = ngff_image.label.get('nuclei') # Create a table with random features for each nuclei in each ROI list_of_records = [] for roi in roi_table.list_rois: nuclei_in_roi = nuclei.get_data_from_roi(roi, mode='numpy') for nuclei_id in np.unique(nuclei_in_roi)[1:]: list_of_records.append( {\"label\": nuclei_id, \"feat1\": np.random.rand(), \"feat2\": np.random.rand(), \"ROI\": roi.infos.get(\"field_index\")} ) feat_df = pd.DataFrame.from_records(list_of_records) # Create a new feature table feat_table = ngff_image.table.new(name='new_feature_table', region='../nuclei', table_type='feature_table', overwrite=True) print(f\"New list of feature table: {ngff_image.table.list(type='feature_table')}\") feat_table.table = feat_df feat_table.write() feat_table.table
List of feature table: ['regionprops_DAPI', 'nuclei_measurements_wf3', 'nuclei_measurements_wf4', 'nuclei_lamin_measurements_wf4']\n
\n---------------------------------------------------------------------------\nTypeError Traceback (most recent call last)\nCell In[10], line 24\n 21 feat_df = pd.DataFrame.from_records(list_of_records)\n 23 # Create a new feature table\n---> 24 feat_table = ngff_image.table.new(name='new_feature_table',\n 25 region='../nuclei',\n 26 table_type='feature_table',\n 27 overwrite=True)\n 29 print(f\"New list of feature table: {ngff_image.table.list(type='feature_table')}\")\n 30 feat_table.table = feat_df\n\nFile /opt/hostedtoolcache/Python/3.12.6/x64/lib/python3.12/site-packages/ngio/tables/tables_group.py:146, in TableGroup.new(self, name, table_type, overwrite, version, **type_specific_kwargs)\n 143 list_of_tables.remove(name)\n 145 table_impl = _find_table_impl(table_type=table_type, version=version)\n--> 146 new_table = table_impl._new(\n 147 parent_group=self._group,\n 148 name=name,\n 149 overwrite=overwrite,\n 150 **type_specific_kwargs,\n 151 )\n 153 self._group.attrs[\"tables\"] = [*list_of_tables, name]\n 155 return new_table\n\nTypeError: FeatureTableV1._new() got an unexpected keyword argument 'region'In\u00a0[\u00a0]: Copied!
\n"},{"location":"notebooks/image/#imageslabelstables","title":"Images/Labels/Tables\u00b6","text":"
In this notebook we will show how to use the Image
, Label
and Table
objects.
Images can be loaded from a NgffImage
object.
roi
objects from a roi_table
can be used to extract a region of interest from an image or a label.
Similarly to the .get_data()
we can use the .set_data()
(or set_data_roi
) method to write part of an image to disk.
When doing image analysis, we often need to create new labels or tables. The ngff_image
allows us to simply create new labels and tables.
Every time we modify a label or a image, we are modifying the on-disk data on one layer only. This means that if we have the image saved in multiple resolutions, we need to consolidate the changes to all resolutions. To do so, we can use the .consolidate()
method.
We can simply create a new table by create a new Table
object from a pandas dataframe. For a simple feature table the only reuiremnt is to have a integer column named label
that will be used to link the table to the objects in the image.
import matplotlib.pyplot as plt\n\nfrom ngio.core import NgffImage\n\nngff_image = NgffImage(\"../../data/20200812-CardiomyocyteDifferentiation14-Cycle1.zarr/B/03/0\")\nimport matplotlib.pyplot as plt from ngio.core import NgffImage ngff_image = NgffImage(\"../../data/20200812-CardiomyocyteDifferentiation14-Cycle1.zarr/B/03/0\") In\u00a0[2]: Copied!
mip_ngff = ngff_image.derive_new_image(\"../../data/20200812-CardiomyocyteDifferentiation14-Cycle1.zarr/B/03/0_mip\",\n name=\"MIP\",\n shape=(1, 1, 2160, 5120))\nmip_ngff = ngff_image.derive_new_image(\"../../data/20200812-CardiomyocyteDifferentiation14-Cycle1.zarr/B/03/0_mip\", name=\"MIP\", shape=(1, 1, 2160, 5120)) In\u00a0[3]: Copied!
# Get the source imag\nsource_image = ngff_image.get_image()\nprint(\"Source image loaded with shape:\", source_image.shape)\n\n# Get the MIP image\nmip_image = mip_ngff.get_image()\nprint(\"MIP image loaded with shape:\", mip_image.shape)\n\n# Get a ROI table\nroi_table = ngff_image.table.get(\"FOV_ROI_table\")\nprint(\"ROI table loaded with\", len(roi_table.list_rois), \"ROIs\")\n\n# For each ROI in the table\n# - get the data from the source image\n# - calculate the MIP\n# - set the data in the MIP image\nfor roi in roi_table.list_rois:\n print(f\" - Processing ROI {roi.infos.get(\"field_index\")}\")\n patch = source_image.get_data_from_roi(roi)\n mip_patch = patch.max(axis=1, keepdims=True)\n mip_image.set_data_from_roi(patch=mip_patch, roi=roi)\n \nprint(\"MIP image saved\")\n\nplt.figure(figsize=(5, 5))\nplt.title(\"Mip\")\nplt.imshow(mip_image.array[0, 0, :, :], cmap=\"gray\")\nplt.axis('off')\nplt.tight_layout()\nplt.show()\n# Get the source imag source_image = ngff_image.get_image() print(\"Source image loaded with shape:\", source_image.shape) # Get the MIP image mip_image = mip_ngff.get_image() print(\"MIP image loaded with shape:\", mip_image.shape) # Get a ROI table roi_table = ngff_image.table.get(\"FOV_ROI_table\") print(\"ROI table loaded with\", len(roi_table.list_rois), \"ROIs\") # For each ROI in the table # - get the data from the source image # - calculate the MIP # - set the data in the MIP image for roi in roi_table.list_rois: print(f\" - Processing ROI {roi.infos.get(\"field_index\")}\") patch = source_image.get_data_from_roi(roi) mip_patch = patch.max(axis=1, keepdims=True) mip_image.set_data_from_roi(patch=mip_patch, roi=roi) print(\"MIP image saved\") plt.figure(figsize=(5, 5)) plt.title(\"Mip\") plt.imshow(mip_image.array[0, 0, :, :], cmap=\"gray\") plt.axis('off') plt.tight_layout() plt.show()
Source image loaded with shape: (1, 2, 2160, 5120)\nMIP image loaded with shape: (1, 1, 2160, 5120)\nROI table loaded with 2 ROIs\n - Processing ROI FOV_1\n - Processing ROI FOV_2\nMIP image saved\nIn\u00a0[4]: Copied!
# Get the MIP image at a lower resolution\nmip_image_2 = mip_ngff.get_image(path=\"2\")\n\nimage_before_consolidation = mip_image_2.get_data(c=0, z=0)\n\n# Consolidate the pyramid\nmip_image.consolidate()\n\nimage_after_consolidation = mip_image_2.get_data(c=0, z=0)\n\nfig, axs = plt.subplots(2, 1, figsize=(10, 5))\naxs[0].set_title(\"Before consolidation\")\naxs[0].imshow(image_before_consolidation, cmap=\"gray\")\naxs[1].set_title(\"After consolidation\")\naxs[1].imshow(image_after_consolidation, cmap=\"gray\")\nfor ax in axs:\n ax.axis('off')\nplt.tight_layout()\nplt.show()\n# Get the MIP image at a lower resolution mip_image_2 = mip_ngff.get_image(path=\"2\") image_before_consolidation = mip_image_2.get_data(c=0, z=0) # Consolidate the pyramid mip_image.consolidate() image_after_consolidation = mip_image_2.get_data(c=0, z=0) fig, axs = plt.subplots(2, 1, figsize=(10, 5)) axs[0].set_title(\"Before consolidation\") axs[0].imshow(image_before_consolidation, cmap=\"gray\") axs[1].set_title(\"After consolidation\") axs[1].imshow(image_after_consolidation, cmap=\"gray\") for ax in axs: ax.axis('off') plt.tight_layout() plt.show() In\u00a0[5]: Copied!
mip_roi_table = mip_ngff.table.new(\"FOV_ROI_table\", overwrite=True)\n\nroi_list = []\nfor roi in roi_table.list_rois:\n print(f\" - Processing ROI {roi.infos.get(\"field_index\")}\")\n roi.z_length = 1 # In the MIP image, the z dimension is 1\n roi_list.append(roi)\n\nmip_roi_table.append_rois(roi_list)\nmip_roi_table.write()\n\nmip_roi_table.table\nmip_roi_table = mip_ngff.table.new(\"FOV_ROI_table\", overwrite=True) roi_list = [] for roi in roi_table.list_rois: print(f\" - Processing ROI {roi.infos.get(\"field_index\")}\") roi.z_length = 1 # In the MIP image, the z dimension is 1 roi_list.append(roi) mip_roi_table.append_rois(roi_list) mip_roi_table.write() mip_roi_table.table
- Processing ROI FOV_1\n - Processing ROI FOV_2\nOut[5]: x_micrometer y_micrometer z_micrometer len_x_micrometer len_y_micrometer len_z_micrometer FieldIndex FOV_1 0.0 0.0 0.0 416.0 351.0 1 FOV_2 416.0 0.0 0.0 416.0 351.0 1 In\u00a0[6]: Copied!
# Setup a simple segmentation function\n\nimport numpy as np\nfrom matplotlib.colors import ListedColormap\nfrom skimage.filters import threshold_otsu\nfrom skimage.measure import label\n\nrand_cmap = np.random.rand(1000, 3)\nrand_cmap[0] = 0\nrand_cmap = ListedColormap(rand_cmap)\n\n\ndef otsu_threshold_segmentation(image: np.ndarray, max_label:int) -> np.ndarray:\n \"\"\"Simple segmentation using Otsu thresholding.\"\"\"\n threshold = threshold_otsu(image)\n binary = image > threshold\n label_image = label(binary)\n label_image += max_label\n label_image = np.where(binary, label_image, 0)\n return label_image\n# Setup a simple segmentation function import numpy as np from matplotlib.colors import ListedColormap from skimage.filters import threshold_otsu from skimage.measure import label rand_cmap = np.random.rand(1000, 3) rand_cmap[0] = 0 rand_cmap = ListedColormap(rand_cmap) def otsu_threshold_segmentation(image: np.ndarray, max_label:int) -> np.ndarray: \"\"\"Simple segmentation using Otsu thresholding.\"\"\" threshold = threshold_otsu(image) binary = image > threshold label_image = label(binary) label_image += max_label label_image = np.where(binary, label_image, 0) return label_image In\u00a0[7]: Copied!
nuclei_image = mip_ngff.label.derive(name=\"nuclei\", overwrite=True)\nnuclei_image = mip_ngff.label.derive(name=\"nuclei\", overwrite=True) In\u00a0[8]: Copied!
# Get the source imag\nsource_image = mip_ngff.get_image()\nprint(\"Source image loaded with shape:\", source_image.shape)\n\n# Get a ROI table\nroi_table = mip_ngff.table.get(\"FOV_ROI_table\")\nprint(\"ROI table loaded with\", len(roi_table.list_rois), \"ROIs\")\n\n# Find the DAPI channel\ndapi_idx = source_image.get_channel_idx(label=\"DAPI\")\n\n# For each ROI in the table\n# - get the data from the source image\n# - calculate the Segmentation\n# - set the data in segmentation image\nmax_label = 0\nfor roi in roi_table.list_rois:\n print(f\" - Processing ROI {roi.infos.get(\"field_index\")}\")\n patch = source_image.get_data_from_roi(roi, c=dapi_idx)\n segmentation = otsu_threshold_segmentation(patch, max_label)\n\n # Add the max label of the previous segmentation to avoid overlapping labels\n max_label = segmentation.max()\n\n nuclei_image.set_data_from_roi(patch=segmentation, roi=roi)\n\n# Consolidate the segmentation image\nnuclei_image.consolidate()\n\nprint(\"Segmentation image saved\")\nfig, axs = plt.subplots(2, 1, figsize=(10, 5))\naxs[0].set_title(\"MIP\")\naxs[0].imshow(source_image.array[0, 0], cmap=\"gray\")\naxs[1].set_title(\"Nuclei segmentation\")\naxs[1].imshow(nuclei_image.array[0], cmap=rand_cmap, interpolation='nearest')\nfor ax in axs:\n ax.axis('off')\nplt.tight_layout()\nplt.show()\n# Get the source imag source_image = mip_ngff.get_image() print(\"Source image loaded with shape:\", source_image.shape) # Get a ROI table roi_table = mip_ngff.table.get(\"FOV_ROI_table\") print(\"ROI table loaded with\", len(roi_table.list_rois), \"ROIs\") # Find the DAPI channel dapi_idx = source_image.get_channel_idx(label=\"DAPI\") # For each ROI in the table # - get the data from the source image # - calculate the Segmentation # - set the data in segmentation image max_label = 0 for roi in roi_table.list_rois: print(f\" - Processing ROI {roi.infos.get(\"field_index\")}\") patch = source_image.get_data_from_roi(roi, c=dapi_idx) segmentation = otsu_threshold_segmentation(patch, max_label) # Add the max label of the previous segmentation to avoid overlapping labels max_label = segmentation.max() nuclei_image.set_data_from_roi(patch=segmentation, roi=roi) # Consolidate the segmentation image nuclei_image.consolidate() print(\"Segmentation image saved\") fig, axs = plt.subplots(2, 1, figsize=(10, 5)) axs[0].set_title(\"MIP\") axs[0].imshow(source_image.array[0, 0], cmap=\"gray\") axs[1].set_title(\"Nuclei segmentation\") axs[1].imshow(nuclei_image.array[0], cmap=rand_cmap, interpolation='nearest') for ax in axs: ax.axis('off') plt.tight_layout() plt.show()
Source image loaded with shape: (1, 1, 2160, 5120)\nROI table loaded with 2 ROIs\n - Processing ROI FOV_1\n - Processing ROI FOV_2\n
Segmentation image saved\n"},{"location":"notebooks/processing/#processing","title":"Processing\u00b6","text":"
In this notebook we will implement a couple of mock image analysis workflows using ngio
.
In this workflow we will read a volumetric image and create a maximum intensity projection (MIP) along the z-axis.
"},{"location":"notebooks/processing/#step-1-create-a-ngff-image","title":"step 1: Create a ngff image\u00b6","text":"For this example we will use the following publicly available image
"},{"location":"notebooks/processing/#step-2-create-a-new-ngff-image-to-store-the-mip","title":"step 2: Create a new ngff image to store the MIP\u00b6","text":""},{"location":"notebooks/processing/#step-3-run-the-workflow","title":"step 3: Run the workflow\u00b6","text":"For each roi in the image, create a MIP and store it in the new image
"},{"location":"notebooks/processing/#step-4-consolidate-the-results-important","title":"step 4: Consolidate the results (!!! Important)\u00b6","text":"In this we wrote the mip image to a single level of the image pyramid. To truly consolidate the results we would need to write the mip to all levels of the image pyramid. We can do this by calling the .consolidate()
method on the image.
As a final step we will create a new ROI table that contains the MIPs as ROIs. Where we correct the z
bounds of the ROIs to reflect the MIP.
Now we can use the MIP image to segment the image using a simple thresholding algorithm.
"},{"location":"notebooks/processing/#step-1-derive-a-new-label-image-from-the-mip-image","title":"step 1: Derive a new label image from the MIP image\u00b6","text":""},{"location":"notebooks/processing/#step-2-run-the-workflow","title":"step 2: Run the workflow\u00b6","text":""}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Welcome to NGIO","text":"NGIO is a Python library to streamline OME-Zarr image analysis workflows.
Main Goals:
To get started, check out the Getting Started guide.
"},{"location":"#ngio-is-under-active-development","title":"\ud83d\udea7 Ngio is Under active Development \ud83d\udea7","text":""},{"location":"#roadmap","title":"Roadmap","text":"Feature Status ETA Description Metadata Handling \u2705 Read, Write, Validate OME-Zarr Metadata (0.4 supported, 0.5 ready) OME-Zarr Validation \u2705 Validate OME-Zarr files for compliance with the OME-Zarr Specification + Compliance between Metadata and Data Base Image Handling \u2705 Load data from OME-Zarr files, retrieve basic metadata, and write data ROI Handling \u2705 Common ROI models Label Handling \u2705 Mid-September Based on Image Handling Table Validation \u2705 Mid-September Validate Table fractal V1 + Compliance between Metadata and Data Table Handling \u2705 Mid-September Read, Write ROI, Features, and Masked Tables Basic Iterators Ongoing End-September Read and Write Iterators for common access patterns Base Documentation \u2705 End-September API Documentation and Examples Beta Ready Testing \u2705 End-September Beta Testing; Library is ready for testing, but the API is not stable Mask Iterators Ongoing October Iterators over Masked Tables Advanced Iterators Not started October Iterators for advanced access patterns Parallel Iterators Not started End of the Year Concurrent Iterators for parallel read and write Full Documentation Not started End of the Year Complete Documentation Release 1.0 (Commitment to API) Not started End of the Year API is stable; breaking changes will be avoided"},{"location":"#contributors","title":"Contributors","text":"ngio
is developed at the BioVisionCenter at the University of Zurich. The main contributors are: @lorenzocerrone, @jluethi.
ngio
is released according to the BSD-3-Clause License. See LICENSE
Warning
The library is still in development and is not yet stable. The API is subject to change, bugs and breaking changes are expected.
Warning
The documentation is still under development. It is not yet complete and may contain errors and inaccuracies.
"},{"location":"getting-started/#installation","title":"Installation","text":"Currently, ngio
is not available on PyPI. You can install it from the source code.
pip install \"ngio[v2]\"\n
The v2
extra installs the latest version of zarr-python
from the v2
branch. ngio
is currently not completely compatible with the v3
release of zarr-python
.
ngio
API Overview","text":"ngio
implements an abstract object base API for handling OME-Zarr files. The three main objects are NgffImage
, Image
(Label
), and ROITables
.
NgffImage
is the main entry point to the library. It is used to open an OME-Zarr Image and manage its metadata. This object can not be used to access the data directly. but it can be used to access and create the Image
, Label
, and Tables
objects. Moreover it can be used to derive a new Ngff
images based on the current one.Image
and Label
are used to access \"ImageLike\" objects. They are the main objects to access the data in the OME-Zarr file, manage the metadata, and write data.ROITables
can be used to access specific region of interest in the image. They are tightly integrated with the Image
and Label
objects.Currently, the library is not yet stable. However, you can see some example usage in our demo notebooks.
ngio.core
","text":"Core classes for the ngio library.
"},{"location":"api/core/#ngffimage","title":"NGFFImage","text":""},{"location":"api/core/#ngio.core.NgffImage","title":"ngio.core.NgffImage
","text":"A class to handle OME-NGFF images.
Source code inngio/core/ngff_image.py
class NgffImage:\n \"\"\"A class to handle OME-NGFF images.\"\"\"\n\n def __init__(self, store: StoreLike) -> None:\n \"\"\"Initialize the NGFFImage in read mode.\"\"\"\n self.store = store\n self.group = open_group_wrapper(store=store, mode=\"r+\")\n self._image_meta = get_ngff_image_meta_handler(\n self.group, meta_mode=\"image\", cache=False\n )\n\n self.table = TableGroup(self.group)\n self.label = LabelGroup(self.group, image_ref=self.get_image())\n\n @property\n def image_meta(self) -> ImageMeta:\n \"\"\"Get the image metadata.\"\"\"\n return self._image_meta.load_meta()\n\n @property\n def num_levels(self) -> int:\n \"\"\"Get the number of levels in the image.\"\"\"\n return self.image_meta.num_levels\n\n @property\n def levels_paths(self) -> list[str]:\n \"\"\"Get the paths of the levels in the image.\"\"\"\n return self.image_meta.levels_paths\n\n def get_image(\n self,\n *,\n path: str | None = None,\n pixel_size: PixelSize | None = None,\n highest_resolution: bool = True,\n ) -> Image:\n \"\"\"Get an image handler for the given level.\n\n Args:\n path (str | None, optional): The path to the level.\n pixel_size (tuple[float, ...] | list[float] | None, optional): The pixel\n size of the level.\n highest_resolution (bool, optional): Whether to get the highest\n resolution level\n\n Returns:\n ImageHandler: The image handler.\n \"\"\"\n if path is not None or pixel_size is not None:\n highest_resolution = False\n\n return Image(\n store=self.group,\n path=path,\n pixel_size=pixel_size,\n highest_resolution=highest_resolution,\n )\n\n def _update_omero_window(self) -> None:\n \"\"\"Update the OMERO window.\"\"\"\n meta = self.image_meta\n image = self.get_image(highest_resolution=True)\n max_dtype = np.iinfo(image.array.dtype).max\n start, end = image.dask_array.min().compute(), image.dask_array.max().compute()\n\n channel_list = meta.omero.channels\n\n new_channel_list = []\n for channel in channel_list:\n channel.extra_fields[\"window\"] = {\n \"start\": start,\n \"end\": end,\n \"min\": 0,\n \"max\": max_dtype,\n }\n new_channel_list.append(channel)\n\n meta.omero.channels = new_channel_list\n self._image_meta.write_meta(meta)\n\n def derive_new_image(\n self,\n store: StoreLike,\n name: str,\n overwrite: bool = True,\n **kwargs,\n ) -> \"NgffImage\":\n \"\"\"Derive a new image from the current image.\n\n Args:\n store (StoreLike): The store to create the new image in.\n name (str): The name of the new image.\n overwrite (bool): Whether to overwrite the image if it exists\n **kwargs: Additional keyword arguments.\n Follow the same signature as `create_empty_ome_zarr_image`.\n\n Returns:\n NgffImage: The new image.\n \"\"\"\n image_0 = self.get_image(highest_resolution=True)\n\n default_kwargs = {\n \"store\": store,\n \"shape\": image_0.on_disk_shape,\n \"chunks\": image_0.array.chunks,\n \"dtype\": image_0.array.dtype,\n \"on_disk_axis\": image_0.dataset.on_disk_axes_names,\n \"pixel_sizes\": image_0.pixel_size,\n \"xy_scaling_factor\": self.image_meta.xy_scaling_factor,\n \"z_scaling_factor\": self.image_meta.z_scaling_factor,\n \"time_spacing\": image_0.dataset.time_spacing,\n \"time_units\": image_0.dataset.time_axis_unit,\n \"num_levels\": self.num_levels,\n \"name\": name,\n \"channel_labels\": image_0.channel_labels,\n \"channel_wavelengths\": None,\n \"channel_kwargs\": None,\n \"omero_kwargs\": None,\n \"overwrite\": overwrite,\n \"version\": self.image_meta.version,\n }\n\n default_kwargs.update(kwargs)\n\n create_empty_ome_zarr_image(\n **default_kwargs,\n )\n return NgffImage(store=store)\n
"},{"location":"api/core/#ngio.core.NgffImage.image_meta","title":"image_meta: ImageMeta
property
","text":"Get the image metadata.
"},{"location":"api/core/#ngio.core.NgffImage.levels_paths","title":"levels_paths: list[str]
property
","text":"Get the paths of the levels in the image.
"},{"location":"api/core/#ngio.core.NgffImage.num_levels","title":"num_levels: int
property
","text":"Get the number of levels in the image.
"},{"location":"api/core/#ngio.core.NgffImage.__init__","title":"__init__(store: StoreLike) -> None
","text":"Initialize the NGFFImage in read mode.
Source code inngio/core/ngff_image.py
def __init__(self, store: StoreLike) -> None:\n \"\"\"Initialize the NGFFImage in read mode.\"\"\"\n self.store = store\n self.group = open_group_wrapper(store=store, mode=\"r+\")\n self._image_meta = get_ngff_image_meta_handler(\n self.group, meta_mode=\"image\", cache=False\n )\n\n self.table = TableGroup(self.group)\n self.label = LabelGroup(self.group, image_ref=self.get_image())\n
"},{"location":"api/core/#ngio.core.NgffImage.derive_new_image","title":"derive_new_image(store: StoreLike, name: str, overwrite: bool = True, **kwargs) -> NgffImage
","text":"Derive a new image from the current image.
Parameters:
store
(StoreLike
) \u2013 The store to create the new image in.
name
(str
) \u2013 The name of the new image.
overwrite
(bool
, default: True
) \u2013 Whether to overwrite the image if it exists
**kwargs
\u2013 Additional keyword arguments. Follow the same signature as create_empty_ome_zarr_image
.
Returns:
NgffImage
( NgffImage
) \u2013 The new image.
ngio/core/ngff_image.py
def derive_new_image(\n self,\n store: StoreLike,\n name: str,\n overwrite: bool = True,\n **kwargs,\n) -> \"NgffImage\":\n \"\"\"Derive a new image from the current image.\n\n Args:\n store (StoreLike): The store to create the new image in.\n name (str): The name of the new image.\n overwrite (bool): Whether to overwrite the image if it exists\n **kwargs: Additional keyword arguments.\n Follow the same signature as `create_empty_ome_zarr_image`.\n\n Returns:\n NgffImage: The new image.\n \"\"\"\n image_0 = self.get_image(highest_resolution=True)\n\n default_kwargs = {\n \"store\": store,\n \"shape\": image_0.on_disk_shape,\n \"chunks\": image_0.array.chunks,\n \"dtype\": image_0.array.dtype,\n \"on_disk_axis\": image_0.dataset.on_disk_axes_names,\n \"pixel_sizes\": image_0.pixel_size,\n \"xy_scaling_factor\": self.image_meta.xy_scaling_factor,\n \"z_scaling_factor\": self.image_meta.z_scaling_factor,\n \"time_spacing\": image_0.dataset.time_spacing,\n \"time_units\": image_0.dataset.time_axis_unit,\n \"num_levels\": self.num_levels,\n \"name\": name,\n \"channel_labels\": image_0.channel_labels,\n \"channel_wavelengths\": None,\n \"channel_kwargs\": None,\n \"omero_kwargs\": None,\n \"overwrite\": overwrite,\n \"version\": self.image_meta.version,\n }\n\n default_kwargs.update(kwargs)\n\n create_empty_ome_zarr_image(\n **default_kwargs,\n )\n return NgffImage(store=store)\n
"},{"location":"api/core/#ngio.core.NgffImage.get_image","title":"get_image(*, path: str | None = None, pixel_size: PixelSize | None = None, highest_resolution: bool = True) -> Image
","text":"Get an image handler for the given level.
Parameters:
path
(str | None
, default: None
) \u2013 The path to the level.
pixel_size
(tuple[float, ...] | list[float] | None
, default: None
) \u2013 The pixel size of the level.
highest_resolution
(bool
, default: True
) \u2013 Whether to get the highest resolution level
Returns:
ImageHandler
( Image
) \u2013 The image handler.
ngio/core/ngff_image.py
def get_image(\n self,\n *,\n path: str | None = None,\n pixel_size: PixelSize | None = None,\n highest_resolution: bool = True,\n) -> Image:\n \"\"\"Get an image handler for the given level.\n\n Args:\n path (str | None, optional): The path to the level.\n pixel_size (tuple[float, ...] | list[float] | None, optional): The pixel\n size of the level.\n highest_resolution (bool, optional): Whether to get the highest\n resolution level\n\n Returns:\n ImageHandler: The image handler.\n \"\"\"\n if path is not None or pixel_size is not None:\n highest_resolution = False\n\n return Image(\n store=self.group,\n path=path,\n pixel_size=pixel_size,\n highest_resolution=highest_resolution,\n )\n
"},{"location":"notebooks/basic_usage/","title":"Basic Usage","text":"In\u00a0[1]: Copied! from ngio.core import NgffImage\nngff_image = NgffImage(\"../../data/20200812-CardiomyocyteDifferentiation14-Cycle1_mip.zarr/B/03/0\")\nfrom ngio.core import NgffImage ngff_image = NgffImage(\"../../data/20200812-CardiomyocyteDifferentiation14-Cycle1_mip.zarr/B/03/0\")
The ngff_image
contains several attributes and methods to interact with the OME-NGFF
(OME-Zarr
) file on the storage.
# Explore object metadata\nprint(\"Levels: \", ngff_image.levels_paths)\nprint(\"Num Levels: \", ngff_image.num_levels)\n# Explore object metadata print(\"Levels: \", ngff_image.levels_paths) print(\"Num Levels: \", ngff_image.num_levels)
Levels: ['0', '1', '2', '3', '4']\nNum Levels: 5\n
Get a single level
of the image pyramid as Image
(to know more about the Image
class, please refer to the Image notebook The Image
object is the main object to interact with the image. It contains methods to interact with the image data and metadata.
from ngio.ngff_meta import PixelSize\n\n# 1. Get image from highest resolution (default)\nimage = ngff_image.get_image()\nprint(f\"{image.shape=}\")\nprint(f\"{image.pixel_size=}\")\n\n# 2. Get image from a specific level using the path keyword\nprint(f\"{image.shape=}\")\nprint(f\"{image.pixel_size=}\")\n\n# 3. Get image from a specific pixel size using the pixel_size keyword\nimage = ngff_image.get_image(pixel_size=PixelSize(x=0.65, y=0.65, z=1))\nprint(f\"{image.shape=}\")\nprint(f\"{image.pixel_size=}\")\nfrom ngio.ngff_meta import PixelSize # 1. Get image from highest resolution (default) image = ngff_image.get_image() print(f\"{image.shape=}\") print(f\"{image.pixel_size=}\") # 2. Get image from a specific level using the path keyword print(f\"{image.shape=}\") print(f\"{image.pixel_size=}\") # 3. Get image from a specific pixel size using the pixel_size keyword image = ngff_image.get_image(pixel_size=PixelSize(x=0.65, y=0.65, z=1)) print(f\"{image.shape=}\") print(f\"{image.pixel_size=}\")
image.shape=(3, 1, 4320, 5120)\nimage.pixel_size=PixelSize(x=0.1625, y=0.1625, z=1.0, unit=micrometer)\nimage.shape=(3, 1, 4320, 5120)\nimage.pixel_size=PixelSize(x=0.1625, y=0.1625, z=1.0, unit=micrometer)\nimage.shape=(3, 1, 1080, 1280)\nimage.pixel_size=PixelSize(x=0.65, y=0.65, z=1.0, unit=micrometer)\nIn\u00a0[4]: Copied!
print(\"List of Labels: \", ngff_image.label.list())\n\nlabel_nuclei = ngff_image.label.get(\"nuclei\", path=\"0\")\nprint(f\"{label_nuclei.shape=}\")\nprint(f\"{label_nuclei.pixel_size=}\")\nprint(\"List of Labels: \", ngff_image.label.list()) label_nuclei = ngff_image.label.get(\"nuclei\", path=\"0\") print(f\"{label_nuclei.shape=}\") print(f\"{label_nuclei.pixel_size=}\")
List of Labels: ['nuclei', 'wf_2_labels', 'wf_3_labels', 'wf_4_labels']\nlabel_nuclei.shape=(1, 4320, 5120)\nlabel_nuclei.pixel_size=PixelSize(x=0.1625, y=0.1625, z=1.0, unit=micrometer)\nIn\u00a0[5]: Copied!
print(\"List of Tables: \", ngff_image.table.list())\nprint(\" - Feature tables: \", ngff_image.table.list(type='feature_table'))\nprint(\" - Roi tables: \", ngff_image.table.list(type='roi_table'))\nprint(\" - Masking Roi tables: \", ngff_image.table.list(type='masking_roi_table'))\nprint(\"List of Tables: \", ngff_image.table.list()) print(\" - Feature tables: \", ngff_image.table.list(type='feature_table')) print(\" - Roi tables: \", ngff_image.table.list(type='roi_table')) print(\" - Masking Roi tables: \", ngff_image.table.list(type='masking_roi_table'))
List of Tables: ['FOV_ROI_table', 'nuclei_ROI_table', 'well_ROI_table', 'regionprops_DAPI', 'nuclei_measurements_wf3', 'nuclei_measurements_wf4', 'nuclei_lamin_measurements_wf4']\n - Feature tables: ['regionprops_DAPI', 'nuclei_measurements_wf3', 'nuclei_measurements_wf4', 'nuclei_lamin_measurements_wf4']\n - Roi tables: ['FOV_ROI_table', 'well_ROI_table']\n - Masking Roi tables: ['nuclei_ROI_table']\nIn\u00a0[6]: Copied!
# Loading a table\nfeature_table = ngff_image.table.get(\"regionprops_DAPI\")\nfeature_table.table\n# Loading a table feature_table = ngff_image.table.get(\"regionprops_DAPI\") feature_table.table
/opt/hostedtoolcache/Python/3.12.6/x64/lib/python3.12/site-packages/anndata/_core/aligned_df.py:68: ImplicitModificationWarning: Transforming to str index.\n warnings.warn(\"Transforming to str index.\", ImplicitModificationWarning)\nOut[6]: area bbox_area equivalent_diameter max_intensity mean_intensity min_intensity standard_deviation_intensity label 1 2120.0 2655.0 15.938437 476.0 278.635864 86.0 54.343792 2 327.0 456.0 8.547709 604.0 324.162079 118.0 90.847092 3 1381.0 1749.0 13.816510 386.0 212.682114 60.0 50.169601 4 2566.0 3588.0 16.985800 497.0 251.731491 61.0 53.307186 5 4201.0 5472.0 20.019413 466.0 223.862885 51.0 56.719025 ... ... ... ... ... ... ... ... 3002 1026.0 1288.0 12.513618 589.0 308.404480 132.0 64.681778 3003 859.0 1080.0 11.794101 400.0 270.349243 107.0 49.040470 3004 508.0 660.0 9.899693 314.0 205.043304 82.0 33.249981 3005 369.0 440.0 8.899028 376.0 217.970184 82.0 50.978519 3006 278.0 330.0 8.097459 339.0 217.996399 100.0 38.510067
3006 rows \u00d7 7 columns
In\u00a0[7]: Copied!# Loading a roi table\nroi_table = ngff_image.table.get(\"FOV_ROI_table\")\n\nprint(f\"{roi_table.list_field_indexes=}\")\nprint(f\"{roi_table.get_roi(\"FOV_1\")=}\")\n\nroi_table.table\n# Loading a roi table roi_table = ngff_image.table.get(\"FOV_ROI_table\") print(f\"{roi_table.list_field_indexes=}\") print(f\"{roi_table.get_roi(\"FOV_1\")=}\") roi_table.table
roi_table.list_field_indexes=['FOV_1', 'FOV_2', 'FOV_3', 'FOV_4']\nroi_table.get_roi(\"FOV_1\")=WorldCooROI(x_length=416.0, y_length=351.0, z_length=1.0, x=0.0, y=0.0, z=0.0)\nOut[7]: x_micrometer y_micrometer z_micrometer len_x_micrometer len_y_micrometer len_z_micrometer x_micrometer_original y_micrometer_original FieldIndex FOV_1 0.0 0.0 0.0 416.0 351.0 1.0 -1448.300049 -1517.699951 FOV_2 416.0 0.0 0.0 416.0 351.0 1.0 -1032.300049 -1517.699951 FOV_3 0.0 351.0 0.0 416.0 351.0 1.0 -1448.300049 -1166.699951 FOV_4 416.0 351.0 0.0 416.0 351.0 1.0 -1032.300049 -1166.699951 In\u00a0[8]: Copied!
new_ngff_image = ngff_image.derive_new_image(\"../../data/new_ome.zarr\", name=\"new_image\")\n\nprint(f\"{new_ngff_image.store=}\")\nprint(f\"{new_ngff_image.levels_paths=}\")\nprint(f\"{new_ngff_image.num_levels=}\")\nnew_ngff_image = ngff_image.derive_new_image(\"../../data/new_ome.zarr\", name=\"new_image\") print(f\"{new_ngff_image.store=}\") print(f\"{new_ngff_image.levels_paths=}\") print(f\"{new_ngff_image.num_levels=}\")
new_ngff_image.store='../../data/new_ome.zarr'\nnew_ngff_image.levels_paths=['0', '1', '2', '3', '4']\nnew_ngff_image.num_levels=5\nIn\u00a0[\u00a0]: Copied!
\n"},{"location":"notebooks/basic_usage/#basic-usage","title":"Basic Usage\u00b6","text":"
In this notebook we will show how to use the 'ngffImage' class to manage a OME-NGFF image.
For this example we will use a small example image that can be downloaded from the following link: example ome-zarr
"},{"location":"notebooks/basic_usage/#ngffimage","title":"NgffImage\u00b6","text":"The NgffImage
provides a high-level interface to read, write and manipulate NGFF images. A NgffImage
can be created from a storelike object (e.g. a path to a directory or a zarr store) or from a zarr.Group
object.
The NgffImage
can also be used to load labels from a OME-NGFF
file.
The NgffImage
can also be used to load tables from a OME-NGFF
file.
ngio
supports three types of tables:
features table
A simple table to store features associated with a label.roi table
A table to store regions of interest.masking roi tables
A table to store single objects bounding boxes associated with a label.When processing an image, it is often useful to derive a new image from the original image. The NgffImage
class provides a method to derive a new image from the original image. When deriving a new image, a new NgffImage
object is created with the same metadata as the original image. Optionally the user can specify different metadata for the new image(.e.g. different channels names).
import matplotlib.pyplot as plt\n\nfrom ngio.core.ngff_image import NgffImage\n\nngff_image = NgffImage(\"../../data/20200812-CardiomyocyteDifferentiation14-Cycle1_mip.zarr/B/03/0\")\nimport matplotlib.pyplot as plt from ngio.core.ngff_image import NgffImage ngff_image = NgffImage(\"../../data/20200812-CardiomyocyteDifferentiation14-Cycle1_mip.zarr/B/03/0\") In\u00a0[2]: Copied!
image = ngff_image.get_image()\n\nprint(\"Image information:\")\nprint(f\"{image.shape=}\")\nprint(f\"{image.axes_names=}\")\nprint(f\"{image.pixel_size=}\")\nprint(f\"{image.channel_labels=}\")\nprint(f\"{image.dimensions=}\")\nimage = ngff_image.get_image() print(\"Image information:\") print(f\"{image.shape=}\") print(f\"{image.axes_names=}\") print(f\"{image.pixel_size=}\") print(f\"{image.channel_labels=}\") print(f\"{image.dimensions=}\")
Image information:\nimage.shape=(3, 1, 4320, 5120)\nimage.axes_names=['c', 'z', 'y', 'x']\nimage.pixel_size=PixelSize(x=0.1625, y=0.1625, z=1.0, unit=micrometer)\nimage.channel_labels=['DAPI', 'nanog', 'Lamin B1']\nimage.dimensions=<ngio.core.dimensions.Dimensions object at 0x7f65aed0d400>\n
The Image
object created is a lazy object, meaning that the image is not loaded into memory until it is needed. To get the image data from disk we can use the .array
attribute or we can get it as a dask.array
object using the .dask_array
attribute.
# Get image as a zarr.Array\narray = image.array\nprint(f\"{array}\")\n\ndask_array = image.dask_array\ndask_array\n# Get image as a zarr.Array array = image.array print(f\"{array}\") dask_array = image.dask_array dask_array
<zarr.core.Array '/0' (3, 1, 4320, 5120) uint16>\nOut[3]: Array Chunk Bytes 126.56 MiB 10.55 MiB Shape (3, 1, 4320, 5120) (1, 1, 2160, 2560) Dask graph 12 chunks in 2 graph layers Data type uint16 numpy.ndarray 3 1 5120 4320 1
Note, directly accessing the .array
or .dask_array
attributes will load the image as stored in the file.
Since in principle the images can have different axes order. A safer way to access the image data is to use the .get_data()
method, which will return the image data in canonical order (TCZYX).
image_numpy = image.get_data(c=0, x=slice(0, 250), y=slice(0, 250), preserve_dimensions=False, mode=\"numpy\")\n\nprint(f\"{image_numpy.shape=}\")\nimage_numpy = image.get_data(c=0, x=slice(0, 250), y=slice(0, 250), preserve_dimensions=False, mode=\"numpy\") print(f\"{image_numpy.shape=}\")
image_numpy.shape=(1, 250, 250)\nIn\u00a0[5]: Copied!
roi_table = ngff_image.table.get(\"FOV_ROI_table\")\nroi = roi_table.get_roi(\"FOV_1\")\nprint(f\"{roi=}\")\n\nimage_roi_1 = image.get_data_from_roi(roi=roi, c=0, preserve_dimensions=True, mode=\"dask\")\nimage_roi_1\nroi_table = ngff_image.table.get(\"FOV_ROI_table\") roi = roi_table.get_roi(\"FOV_1\") print(f\"{roi=}\") image_roi_1 = image.get_data_from_roi(roi=roi, c=0, preserve_dimensions=True, mode=\"dask\") image_roi_1
roi=WorldCooROI(x_length=416.0, y_length=351.0, z_length=1.0, x=0.0, y=0.0, z=0.0)\nOut[5]: Array Chunk Bytes 10.55 MiB 10.55 MiB Shape (1, 1, 2160, 2560) (1, 1, 2160, 2560) Dask graph 1 chunks in 3 graph layers Data type uint16 numpy.ndarray 1 1 2560 2160 1
The roi object can is defined in physical coordinates, and can be used to extract the region of interest from the image or label at any resolution.
In\u00a0[6]: Copied!image_2 = ngff_image.get_image(path=\"2\")\n# Two images at different resolutions\nprint(f\"{image.pixel_size=}\")\nprint(f\"{image_2.pixel_size=}\")\n\n# Get roi for higher resolution image\nimage_1_roi_1 = image.get_data_from_roi(roi=roi, c=0, preserve_dimensions=False)\n\n# Get roi for lower resolution image\nimage_2_roi_1 = image_2.get_data_from_roi(roi=roi, c=0, preserve_dimensions=False)\n\n# Plot the two images side by side\nfig, axs = plt.subplots(1, 2, figsize=(10, 5))\naxs[0].imshow(image_1_roi_1[0], cmap=\"gray\")\naxs[1].imshow(image_2_roi_1[0], cmap=\"gray\")\nplt.show()\nimage_2 = ngff_image.get_image(path=\"2\") # Two images at different resolutions print(f\"{image.pixel_size=}\") print(f\"{image_2.pixel_size=}\") # Get roi for higher resolution image image_1_roi_1 = image.get_data_from_roi(roi=roi, c=0, preserve_dimensions=False) # Get roi for lower resolution image image_2_roi_1 = image_2.get_data_from_roi(roi=roi, c=0, preserve_dimensions=False) # Plot the two images side by side fig, axs = plt.subplots(1, 2, figsize=(10, 5)) axs[0].imshow(image_1_roi_1[0], cmap=\"gray\") axs[1].imshow(image_2_roi_1[0], cmap=\"gray\") plt.show()
image.pixel_size=PixelSize(x=0.1625, y=0.1625, z=1.0, unit=micrometer)\nimage_2.pixel_size=PixelSize(x=0.65, y=0.65, z=1.0, unit=micrometer)\nIn\u00a0[7]: Copied!
import numpy as np\n\n# Get a small slice of the image\nsmall_slice = image.get_data(x=slice(1000, 2000), y=slice(1000, 2000))\n\n# Set the sample slice to zeros\nzeros_slice = np.zeros_like(small_slice)\nimage.set_data(patch=zeros_slice, x=slice(1000, 2000), y=slice(1000, 2000))\n\n\n# Load the image from disk and show the edited image\nnuclei = ngff_image.label.get(\"nuclei\")\nfig, axs = plt.subplots(1, 2, figsize=(10, 5))\naxs[0].imshow(image.array[0, 0], cmap=\"gray\")\naxs[1].imshow(nuclei.array[0])\nfor ax in axs:\n ax.axis(\"off\")\nplt.tight_layout()\nplt.show()\n\n# Add back the original slice to the image\nimage.set_data(patch=small_slice, x=slice(1000, 2000), y=slice(1000, 2000))\nimport numpy as np # Get a small slice of the image small_slice = image.get_data(x=slice(1000, 2000), y=slice(1000, 2000)) # Set the sample slice to zeros zeros_slice = np.zeros_like(small_slice) image.set_data(patch=zeros_slice, x=slice(1000, 2000), y=slice(1000, 2000)) # Load the image from disk and show the edited image nuclei = ngff_image.label.get(\"nuclei\") fig, axs = plt.subplots(1, 2, figsize=(10, 5)) axs[0].imshow(image.array[0, 0], cmap=\"gray\") axs[1].imshow(nuclei.array[0]) for ax in axs: ax.axis(\"off\") plt.tight_layout() plt.show() # Add back the original slice to the image image.set_data(patch=small_slice, x=slice(1000, 2000), y=slice(1000, 2000)) In\u00a0[8]: Copied!
# Create a a new label object and set it to a simple segmentation\nnew_label = ngff_image.label.derive(\"new_label\", overwrite=True)\n\nsimple_segmentation = image.array[0] > 100\nnew_label.array[...] = simple_segmentation\n\n# make a subplot with two image show side by side\nfig, axs = plt.subplots(1, 2, figsize=(10, 5))\naxs[0].imshow(image.array[0, 0], cmap=\"gray\")\naxs[1].imshow(new_label.array[0], cmap=\"gray\")\nfor ax in axs:\n ax.axis(\"off\")\nplt.tight_layout()\nplt.show()\n# Create a a new label object and set it to a simple segmentation new_label = ngff_image.label.derive(\"new_label\", overwrite=True) simple_segmentation = image.array[0] > 100 new_label.array[...] = simple_segmentation # make a subplot with two image show side by side fig, axs = plt.subplots(1, 2, figsize=(10, 5)) axs[0].imshow(image.array[0, 0], cmap=\"gray\") axs[1].imshow(new_label.array[0], cmap=\"gray\") for ax in axs: ax.axis(\"off\") plt.tight_layout() plt.show() In\u00a0[9]: Copied!
label_0 = ngff_image.label.get(\"new_label\", path=\"0\")\nlabel_2 = ngff_image.label.get(\"new_label\", path=\"2\")\n\nlabel_before_consolidation = label_2.array[...]\n\n# Consolidate the label\nlabel_0.consolidate()\n\nlabel_after_consolidation = label_2.array[...]\n\n\n# make a subplot with two image show side by side\nfig, axs = plt.subplots(1, 2, figsize=(10, 5))\naxs[0].imshow(label_before_consolidation[0], cmap=\"gray\")\naxs[1].imshow(label_after_consolidation[0], cmap=\"gray\")\nfor ax in axs:\n ax.axis(\"off\")\nplt.tight_layout()\nplt.show()\nlabel_0 = ngff_image.label.get(\"new_label\", path=\"0\") label_2 = ngff_image.label.get(\"new_label\", path=\"2\") label_before_consolidation = label_2.array[...] # Consolidate the label label_0.consolidate() label_after_consolidation = label_2.array[...] # make a subplot with two image show side by side fig, axs = plt.subplots(1, 2, figsize=(10, 5)) axs[0].imshow(label_before_consolidation[0], cmap=\"gray\") axs[1].imshow(label_after_consolidation[0], cmap=\"gray\") for ax in axs: ax.axis(\"off\") plt.tight_layout() plt.show() In\u00a0[10]: Copied!
import numpy as np\nimport pandas as pd\n\nprint(f\"List of feature table: {ngff_image.table.list(type='feature_table')}\")\n\n\nnuclei = ngff_image.label.get('nuclei')\n\n# Create a table with random features for each nuclei in each ROI\nlist_of_records = []\nfor roi in roi_table.list_rois:\n nuclei_in_roi = nuclei.get_data_from_roi(roi, mode='numpy')\n for nuclei_id in np.unique(nuclei_in_roi)[1:]:\n list_of_records.append(\n {\"label\": nuclei_id,\n \"feat1\": np.random.rand(),\n \"feat2\": np.random.rand(),\n \"ROI\": roi.infos.get(\"field_index\")}\n )\n\nfeat_df = pd.DataFrame.from_records(list_of_records)\n\n# Create a new feature table\nfeat_table = ngff_image.table.new(name='new_feature_table',\n region='../nuclei',\n table_type='feature_table',\n overwrite=True)\n\nprint(f\"New list of feature table: {ngff_image.table.list(type='feature_table')}\")\nfeat_table.table = feat_df\nfeat_table.write()\n\nfeat_table.table\nimport numpy as np import pandas as pd print(f\"List of feature table: {ngff_image.table.list(type='feature_table')}\") nuclei = ngff_image.label.get('nuclei') # Create a table with random features for each nuclei in each ROI list_of_records = [] for roi in roi_table.list_rois: nuclei_in_roi = nuclei.get_data_from_roi(roi, mode='numpy') for nuclei_id in np.unique(nuclei_in_roi)[1:]: list_of_records.append( {\"label\": nuclei_id, \"feat1\": np.random.rand(), \"feat2\": np.random.rand(), \"ROI\": roi.infos.get(\"field_index\")} ) feat_df = pd.DataFrame.from_records(list_of_records) # Create a new feature table feat_table = ngff_image.table.new(name='new_feature_table', region='../nuclei', table_type='feature_table', overwrite=True) print(f\"New list of feature table: {ngff_image.table.list(type='feature_table')}\") feat_table.table = feat_df feat_table.write() feat_table.table
List of feature table: ['regionprops_DAPI', 'nuclei_measurements_wf3', 'nuclei_measurements_wf4', 'nuclei_lamin_measurements_wf4']\n
\n---------------------------------------------------------------------------\nTypeError Traceback (most recent call last)\nCell In[10], line 24\n 21 feat_df = pd.DataFrame.from_records(list_of_records)\n 23 # Create a new feature table\n---> 24 feat_table = ngff_image.table.new(name='new_feature_table',\n 25 region='../nuclei',\n 26 table_type='feature_table',\n 27 overwrite=True)\n 29 print(f\"New list of feature table: {ngff_image.table.list(type='feature_table')}\")\n 30 feat_table.table = feat_df\n\nFile /opt/hostedtoolcache/Python/3.12.6/x64/lib/python3.12/site-packages/ngio/tables/tables_group.py:146, in TableGroup.new(self, name, table_type, overwrite, version, **type_specific_kwargs)\n 143 list_of_tables.remove(name)\n 145 table_impl = _find_table_impl(table_type=table_type, version=version)\n--> 146 new_table = table_impl._new(\n 147 parent_group=self._group,\n 148 name=name,\n 149 overwrite=overwrite,\n 150 **type_specific_kwargs,\n 151 )\n 153 self._group.attrs[\"tables\"] = [*list_of_tables, name]\n 155 return new_table\n\nTypeError: FeatureTableV1._new() got an unexpected keyword argument 'region'In\u00a0[\u00a0]: Copied!
\n"},{"location":"notebooks/image/#imageslabelstables","title":"Images/Labels/Tables\u00b6","text":"
In this notebook we will show how to use the Image
, Label
and Table
objects.
Images can be loaded from a NgffImage
object.
roi
objects from a roi_table
can be used to extract a region of interest from an image or a label.
Similarly to the .get_data()
we can use the .set_data()
(or set_data_roi
) method to write part of an image to disk.
When doing image analysis, we often need to create new labels or tables. The ngff_image
allows us to simply create new labels and tables.
Every time we modify a label or a image, we are modifying the on-disk data on one layer only. This means that if we have the image saved in multiple resolutions, we need to consolidate the changes to all resolutions. To do so, we can use the .consolidate()
method.
We can simply create a new table by create a new Table
object from a pandas dataframe. For a simple feature table the only reuiremnt is to have a integer column named label
that will be used to link the table to the objects in the image.
import matplotlib.pyplot as plt\n\nfrom ngio.core import NgffImage\n\nngff_image = NgffImage(\"../../data/20200812-CardiomyocyteDifferentiation14-Cycle1.zarr/B/03/0\")\nimport matplotlib.pyplot as plt from ngio.core import NgffImage ngff_image = NgffImage(\"../../data/20200812-CardiomyocyteDifferentiation14-Cycle1.zarr/B/03/0\") In\u00a0[2]: Copied!
mip_ngff = ngff_image.derive_new_image(\"../../data/20200812-CardiomyocyteDifferentiation14-Cycle1.zarr/B/03/0_mip\",\n name=\"MIP\",\n shape=(1, 1, 2160, 5120))\nmip_ngff = ngff_image.derive_new_image(\"../../data/20200812-CardiomyocyteDifferentiation14-Cycle1.zarr/B/03/0_mip\", name=\"MIP\", shape=(1, 1, 2160, 5120)) In\u00a0[3]: Copied!
# Get the source imag\nsource_image = ngff_image.get_image()\nprint(\"Source image loaded with shape:\", source_image.shape)\n\n# Get the MIP image\nmip_image = mip_ngff.get_image()\nprint(\"MIP image loaded with shape:\", mip_image.shape)\n\n# Get a ROI table\nroi_table = ngff_image.table.get(\"FOV_ROI_table\")\nprint(\"ROI table loaded with\", len(roi_table.list_rois), \"ROIs\")\n\n# For each ROI in the table\n# - get the data from the source image\n# - calculate the MIP\n# - set the data in the MIP image\nfor roi in roi_table.list_rois:\n print(f\" - Processing ROI {roi.infos.get(\"field_index\")}\")\n patch = source_image.get_data_from_roi(roi)\n mip_patch = patch.max(axis=1, keepdims=True)\n mip_image.set_data_from_roi(patch=mip_patch, roi=roi)\n \nprint(\"MIP image saved\")\n\nplt.figure(figsize=(5, 5))\nplt.title(\"Mip\")\nplt.imshow(mip_image.array[0, 0, :, :], cmap=\"gray\")\nplt.axis('off')\nplt.tight_layout()\nplt.show()\n# Get the source imag source_image = ngff_image.get_image() print(\"Source image loaded with shape:\", source_image.shape) # Get the MIP image mip_image = mip_ngff.get_image() print(\"MIP image loaded with shape:\", mip_image.shape) # Get a ROI table roi_table = ngff_image.table.get(\"FOV_ROI_table\") print(\"ROI table loaded with\", len(roi_table.list_rois), \"ROIs\") # For each ROI in the table # - get the data from the source image # - calculate the MIP # - set the data in the MIP image for roi in roi_table.list_rois: print(f\" - Processing ROI {roi.infos.get(\"field_index\")}\") patch = source_image.get_data_from_roi(roi) mip_patch = patch.max(axis=1, keepdims=True) mip_image.set_data_from_roi(patch=mip_patch, roi=roi) print(\"MIP image saved\") plt.figure(figsize=(5, 5)) plt.title(\"Mip\") plt.imshow(mip_image.array[0, 0, :, :], cmap=\"gray\") plt.axis('off') plt.tight_layout() plt.show()
Source image loaded with shape: (1, 2, 2160, 5120)\nMIP image loaded with shape: (1, 1, 2160, 5120)\nROI table loaded with 2 ROIs\n - Processing ROI FOV_1\n - Processing ROI FOV_2\nMIP image saved\nIn\u00a0[4]: Copied!
# Get the MIP image at a lower resolution\nmip_image_2 = mip_ngff.get_image(path=\"2\")\n\nimage_before_consolidation = mip_image_2.get_data(c=0, z=0)\n\n# Consolidate the pyramid\nmip_image.consolidate()\n\nimage_after_consolidation = mip_image_2.get_data(c=0, z=0)\n\nfig, axs = plt.subplots(2, 1, figsize=(10, 5))\naxs[0].set_title(\"Before consolidation\")\naxs[0].imshow(image_before_consolidation, cmap=\"gray\")\naxs[1].set_title(\"After consolidation\")\naxs[1].imshow(image_after_consolidation, cmap=\"gray\")\nfor ax in axs:\n ax.axis('off')\nplt.tight_layout()\nplt.show()\n# Get the MIP image at a lower resolution mip_image_2 = mip_ngff.get_image(path=\"2\") image_before_consolidation = mip_image_2.get_data(c=0, z=0) # Consolidate the pyramid mip_image.consolidate() image_after_consolidation = mip_image_2.get_data(c=0, z=0) fig, axs = plt.subplots(2, 1, figsize=(10, 5)) axs[0].set_title(\"Before consolidation\") axs[0].imshow(image_before_consolidation, cmap=\"gray\") axs[1].set_title(\"After consolidation\") axs[1].imshow(image_after_consolidation, cmap=\"gray\") for ax in axs: ax.axis('off') plt.tight_layout() plt.show() In\u00a0[5]: Copied!
mip_roi_table = mip_ngff.table.new(\"FOV_ROI_table\", overwrite=True)\n\nroi_list = []\nfor roi in roi_table.list_rois:\n print(f\" - Processing ROI {roi.infos.get(\"field_index\")}\")\n roi.z_length = 1 # In the MIP image, the z dimension is 1\n roi_list.append(roi)\n\nmip_roi_table.append_rois(roi_list)\nmip_roi_table.write()\n\nmip_roi_table.table\nmip_roi_table = mip_ngff.table.new(\"FOV_ROI_table\", overwrite=True) roi_list = [] for roi in roi_table.list_rois: print(f\" - Processing ROI {roi.infos.get(\"field_index\")}\") roi.z_length = 1 # In the MIP image, the z dimension is 1 roi_list.append(roi) mip_roi_table.append_rois(roi_list) mip_roi_table.write() mip_roi_table.table
- Processing ROI FOV_1\n - Processing ROI FOV_2\nOut[5]: x_micrometer y_micrometer z_micrometer len_x_micrometer len_y_micrometer len_z_micrometer FieldIndex FOV_1 0.0 0.0 0.0 416.0 351.0 1 FOV_2 416.0 0.0 0.0 416.0 351.0 1 In\u00a0[6]: Copied!
# Setup a simple segmentation function\n\nimport numpy as np\nfrom matplotlib.colors import ListedColormap\nfrom skimage.filters import threshold_otsu\nfrom skimage.measure import label\n\nrand_cmap = np.random.rand(1000, 3)\nrand_cmap[0] = 0\nrand_cmap = ListedColormap(rand_cmap)\n\n\ndef otsu_threshold_segmentation(image: np.ndarray, max_label:int) -> np.ndarray:\n \"\"\"Simple segmentation using Otsu thresholding.\"\"\"\n threshold = threshold_otsu(image)\n binary = image > threshold\n label_image = label(binary)\n label_image += max_label\n label_image = np.where(binary, label_image, 0)\n return label_image\n# Setup a simple segmentation function import numpy as np from matplotlib.colors import ListedColormap from skimage.filters import threshold_otsu from skimage.measure import label rand_cmap = np.random.rand(1000, 3) rand_cmap[0] = 0 rand_cmap = ListedColormap(rand_cmap) def otsu_threshold_segmentation(image: np.ndarray, max_label:int) -> np.ndarray: \"\"\"Simple segmentation using Otsu thresholding.\"\"\" threshold = threshold_otsu(image) binary = image > threshold label_image = label(binary) label_image += max_label label_image = np.where(binary, label_image, 0) return label_image In\u00a0[7]: Copied!
nuclei_image = mip_ngff.label.derive(name=\"nuclei\", overwrite=True)\nnuclei_image = mip_ngff.label.derive(name=\"nuclei\", overwrite=True) In\u00a0[8]: Copied!
# Get the source imag\nsource_image = mip_ngff.get_image()\nprint(\"Source image loaded with shape:\", source_image.shape)\n\n# Get a ROI table\nroi_table = mip_ngff.table.get(\"FOV_ROI_table\")\nprint(\"ROI table loaded with\", len(roi_table.list_rois), \"ROIs\")\n\n# Find the DAPI channel\ndapi_idx = source_image.get_channel_idx(label=\"DAPI\")\n\n# For each ROI in the table\n# - get the data from the source image\n# - calculate the Segmentation\n# - set the data in segmentation image\nmax_label = 0\nfor roi in roi_table.list_rois:\n print(f\" - Processing ROI {roi.infos.get(\"field_index\")}\")\n patch = source_image.get_data_from_roi(roi, c=dapi_idx)\n segmentation = otsu_threshold_segmentation(patch, max_label)\n\n # Add the max label of the previous segmentation to avoid overlapping labels\n max_label = segmentation.max()\n\n nuclei_image.set_data_from_roi(patch=segmentation, roi=roi)\n\n# Consolidate the segmentation image\nnuclei_image.consolidate()\n\nprint(\"Segmentation image saved\")\nfig, axs = plt.subplots(2, 1, figsize=(10, 5))\naxs[0].set_title(\"MIP\")\naxs[0].imshow(source_image.array[0, 0], cmap=\"gray\")\naxs[1].set_title(\"Nuclei segmentation\")\naxs[1].imshow(nuclei_image.array[0], cmap=rand_cmap, interpolation='nearest')\nfor ax in axs:\n ax.axis('off')\nplt.tight_layout()\nplt.show()\n# Get the source imag source_image = mip_ngff.get_image() print(\"Source image loaded with shape:\", source_image.shape) # Get a ROI table roi_table = mip_ngff.table.get(\"FOV_ROI_table\") print(\"ROI table loaded with\", len(roi_table.list_rois), \"ROIs\") # Find the DAPI channel dapi_idx = source_image.get_channel_idx(label=\"DAPI\") # For each ROI in the table # - get the data from the source image # - calculate the Segmentation # - set the data in segmentation image max_label = 0 for roi in roi_table.list_rois: print(f\" - Processing ROI {roi.infos.get(\"field_index\")}\") patch = source_image.get_data_from_roi(roi, c=dapi_idx) segmentation = otsu_threshold_segmentation(patch, max_label) # Add the max label of the previous segmentation to avoid overlapping labels max_label = segmentation.max() nuclei_image.set_data_from_roi(patch=segmentation, roi=roi) # Consolidate the segmentation image nuclei_image.consolidate() print(\"Segmentation image saved\") fig, axs = plt.subplots(2, 1, figsize=(10, 5)) axs[0].set_title(\"MIP\") axs[0].imshow(source_image.array[0, 0], cmap=\"gray\") axs[1].set_title(\"Nuclei segmentation\") axs[1].imshow(nuclei_image.array[0], cmap=rand_cmap, interpolation='nearest') for ax in axs: ax.axis('off') plt.tight_layout() plt.show()
Source image loaded with shape: (1, 1, 2160, 5120)\nROI table loaded with 2 ROIs\n - Processing ROI FOV_1\n - Processing ROI FOV_2\n
Segmentation image saved\n"},{"location":"notebooks/processing/#processing","title":"Processing\u00b6","text":"
In this notebook we will implement a couple of mock image analysis workflows using ngio
.
In this workflow we will read a volumetric image and create a maximum intensity projection (MIP) along the z-axis.
"},{"location":"notebooks/processing/#step-1-create-a-ngff-image","title":"step 1: Create a ngff image\u00b6","text":"For this example we will use the following publicly available image
"},{"location":"notebooks/processing/#step-2-create-a-new-ngff-image-to-store-the-mip","title":"step 2: Create a new ngff image to store the MIP\u00b6","text":""},{"location":"notebooks/processing/#step-3-run-the-workflow","title":"step 3: Run the workflow\u00b6","text":"For each roi in the image, create a MIP and store it in the new image
"},{"location":"notebooks/processing/#step-4-consolidate-the-results-important","title":"step 4: Consolidate the results (!!! Important)\u00b6","text":"In this we wrote the mip image to a single level of the image pyramid. To truly consolidate the results we would need to write the mip to all levels of the image pyramid. We can do this by calling the .consolidate()
method on the image.
As a final step we will create a new ROI table that contains the MIPs as ROIs. Where we correct the z
bounds of the ROIs to reflect the MIP.
Now we can use the MIP image to segment the image using a simple thresholding algorithm.
"},{"location":"notebooks/processing/#step-1-derive-a-new-label-image-from-the-mip-image","title":"step 1: Derive a new label image from the MIP image\u00b6","text":""},{"location":"notebooks/processing/#step-2-run-the-workflow","title":"step 2: Run the workflow\u00b6","text":""}]} \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml index 93cc529..ef78439 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -2,26 +2,26 @@