From 2c8b1a60962aab9a90f07033955732fb12bd5dcd Mon Sep 17 00:00:00 2001 From: Alan O'Callaghan Date: Thu, 17 Oct 2024 12:14:04 +0100 Subject: [PATCH 1/5] Bioio --- notebooks/opening_images.ipynb | 28 ++++++++++++++-------------- notebooks/working_with_objects.ipynb | 2 +- qubalab/images/aicsimageio_server.py | 15 ++++++++------- setup.cfg | 2 +- 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/notebooks/opening_images.ipynb b/notebooks/opening_images.ipynb index 55eccce..940fb5c 100644 --- a/notebooks/opening_images.ipynb +++ b/notebooks/opening_images.ipynb @@ -7,17 +7,17 @@ "source": [ "# Opening images\n", "\n", - "This notebook will show how to access metadata and pixel values of images with QuBaLab.\n", + "This notebook will show you how to access metadata and pixel values of images with QuBaLab.\n", "\n", - "A few classes and functions of the QuBaLab package will be presented throughout this notebook. To have more details on them, you can go to the documentation on https://qupath.github.io/qubalab/ and type a class/function name in the search bar. You will then see details on parameters functions take.\n", + "A few classes and functions of the QuBaLab package will be presented in this notebook. To view more details on them, you can go to the documentation at https://qupath.github.io/qubalab-docs/ and type a class/function name in the search bar. You will then see details on the arguments that functions accept.\n", "\n", - "QuBaLab uses the `ImageServer` class to open an image and get its metadata and pixel values. `ImageServer` is an abstract class, so it cannot be directly used. Instead, you have to choose an implementation based on the type of image you want to open:\n", + "QuBaLab uses the `ImageServer` class to open an image and get its metadata and pixel values. `ImageServer` is an abstract class, so it cannot be directly created. Instead, you have to choose an implementation based on the type of image you want to open:\n", "\n", - "* If you need to open an RGB pyramidal image, you can use the `OpenSlideServer`. This class internally uses [OpenSlide](https://openslide.org/): it can read all resolutions of a large pyramidal image but is only suitable for RGB images.\n", - "* If you need to open any kind of microscopy image, you can use the `AICSImageIoServer`. This class internally uses [AICSImageIO](https://github.com/AllenCellModeling/aicsimageio) which is suited for a lot of formats. However, this library does not properly support pyramids, so you might only get the full resolution image when opening a pyramidal image.\n", - "* If your image has an embedded ICC profile, or if you want to apply a custom ICC Profile to an image, you can use the `IccProfileServer`. This takes another `ImageServer` as a parameter and change pixels values. You can find more information about ICC profiles on [this link](http://www.andrewjanowczyk.com/application-of-icc-profiles-to-digital-pathology-images/).\n", - "* If you want to represent objects such as annotations or detections on an image, you can use the `LabeledImageServer`. Pixel values are labels corresponding to image features (such as annotations) present on an image. See the *working_with_objects.ipynb* notebook for more information.\n", - "* If you want to access an image opened in QuPath, you can use the `QuPathServer`. See the *communicating_with_qupath.ipynb* notebook for more information.\n", + "* If you need to open an RGB pyramidal image, you can use an `OpenSlideServer`. This class internally uses [OpenSlide](https://openslide.org/): it can read all resolutions of a large pyramidal image but is only suitable for RGB images.\n", + "* If you need to open any kind of microscopy image, you can use an `AICSImageIoServer`. This class internally uses [AICSImageIO](https://github.com/AllenCellModeling/aicsimageio) which is suited for a lot of formats. However, this library does not properly support pyramids, so you might only get the full resolution image when opening a pyramidal image.\n", + "* If your image has an embedded ICC profile, or if you want to apply a custom ICC Profile to an image, you can use an `IccProfileServer`. This takes another `ImageServer` as a parameter and changes pixels values based on the transforms defined in the ICC profile. You can find more information about ICC profiles on [this link](http://www.andrewjanowczyk.com/application-of-icc-profiles-to-digital-pathology-images/).\n", + "* If you want to represent objects such as annotations or detections on an image, you can use a `LabeledImageServer`. Pixel values are labels corresponding to image features (such as annotations) present on an image. See the *working_with_objects.ipynb* notebook for more information.\n", + "* If you want to access an image opened in QuPath, you can use a `QuPathServer`. See the *communicating_with_qupath.ipynb* notebook for more information.\n", "\n", "All these `ImageServer` are used in the same way, only their creation differ. This notebook will show how to create and use these servers." ] @@ -27,11 +27,11 @@ "id": "7fb5756f-d27e-4720-bbcd-f430b7562351", "metadata": {}, "source": [ - "## ImageServers creation\n", + "## ImageServer creation\n", "\n", "To create an `ImageServer`, you have to use one of the implementations presented above.\n", "\n", - "This notebook will download several sample images of large size. You can change the line below to define a folder where images will be stored once downloaded (or leave it blank not to use any cache)." + "This notebook will download several large sample images. You can change the line below to define a folder where images will be stored once downloaded (or leave it blank not to use any cache)." ] }, { @@ -190,7 +190,7 @@ "source": [ "### QuPathServer\n", "\n", - "A `QuPathServer` allows to read an image opened in QuPath\n", + "A `QuPathServer` allows you to read an image opened in QuPath\n", "\n", "We will not detail the creation of this server here. Take a look at the *communicating_with_qupath.ipynb* notebook for more information. However, all functions presented below are also valid for a `QuPathServer`." ] @@ -200,9 +200,9 @@ "id": "1e0a79f3-9467-4a52-ad80-82e74721c9cc", "metadata": {}, "source": [ - "## ImageServers usage\n", + "## ImageServer usage\n", "\n", - "To access metadata and pixel values of an `ImageServer`, you can use the functions presented below.\n", + "To access metadata and pixel values from an `ImageServer`, you can use the functions presented below.\n", "\n", "These functions are available for any implementation of `ImageServer`. We will use the `OpenSlideServer` created above but they are also valid for any other `ImageServer`." ] @@ -224,7 +224,7 @@ "source": [ "### Image metadata\n", "\n", - "Image metadata is access with the `ImageServer.metadata` property." + "Image metadata is accessed with the `ImageServer.metadata` property." ] }, { diff --git a/notebooks/working_with_objects.ipynb b/notebooks/working_with_objects.ipynb index 6bb0d05..3f5ec8c 100644 --- a/notebooks/working_with_objects.ipynb +++ b/notebooks/working_with_objects.ipynb @@ -7,7 +7,7 @@ "source": [ "# Working with objects\n", "\n", - "This notebook will show how to exchange objects (e.g. annotations, detections) between QuPath and Python.\n", + "This notebook will show you how to exchange objects (e.g. annotations, detections) between QuPath and Python.\n", "\n", "As we will communicate with QuPath, it is recommended to go through the *communicating_with_qupath.ipynb* notebook first. Also, as we will work with images, it is recommended to go through the *opening_images.ipynb* notebook first.\n", "\n", diff --git a/qubalab/images/aicsimageio_server.py b/qubalab/images/aicsimageio_server.py index fe095b7..146070f 100644 --- a/qubalab/images/aicsimageio_server.py +++ b/qubalab/images/aicsimageio_server.py @@ -2,7 +2,8 @@ import dask.array as da import math from pathlib import Path -from aicsimageio import AICSImage +# from aicsimageio import AICSImage +from bioio import BioImage from dataclasses import astuple from .image_server import ImageServer from .metadata.image_metadata import ImageMetadata @@ -35,7 +36,7 @@ def __init__(self, path: str, scene: int = 0, detect_resolutions=True, aics_kwar """ super().__init__(**kwargs) self._path = path - self._reader = AICSImage(path, dask_tiles=True, **aics_kwargs) + self._reader = BioImage(path, dask_tiles=True, **aics_kwargs) self._scene = scene self._detect_resolutions = detect_resolutions @@ -73,7 +74,7 @@ def _read_block(self, level: int, region: Region2D) -> np.ndarray: return self._reader.get_image_dask_data(axes)[t, z, :, y:y + height, x:x + width].compute() @staticmethod - def _get_shapes(reader: AICSImage, scene: int) -> tuple[ImageShape, ...]: + def _get_shapes(reader: BioImage, scene: int) -> tuple[ImageShape, ...]: shapes = [] for scene in reader.scenes[scene:]: shape = AICSImageIoServer._get_scene_shape(reader, scene) @@ -85,7 +86,7 @@ def _get_shapes(reader: AICSImage, scene: int) -> tuple[ImageShape, ...]: return tuple(shapes) @staticmethod - def _get_scene_shape(reader: AICSImage, scene: int) -> ImageShape: + def _get_scene_shape(reader: BioImage, scene: int) -> ImageShape: reader.set_scene(scene) return ImageShape( @@ -97,12 +98,12 @@ def _get_scene_shape(reader: AICSImage, scene: int) -> ImageShape: ) @staticmethod - def _get_pixel_calibration(reader: AICSImage, scene: int) -> PixelCalibration: + def _get_pixel_calibration(reader: BioImage, scene: int) -> PixelCalibration: reader.set_scene(scene) sizes = reader.physical_pixel_sizes if sizes.X or sizes.Y or sizes.Z: - # The AICSImage library does not currently handle unit attachment, so the pixel unit is returned + # The bioio library does not currently handle unit attachment, so the pixel unit is returned return PixelCalibration( PixelLength(sizes.X) if sizes.X is not None else PixelLength(), PixelLength(sizes.Y) if sizes.Y is not None else PixelLength(), @@ -112,7 +113,7 @@ def _get_pixel_calibration(reader: AICSImage, scene: int) -> PixelCalibration: return PixelCalibration() @staticmethod - def _is_rgb(reader: AICSImage, scene: int) -> bool: + def _is_rgb(reader: BioImage, scene: int) -> bool: reader.set_scene(scene) return ('S' in reader.dims.order and reader.dims.S in [3, 4]) or (reader.dtype == np.uint8 and reader.dims.C == 3) diff --git a/setup.cfg b/setup.cfg index 8fca0fd..4002b88 100644 --- a/setup.cfg +++ b/setup.cfg @@ -18,7 +18,7 @@ packages = python_requires = >= 3.10 install_requires = tiffslide ~= 2.1.2 - aicsimageio ~= 4.11.0 + bioio ~= 1.0.0 dask-image ~= 2024.5.3 py4j ~= 0.10.9.7 geojson ~= 3.1.0 From 77b71bdadc7dbf73c68305abe7cce3e5592dd1f2 Mon Sep 17 00:00:00 2001 From: Alan O'Callaghan Date: Tue, 29 Oct 2024 11:55:38 +0000 Subject: [PATCH 2/5] Rename AICS --- notebooks/opening_images.ipynb | 10 +- ...{aicsimageio_server.py => bioio_server.py} | 25 ++- ...imageio_server.py => test_bioio_server.py} | 162 +++++++++--------- 3 files changed, 98 insertions(+), 99 deletions(-) rename qubalab/images/{aicsimageio_server.py => bioio_server.py} (82%) rename tests/images/{test_aicsimageio_server.py => test_bioio_server.py} (53%) diff --git a/notebooks/opening_images.ipynb b/notebooks/opening_images.ipynb index ef7159e..9063b7f 100644 --- a/notebooks/opening_images.ipynb +++ b/notebooks/opening_images.ipynb @@ -14,7 +14,7 @@ "QuBaLab uses the `ImageServer` class to open an image and get its metadata and pixel values. `ImageServer` is an abstract class, so it cannot be directly created. Instead, you have to choose an implementation based on the type of image you want to open:\n", "\n", "* If you need to open an RGB pyramidal image, you can use an `OpenSlideServer`. This class internally uses [OpenSlide](https://openslide.org/): it can read all resolutions of a large pyramidal image but is only suitable for RGB images.\n", - "* If you need to open any kind of microscopy image, you can use an `AICSImageIoServer`. This class internally uses [AICSImageIO](https://github.com/AllenCellModeling/aicsimageio) which is suited for a lot of formats. However, this library does not properly support pyramids, so you might only get the full resolution image when opening a pyramidal image.\n", + "* If you need to open any kind of microscopy image, you can use an `BioIOServer`. This class internally uses [BioIO](https://github.com/bioio-devs/bioio) which is suited for a lot of formats. However, this library does not properly support pyramids, so you might only get the full resolution image when opening a pyramidal image.\n", "* If your image has an embedded ICC profile, or if you want to apply a custom ICC Profile to an image, you can use an `IccProfileServer`. This takes another `ImageServer` as a parameter and changes pixels values based on the transforms defined in the ICC profile. You can find more information about ICC profiles on [this link](http://www.andrewjanowczyk.com/application-of-icc-profiles-to-digital-pathology-images/).\n", "* If you want to represent objects such as annotations or detections on an image, you can use a `LabeledImageServer`. Pixel values are labels corresponding to image features (such as annotations) present on an image. See the *working_with_objects.ipynb* notebook for more information.\n", "* If you want to access an image opened in QuPath, you can use a `QuPathServer`. See the *communicating_with_qupath.ipynb* notebook for more information.\n", @@ -117,7 +117,7 @@ "id": "624f004b-9383-4053-921e-d8d691749b00", "metadata": {}, "source": [ - "### AICSImageIoServer\n", + "### BioIOServer\n", "\n", "We will use the **Patient_test_1.ome.tiff** image (CC0 license) because it's a relatively small fluorescence image." ] @@ -140,12 +140,12 @@ "metadata": {}, "outputs": [], "source": [ - "from qubalab.images.aicsimageio_server import AICSImageIoServer\n", + "from qubalab.images.bioio_server import BioIOServer\n", "\n", "\n", "# Create the ImageServer from the image path. This will read the image metadata but not the pixel values yet.\n", "# This function has optional parameters you can find in the documentation\n", - "aicsimageio_server = AICSImageIoServer(fluoro_path)" + "bioio_server = BioIOServer(fluoro_path)" ] }, { @@ -214,7 +214,7 @@ "metadata": {}, "outputs": [], "source": [ - "server = openslide_server # you can change it to aicsimageio_server or icc_profile_server" + "server = openslide_server # you can change it to bioio_server or icc_profile_server" ] }, { diff --git a/qubalab/images/aicsimageio_server.py b/qubalab/images/bioio_server.py similarity index 82% rename from qubalab/images/aicsimageio_server.py rename to qubalab/images/bioio_server.py index 146070f..b19259c 100644 --- a/qubalab/images/aicsimageio_server.py +++ b/qubalab/images/bioio_server.py @@ -2,7 +2,6 @@ import dask.array as da import math from pathlib import Path -# from aicsimageio import AICSImage from bioio import BioImage from dataclasses import astuple from .image_server import ImageServer @@ -12,31 +11,31 @@ from .region_2d import Region2D -class AICSImageIoServer(ImageServer): +class BioIOServer(ImageServer): """ - An ImageServer using AICSImageIO (https://github.com/AllenCellModeling/aicsimageio). + An ImageServer using BioIO (https://github.com/AllenCellModeling/BioIO). - What this actually supports will depend upon how AICSImageIO is installed. + What this actually supports will depend upon how BioIO is installed. For example, it may provide Bio-Formats or CZI support... or it may not. - Note that the AICSImage library does not currently handle unit attachment, so the pixel unit + Note that the BioIo library does not currently handle unit attachment, so the pixel unit given by this server will always be 'pixels'. - Note that the AICSImage library does not properly support pyramids, so you might only get the full + Note that the BioIO library does not properly support pyramids, so you might only get the full resolution image when opening a pyramidal image. """ - def __init__(self, path: str, scene: int = 0, detect_resolutions=True, aics_kwargs: dict[str, any] = {}, **kwargs): + def __init__(self, path: str, scene: int = 0, detect_resolutions=True, bioio_kwargs: dict[str, any] = {}, **kwargs): """ :param path: the local path to the image to open - :param scene: AICSImageIO divides images into scene. This parameter specifies which scene to consider + :param scene: BioIO divides images into scene. This parameter specifies which scene to consider :param detect_resolutions: whether to look at all resolutions of the image (instead of just the full resolution) - :param aics_kwargs: any specific keyword arguments to pass down to the fsspec created filesystem handled by the AICSImageIO reader + :param bioio_kwargs: any specific keyword arguments to pass down to the fsspec created filesystem handled by the BioIO reader :param resize_method: the resampling method to use when resizing the image for downsampling. Bicubic by default """ super().__init__(**kwargs) self._path = path - self._reader = BioImage(path, dask_tiles=True, **aics_kwargs) + self._reader = BioImage(path, dask_tiles=True, **bioio_kwargs) self._scene = scene self._detect_resolutions = detect_resolutions @@ -77,9 +76,9 @@ def _read_block(self, level: int, region: Region2D) -> np.ndarray: def _get_shapes(reader: BioImage, scene: int) -> tuple[ImageShape, ...]: shapes = [] for scene in reader.scenes[scene:]: - shape = AICSImageIoServer._get_scene_shape(reader, scene) + shape = BioIOServer._get_scene_shape(reader, scene) - if len(shapes) == 0 or AICSImageIoServer._is_lower_resolution(shapes[-1], shape): + if len(shapes) == 0 or BioIOServer._is_lower_resolution(shapes[-1], shape): shapes.append(shape) else: break @@ -122,7 +121,7 @@ def _is_lower_resolution(base_shape: ImageShape, series_shape: ImageShape) -> bo """ Calculate if the series shape is a lower resolution than the base shape. - This involves a bit of guesswork, but it's needed for so long as AICSImageIO doesn't properly support pyramids. + This involves a bit of guesswork, but it's needed for so long as BioIO doesn't properly support pyramids. """ if base_shape.z == series_shape.z and \ base_shape.t == series_shape.t and \ diff --git a/tests/images/test_aicsimageio_server.py b/tests/images/test_bioio_server.py similarity index 53% rename from tests/images/test_aicsimageio_server.py rename to tests/images/test_bioio_server.py index 70d28da..b6f6ab3 100644 --- a/tests/images/test_aicsimageio_server.py +++ b/tests/images/test_bioio_server.py @@ -1,78 +1,78 @@ import numpy as np -from qubalab.images.aicsimageio_server import AICSImageIoServer +from qubalab.images.bioio_server import BioIOServer from qubalab.images.region_2d import Region2D from qubalab.images.metadata.pixel_calibration import PixelCalibration, PixelLength from ..res import multi_resolution_uint8_3channels, single_resolution_float_5d, single_resolution_rgb_image def test_uint8_3channels_image_name(): - aicsimageio_server = AICSImageIoServer(multi_resolution_uint8_3channels.get_path()) + bioio_server = BioIOServer(multi_resolution_uint8_3channels.get_path()) - name = aicsimageio_server.metadata.name + name = bioio_server.metadata.name assert name == multi_resolution_uint8_3channels.get_name() - aicsimageio_server.close() + bioio_server.close() def test_uint8_3channels_image_shapes(): - aicsimageio_server = AICSImageIoServer(multi_resolution_uint8_3channels.get_path()) + bioio_server = BioIOServer(multi_resolution_uint8_3channels.get_path()) - shapes = aicsimageio_server.metadata.shapes + shapes = bioio_server.metadata.shapes - assert shapes == (multi_resolution_uint8_3channels.get_shapes()[0], ) # The AICSImage library does not properly support pyramids + assert shapes == (multi_resolution_uint8_3channels.get_shapes()[0], ) # The BioIO library does not properly support pyramids - aicsimageio_server.close() + bioio_server.close() def test_uint8_3channels_image_pixel_calibration(): - aicsimageio_server = AICSImageIoServer(multi_resolution_uint8_3channels.get_path()) + bioio_server = BioIOServer(multi_resolution_uint8_3channels.get_path()) - pixel_calibration = aicsimageio_server.metadata.pixel_calibration + pixel_calibration = bioio_server.metadata.pixel_calibration assert pixel_calibration == PixelCalibration( - PixelLength(multi_resolution_uint8_3channels.get_pixel_size_x_y_in_micrometers()), # The AICSImage library does not currently handle unit attachment + PixelLength(multi_resolution_uint8_3channels.get_pixel_size_x_y_in_micrometers()), # The BioIO library does not currently handle unit attachment PixelLength(multi_resolution_uint8_3channels.get_pixel_size_x_y_in_micrometers()) ) - aicsimageio_server.close() + bioio_server.close() def test_uint8_3channels_image_is_rgb(): - aicsimageio_server = AICSImageIoServer(multi_resolution_uint8_3channels.get_path()) + bioio_server = BioIOServer(multi_resolution_uint8_3channels.get_path()) - is_rgb = aicsimageio_server.metadata.is_rgb + is_rgb = bioio_server.metadata.is_rgb assert is_rgb - aicsimageio_server.close() + bioio_server.close() def test_uint8_3channels_image_dtype(): - aicsimageio_server = AICSImageIoServer(multi_resolution_uint8_3channels.get_path()) + bioio_server = BioIOServer(multi_resolution_uint8_3channels.get_path()) - dtype = aicsimageio_server.metadata.dtype + dtype = bioio_server.metadata.dtype assert dtype == multi_resolution_uint8_3channels.get_dtype() - aicsimageio_server.close() + bioio_server.close() def test_uint8_3channels_image_downsamples(): - aicsimageio_server = AICSImageIoServer(multi_resolution_uint8_3channels.get_path()) + bioio_server = BioIOServer(multi_resolution_uint8_3channels.get_path()) - downsamples = aicsimageio_server.metadata.downsamples + downsamples = bioio_server.metadata.downsamples - assert downsamples == (multi_resolution_uint8_3channels.get_downsamples()[0], ) # The AICSImage library does not properly support pyramids + assert downsamples == (multi_resolution_uint8_3channels.get_downsamples()[0], ) # The BioIO library does not properly support pyramids - aicsimageio_server.close() + bioio_server.close() def test_read_uint8_3channels_image(): level = 0 full_resolution = multi_resolution_uint8_3channels.get_shapes()[level] downsample = multi_resolution_uint8_3channels.get_downsamples()[level] - aicsimageio_server = AICSImageIoServer(multi_resolution_uint8_3channels.get_path()) + bioio_server = BioIOServer(multi_resolution_uint8_3channels.get_path()) expected_pixels = np.array( [[[multi_resolution_uint8_3channels.get_pixel_value(downsample, x, y, c) for x in range(full_resolution.x)] @@ -81,21 +81,21 @@ def test_read_uint8_3channels_image(): multi_resolution_uint8_3channels.get_dtype() ) - image = aicsimageio_server.read_region( + image = bioio_server.read_region( downsample, - Region2D(width=aicsimageio_server.metadata.width, height=aicsimageio_server.metadata.height) + Region2D(width=bioio_server.metadata.width, height=bioio_server.metadata.height) ) np.testing.assert_array_equal(image, expected_pixels) - aicsimageio_server.close() + bioio_server.close() def test_read_uint8_3channels_image_with_dask(): level = 0 full_resolution = multi_resolution_uint8_3channels.get_shapes()[level] downsample = multi_resolution_uint8_3channels.get_downsamples()[level] - aicsimageio_server = AICSImageIoServer(multi_resolution_uint8_3channels.get_path()) + bioio_server = BioIOServer(multi_resolution_uint8_3channels.get_path()) expected_pixels = np.array( [[[multi_resolution_uint8_3channels.get_pixel_value(downsample, x, y, c) for x in range(full_resolution.x)] @@ -104,81 +104,81 @@ def test_read_uint8_3channels_image_with_dask(): multi_resolution_uint8_3channels.get_dtype() ) - image = aicsimageio_server.level_to_dask(level).compute() + image = bioio_server.level_to_dask(level).compute() np.testing.assert_array_equal(image, expected_pixels) - aicsimageio_server.close() + bioio_server.close() def test_float_5d_image_name(): - aicsimageio_server = AICSImageIoServer(single_resolution_float_5d.get_path()) + bioio_server = BioIOServer(single_resolution_float_5d.get_path()) - name = aicsimageio_server.metadata.name + name = bioio_server.metadata.name assert name == single_resolution_float_5d.get_name() - aicsimageio_server.close() + bioio_server.close() def test_float_5d_image_shapes(): - aicsimageio_server = AICSImageIoServer(single_resolution_float_5d.get_path()) + bioio_server = BioIOServer(single_resolution_float_5d.get_path()) - shapes = aicsimageio_server.metadata.shapes + shapes = bioio_server.metadata.shapes assert shapes == single_resolution_float_5d.get_shapes() - aicsimageio_server.close() + bioio_server.close() def test_float_5d_image_pixel_calibration(): - aicsimageio_server = AICSImageIoServer(single_resolution_float_5d.get_path()) + bioio_server = BioIOServer(single_resolution_float_5d.get_path()) - pixel_calibration = aicsimageio_server.metadata.pixel_calibration + pixel_calibration = bioio_server.metadata.pixel_calibration assert pixel_calibration == PixelCalibration( - PixelLength(single_resolution_float_5d.get_pixel_size_x_y_in_micrometers()), # The AICSImage library does not currently handle unit attachment + PixelLength(single_resolution_float_5d.get_pixel_size_x_y_in_micrometers()), # The BioIO library does not currently handle unit attachment PixelLength(single_resolution_float_5d.get_pixel_size_x_y_in_micrometers()) ) - aicsimageio_server.close() + bioio_server.close() def test_float_5d_image_is_not_rgb(): - aicsimageio_server = AICSImageIoServer(single_resolution_float_5d.get_path()) + bioio_server = BioIOServer(single_resolution_float_5d.get_path()) - is_rgb = aicsimageio_server.metadata.is_rgb + is_rgb = bioio_server.metadata.is_rgb assert not(is_rgb) - aicsimageio_server.close() + bioio_server.close() def test_float_5d_image_dtype(): - aicsimageio_server = AICSImageIoServer(single_resolution_float_5d.get_path()) + bioio_server = BioIOServer(single_resolution_float_5d.get_path()) - dtype = aicsimageio_server.metadata.dtype + dtype = bioio_server.metadata.dtype assert dtype == single_resolution_float_5d.get_dtype() - aicsimageio_server.close() + bioio_server.close() def test_float_5d_image_downsamples(): - aicsimageio_server = AICSImageIoServer(single_resolution_float_5d.get_path()) + bioio_server = BioIOServer(single_resolution_float_5d.get_path()) - downsamples = aicsimageio_server.metadata.downsamples + downsamples = bioio_server.metadata.downsamples assert downsamples == single_resolution_float_5d.get_downsamples() - aicsimageio_server.close() + bioio_server.close() def test_read_float_5d_image(): full_resolution = single_resolution_float_5d.get_shapes()[0] z = int(full_resolution.z / 2) t = int(full_resolution.t / 2) - aicsimageio_server = AICSImageIoServer(single_resolution_float_5d.get_path()) + bioio_server = BioIOServer(single_resolution_float_5d.get_path()) expected_pixels = np.array( [[[single_resolution_float_5d.get_pixel_value(x, y, c, z, t) for x in range(full_resolution.x)] @@ -187,19 +187,19 @@ def test_read_float_5d_image(): single_resolution_float_5d.get_dtype() ) - image = aicsimageio_server.read_region( + image = bioio_server.read_region( 1, - Region2D(width=aicsimageio_server.metadata.width, height=aicsimageio_server.metadata.height, z=z, t=t) + Region2D(width=bioio_server.metadata.width, height=bioio_server.metadata.height, z=z, t=t) ) np.testing.assert_array_equal(image, expected_pixels) - aicsimageio_server.close() + bioio_server.close() def test_read_float_5d_image_with_dask(): full_resolution = single_resolution_float_5d.get_shapes()[0] - aicsimageio_server = AICSImageIoServer(single_resolution_float_5d.get_path()) + bioio_server = BioIOServer(single_resolution_float_5d.get_path()) expected_pixels = np.array( [[[[[single_resolution_float_5d.get_pixel_value(x, y, c, z, t) for x in range(full_resolution.x)] @@ -210,79 +210,79 @@ def test_read_float_5d_image_with_dask(): single_resolution_float_5d.get_dtype() ) - image = aicsimageio_server.level_to_dask(0).compute() + image = bioio_server.level_to_dask(0).compute() np.testing.assert_array_equal(image, expected_pixels) - aicsimageio_server.close() + bioio_server.close() def test_rgb_image_name(): - aicsimageio_server = AICSImageIoServer(single_resolution_rgb_image.get_path()) + bioio_server = BioIOServer(single_resolution_rgb_image.get_path()) - name = aicsimageio_server.metadata.name + name = bioio_server.metadata.name assert name == single_resolution_rgb_image.get_name() - aicsimageio_server.close() + bioio_server.close() def test_rgb_image_shapes(): - aicsimageio_server = AICSImageIoServer(single_resolution_rgb_image.get_path()) + bioio_server = BioIOServer(single_resolution_rgb_image.get_path()) - shapes = aicsimageio_server.metadata.shapes + shapes = bioio_server.metadata.shapes assert shapes == single_resolution_rgb_image.get_shapes() - aicsimageio_server.close() + bioio_server.close() def test_rgb_image_pixel_calibration(): - aicsimageio_server = AICSImageIoServer(single_resolution_rgb_image.get_path()) + bioio_server = BioIOServer(single_resolution_rgb_image.get_path()) - pixel_calibration = aicsimageio_server.metadata.pixel_calibration + pixel_calibration = bioio_server.metadata.pixel_calibration assert pixel_calibration == PixelCalibration( - PixelLength(single_resolution_rgb_image.get_pixel_size_x_y_in_micrometers()), # The AICSImage library does not currently handle unit attachment + PixelLength(single_resolution_rgb_image.get_pixel_size_x_y_in_micrometers()), # The BioIO library does not currently handle unit attachment PixelLength(single_resolution_rgb_image.get_pixel_size_x_y_in_micrometers()) ) - aicsimageio_server.close() + bioio_server.close() def test_rgb_image_is_rgb(): - aicsimageio_server = AICSImageIoServer(single_resolution_rgb_image.get_path()) + bioio_server = BioIOServer(single_resolution_rgb_image.get_path()) - is_rgb = aicsimageio_server.metadata.is_rgb + is_rgb = bioio_server.metadata.is_rgb assert is_rgb - aicsimageio_server.close() + bioio_server.close() def test_rgb_image_dtype(): - aicsimageio_server = AICSImageIoServer(single_resolution_rgb_image.get_path()) + bioio_server = BioIOServer(single_resolution_rgb_image.get_path()) - dtype = aicsimageio_server.metadata.dtype + dtype = bioio_server.metadata.dtype assert dtype == single_resolution_rgb_image.get_dtype() - aicsimageio_server.close() + bioio_server.close() def test_rgb_image_downsamples(): - aicsimageio_server = AICSImageIoServer(single_resolution_rgb_image.get_path()) + bioio_server = BioIOServer(single_resolution_rgb_image.get_path()) - downsamples = aicsimageio_server.metadata.downsamples + downsamples = bioio_server.metadata.downsamples assert downsamples == single_resolution_rgb_image.get_downsamples() - aicsimageio_server.close() + bioio_server.close() def test_read_rgb_image(): full_resolution = single_resolution_rgb_image.get_shapes()[0] - aicsimageio_server = AICSImageIoServer(single_resolution_rgb_image.get_path()) + bioio_server = BioIOServer(single_resolution_rgb_image.get_path()) expected_pixels = np.array( [[[single_resolution_rgb_image.get_pixel_value(x, y, c) for x in range(full_resolution.x)] @@ -291,19 +291,19 @@ def test_read_rgb_image(): single_resolution_rgb_image.get_dtype() ) - image = aicsimageio_server.read_region( + image = bioio_server.read_region( 1, - Region2D(width=aicsimageio_server.metadata.width, height=aicsimageio_server.metadata.height) + Region2D(width=bioio_server.metadata.width, height=bioio_server.metadata.height) ) np.testing.assert_array_equal(image, expected_pixels) - aicsimageio_server.close() + bioio_server.close() def test_read_rgb_image_with_dask(): full_resolution = single_resolution_rgb_image.get_shapes()[0] - aicsimageio_server = AICSImageIoServer(single_resolution_rgb_image.get_path()) + bioio_server = BioIOServer(single_resolution_rgb_image.get_path()) expected_pixels = np.array( [[[single_resolution_rgb_image.get_pixel_value(x, y, c) for x in range(full_resolution.x)] @@ -312,8 +312,8 @@ def test_read_rgb_image_with_dask(): single_resolution_rgb_image.get_dtype() ) - image = aicsimageio_server.level_to_dask(0).compute() + image = bioio_server.level_to_dask(0).compute() np.testing.assert_array_equal(image, expected_pixels) - aicsimageio_server.close() + bioio_server.close() From 06e56c62de43342a25b2add625fb39daa003ce87 Mon Sep 17 00:00:00 2001 From: Alan O'Callaghan Date: Thu, 31 Oct 2024 11:54:49 +0000 Subject: [PATCH 3/5] Add zarr test --- .gitignore | 1 + qubalab/images/bioio_server.py | 2 +- tests/images/test_bioio_server.py | 112 ++++++++++++++++++- tests/res/single_resolution_float_5d_zarr.py | 60 ++++++++++ 4 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 tests/res/single_resolution_float_5d_zarr.py diff --git a/.gitignore b/.gitignore index bb687b0..d226ef3 100644 --- a/.gitignore +++ b/.gitignore @@ -137,3 +137,4 @@ dmypy.json # Generated test images *.tif +*.zarr diff --git a/qubalab/images/bioio_server.py b/qubalab/images/bioio_server.py index b19259c..6d485ad 100644 --- a/qubalab/images/bioio_server.py +++ b/qubalab/images/bioio_server.py @@ -35,7 +35,7 @@ def __init__(self, path: str, scene: int = 0, detect_resolutions=True, bioio_kwa """ super().__init__(**kwargs) self._path = path - self._reader = BioImage(path, dask_tiles=True, **bioio_kwargs) + self._reader = BioImage(path, **bioio_kwargs) self._scene = scene self._detect_resolutions = detect_resolutions diff --git a/tests/images/test_bioio_server.py b/tests/images/test_bioio_server.py index b6f6ab3..25a735e 100644 --- a/tests/images/test_bioio_server.py +++ b/tests/images/test_bioio_server.py @@ -2,7 +2,7 @@ from qubalab.images.bioio_server import BioIOServer from qubalab.images.region_2d import Region2D from qubalab.images.metadata.pixel_calibration import PixelCalibration, PixelLength -from ..res import multi_resolution_uint8_3channels, single_resolution_float_5d, single_resolution_rgb_image +from ..res import multi_resolution_uint8_3channels, single_resolution_float_5d, single_resolution_float_5d_zarr, single_resolution_rgb_image def test_uint8_3channels_image_name(): @@ -217,6 +217,116 @@ def test_read_float_5d_image_with_dask(): bioio_server.close() + +## start + +def test_float_5d_zarr_image_name(): + bioio_server = BioIOServer(single_resolution_float_5d_zarr.get_path()) + + name = bioio_server.metadata.name + + assert name == single_resolution_float_5d_zarr.get_name() + + bioio_server.close() + + +def test_float_5d_zarr_image_shapes(): + bioio_server = BioIOServer(single_resolution_float_5d_zarr.get_path()) + + shapes = bioio_server.metadata.shapes + + assert shapes == single_resolution_float_5d_zarr.get_shapes() + + bioio_server.close() + + +def test_float_5d_zarr_image_pixel_calibration(): + bioio_server = BioIOServer(single_resolution_float_5d_zarr.get_path()) + + pixel_calibration = bioio_server.metadata.pixel_calibration + + assert pixel_calibration == PixelCalibration( + PixelLength(single_resolution_float_5d_zarr.get_pixel_size_x_y_in_micrometers()), # The BioIO library does not currently handle unit attachment + PixelLength(single_resolution_float_5d_zarr.get_pixel_size_x_y_in_micrometers()), + PixelLength(single_resolution_float_5d_zarr.get_pixel_size_x_y_in_micrometers()) + ) + + bioio_server.close() + + +def test_float_5d_zarr_image_is_not_rgb(): + bioio_server = BioIOServer(single_resolution_float_5d_zarr.get_path()) + + is_rgb = bioio_server.metadata.is_rgb + + assert not(is_rgb) + + bioio_server.close() + + +def test_float_5d_zarr_image_dtype(): + bioio_server = BioIOServer(single_resolution_float_5d_zarr.get_path()) + + dtype = bioio_server.metadata.dtype + + assert dtype == single_resolution_float_5d_zarr.get_dtype() + + bioio_server.close() + + +def test_float_5d_zarr_image_downsamples(): + bioio_server = BioIOServer(single_resolution_float_5d_zarr.get_path()) + + downsamples = bioio_server.metadata.downsamples + + assert downsamples == single_resolution_float_5d_zarr.get_downsamples() + + bioio_server.close() + + +def test_read_float_5d_zarr_image(): + full_resolution = single_resolution_float_5d_zarr.get_shapes()[0] + z = int(full_resolution.z / 2) + t = int(full_resolution.t / 2) + bioio_server = BioIOServer(single_resolution_float_5d_zarr.get_path()) + expected_pixels = np.array( + [[[single_resolution_float_5d_zarr.get_pixel_value(x, y, c, z, t) + for x in range(full_resolution.x)] + for y in range(full_resolution.y)] + for c in range(full_resolution.c)], + single_resolution_float_5d_zarr.get_dtype() + ) + + image = bioio_server.read_region( + 1, + Region2D(width=bioio_server.metadata.width, height=bioio_server.metadata.height, z=z, t=t) + ) + + np.testing.assert_array_equal(image, expected_pixels) + + bioio_server.close() + + +def test_read_float_5d_zarr_image_with_dask(): + full_resolution = single_resolution_float_5d_zarr.get_shapes()[0] + bioio_server = BioIOServer(single_resolution_float_5d_zarr.get_path()) + expected_pixels = np.array( + [[[[[single_resolution_float_5d_zarr.get_pixel_value(x, y, c, z, t) + for x in range(full_resolution.x)] + for y in range(full_resolution.y)] + for z in range(full_resolution.z)] + for c in range(full_resolution.c)] + for t in range(full_resolution.t)], + single_resolution_float_5d_zarr.get_dtype() + ) + + image = bioio_server.level_to_dask(0).compute() + + np.testing.assert_array_equal(image, expected_pixels) + + bioio_server.close() + + def test_rgb_image_name(): bioio_server = BioIOServer(single_resolution_rgb_image.get_path()) diff --git a/tests/res/single_resolution_float_5d_zarr.py b/tests/res/single_resolution_float_5d_zarr.py new file mode 100644 index 0000000..1abea0b --- /dev/null +++ b/tests/res/single_resolution_float_5d_zarr.py @@ -0,0 +1,60 @@ +import bioio.writers +import os +import shutil +import numpy as np +from bioio_base.types import PhysicalPixelSizes +from qubalab.images.metadata.image_shape import ImageShape + + +def get_name() -> str: + return "single_resolution_float_5d.ome.zarr" + + +def get_path() -> str: + return os.path.realpath(os.path.join(os.path.realpath(__file__), os.pardir, get_name())) + + +def get_shapes() -> tuple[ImageShape, ...]: + return (ImageShape(32, 16, 10, 5, 15),) + + +def get_pixel_size_x_y_in_micrometers() -> float: + return 0.5 + + +def get_dtype(): + return np.float64 + + +def get_downsamples() -> tuple[float, ...]: + return tuple([get_shapes()[0].x / shape.x for shape in get_shapes()]) + + +def get_pixel_value(x: int, y: int, c: int, z: int, t: int) -> int: + return pixels[t, c, z, y, x] + + +def _get_pixels() -> np.array: + return np.random.rand(get_shapes()[0].t, get_shapes()[0].c, get_shapes()[0].z, get_shapes()[0].y, get_shapes()[0].x) + + +def _write_image(pixels: np.array): + ## zarr writer fails if dir exists + if os.path.exists(get_path()) and os.path.isdir(get_path()): + shutil.rmtree(get_path()) + + zarr = bioio.writers.OmeZarrWriter(get_path()) + zarr.write_image( + image_data = pixels, + image_name = "single_resolution_float_5d", + channel_names = ["Channel " + str(i) for i in range(get_shapes()[0].c)], + channel_colors = [i for i in range(get_shapes()[0].c)], + physical_pixel_sizes = PhysicalPixelSizes( + X = get_pixel_size_x_y_in_micrometers(), + Y = get_pixel_size_x_y_in_micrometers(), + Z = get_pixel_size_x_y_in_micrometers()) + ) + + +pixels = _get_pixels() +_write_image(pixels) From 438cdaa4e25c9aa5d7c09d7eb92c332e9b83ac36 Mon Sep 17 00:00:00 2001 From: Alan O'Callaghan Date: Thu, 31 Oct 2024 12:02:12 +0000 Subject: [PATCH 4/5] Remove redundant comment --- tests/images/test_bioio_server.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/images/test_bioio_server.py b/tests/images/test_bioio_server.py index 25a735e..e9fdf56 100644 --- a/tests/images/test_bioio_server.py +++ b/tests/images/test_bioio_server.py @@ -218,8 +218,6 @@ def test_read_float_5d_image_with_dask(): -## start - def test_float_5d_zarr_image_name(): bioio_server = BioIOServer(single_resolution_float_5d_zarr.get_path()) From 4f772f792f0fad54691eaabc5883f10347de2570 Mon Sep 17 00:00:00 2001 From: Alan O'Callaghan Date: Mon, 4 Nov 2024 15:32:01 +0000 Subject: [PATCH 5/5] Address Leo's comment --- notebooks/opening_images.ipynb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/notebooks/opening_images.ipynb b/notebooks/opening_images.ipynb index 9063b7f..343dda9 100644 --- a/notebooks/opening_images.ipynb +++ b/notebooks/opening_images.ipynb @@ -55,13 +55,14 @@ "\n", "from pathlib import Path\n", "import urllib.request\n", + "import tempfile\n", "\n", "if cache_folder != \"\":\n", " Path(cache_folder).mkdir(parents=True, exist_ok=True)\n", "\n", "def get_image(image_name, image_url):\n", " if cache_folder == \"\":\n", - " filename = None\n", + " filename = tempfile.gettempdir() / Path(image_name)\n", " else:\n", " filename = Path(cache_folder) / image_name\n", " \n",