|
57 | 57 | " # Install dependencies\n", |
58 | 58 | " ! pip install --upgrade pip\n", |
59 | 59 | " ! pip install czitools\n", |
| 60 | + " ! pip install ome-zarr\n", |
60 | 61 | " ! pip install ngff-zarr[validate, dask-image]" |
61 | 62 | ] |
62 | 63 | }, |
|
70 | 71 | "name": "stdout", |
71 | 72 | "output_type": "stream", |
72 | 73 | "text": [ |
73 | | - "ZARR Verion: 3.1.1 NGFF-ZARR Version: 0.16.1\n" |
| 74 | + "\u001b[32m2025-08-25 15:17:31,259 - czitools - INFO - Using ngff format version: 0.5\u001b[0m\n", |
| 75 | + "\u001b[32m2025-08-25 15:17:31,260 - czitools - INFO - ZARR Version: 3.1.1\u001b[0m\n", |
| 76 | + "\u001b[32m2025-08-25 15:17:31,260 - czitools - INFO - NGFF-ZARR Version: 0.16.1\u001b[0m\n", |
| 77 | + "\u001b[32m2025-08-25 15:17:31,262 - czitools - INFO - OME-ZARR Version: 0.12.2\u001b[0m\n" |
74 | 78 | ] |
75 | 79 | } |
76 | 80 | ], |
77 | 81 | "source": [ |
78 | 82 | "from czitools.read_tools import read_tools\n", |
79 | 83 | "from czitools.metadata_tools import czi_metadata as czimd\n", |
80 | | - "from pathlib import Path\n", |
81 | | - "import shutil\n", |
82 | | - "import requests\n", |
83 | | - "import os\n", |
84 | 84 | "import ngff_zarr as nz\n", |
| 85 | + "from pathlib import Path\n", |
| 86 | + "import dask.array as da\n", |
85 | 87 | "import zarr\n", |
86 | | - "print(f\"ZARR Verion: {zarr.__version__} NGFF-ZARR Version: {nz.__version__}\")" |
| 88 | + "import os\n", |
| 89 | + "import requests\n", |
| 90 | + "import ome_zarr.writer\n", |
| 91 | + "import ome_zarr.format\n", |
| 92 | + "from ome_zarr.io import parse_url\n", |
| 93 | + "from typing import Union\n", |
| 94 | + "import shutil\n", |
| 95 | + "import numpy as np\n", |
| 96 | + "from czitools.utils import logging_tools\n", |
| 97 | + "from importlib.metadata import version\n", |
| 98 | + "\n", |
| 99 | + "logger = logging_tools.set_logging()\n", |
| 100 | + "\n", |
| 101 | + "# show currently used version of NGFF specification\n", |
| 102 | + "ngff_version = ome_zarr.format.CurrentFormat().version\n", |
| 103 | + "logger.info(f\"Using ngff format version: {ngff_version}\")\n", |
| 104 | + "logger.info(f\"ZARR Version: {zarr.__version__}\")\n", |
| 105 | + "logger.info(f\"NGFF-ZARR Version: {nz.__version__}\")\n", |
| 106 | + "logger.info(f\"OME-ZARR Version: {version('ome-zarr')}\")" |
87 | 107 | ] |
88 | 108 | }, |
89 | 109 | { |
90 | 110 | "cell_type": "code", |
91 | 111 | "execution_count": 4, |
| 112 | + "id": "1ba3c0b3", |
| 113 | + "metadata": {}, |
| 114 | + "outputs": [], |
| 115 | + "source": [ |
| 116 | + "def write_omezarr(\n", |
| 117 | + " array5d: Union[np.ndarray, da.Array],\n", |
| 118 | + " zarr_path: str,\n", |
| 119 | + " axes: str = \"tczyx\",\n", |
| 120 | + " overwrite: bool = False,\n", |
| 121 | + ") -> str:\n", |
| 122 | + " \"\"\"\n", |
| 123 | + " Writes a 5D array to an OME-ZARR file.\n", |
| 124 | + " Parameters:\n", |
| 125 | + " -----------\n", |
| 126 | + " array5d : Union[np.ndarray, da.Array]\n", |
| 127 | + " The 5D array to be written. The dimensions should not exceed 5.\n", |
| 128 | + " zarr_path : str\n", |
| 129 | + " The path where the OME-ZARR file will be saved.\n", |
| 130 | + " axes : str, optional\n", |
| 131 | + " The order of axes in the array. Default is \"tczyx\".\n", |
| 132 | + " overwrite : bool, optional\n", |
| 133 | + " If True, the existing file at zarr_path will be overwritten. Default is False.\n", |
| 134 | + " Returns:\n", |
| 135 | + " --------\n", |
| 136 | + " str\n", |
| 137 | + " The path to the written OME-ZARR folder if successful, otherwise None.\n", |
| 138 | + " Notes:\n", |
| 139 | + " ------\n", |
| 140 | + " - The function ensures the axes are in lowercase and removes any invalid dimensions.\n", |
| 141 | + " - If the zarr_path already exists and overwrite is True, the existing directory will be removed.\n", |
| 142 | + " - The function logs the NGFF format version being used.\n", |
| 143 | + " - The function writes the image data to the specified zarr_path.\n", |
| 144 | + " - If the writing process is successful, the function returns the zarr_path; otherwise, it returns None.\n", |
| 145 | + " \"\"\"\n", |
| 146 | + "\n", |
| 147 | + " # check number of dimension of input array\n", |
| 148 | + " if len(array5d.shape) > 5:\n", |
| 149 | + " logger.warning(\"Input array as more than 5 dimensions.\")\n", |
| 150 | + " return None\n", |
| 151 | + "\n", |
| 152 | + " # make sure lower case is use for axes order\n", |
| 153 | + " axes = axes.lower()\n", |
| 154 | + "\n", |
| 155 | + " # check for invalid dimensions and clean up\n", |
| 156 | + " for character in [\"b\", \"h\", \"s\", \"i\", \"v\", \"a\"]:\n", |
| 157 | + " axes = axes.replace(character, \"\")\n", |
| 158 | + "\n", |
| 159 | + " # check if zarr_path already exits\n", |
| 160 | + " if Path(zarr_path).exists() and overwrite:\n", |
| 161 | + " shutil.rmtree(zarr_path, ignore_errors=False, onerror=None)\n", |
| 162 | + " elif Path(zarr_path).exists() and not overwrite:\n", |
| 163 | + " logger.warning(\n", |
| 164 | + " f\"File already exists at {zarr_path}. Set overwrite=True to remove.\"\n", |
| 165 | + " )\n", |
| 166 | + " return None\n", |
| 167 | + "\n", |
| 168 | + " # write the image data\n", |
| 169 | + " store = parse_url(zarr_path, mode=\"w\").store\n", |
| 170 | + " root = zarr.group(store=store, overwrite=overwrite)\n", |
| 171 | + "\n", |
| 172 | + " # TODO: Add Channel information etc. to the root along those lines\n", |
| 173 | + " \"\"\"\n", |
| 174 | + " # add omero metadata_tools: the napari ome-zarr plugin uses this to pass rendering\n", |
| 175 | + " # options to napari.\n", |
| 176 | + " root.attrs['omero'] = {\n", |
| 177 | + " 'channels': [{\n", |
| 178 | + " 'color': 'ffffff',\n", |
| 179 | + " 'label': 'LS-data',\n", |
| 180 | + " 'active': True,\n", |
| 181 | + " }]\n", |
| 182 | + " }\n", |
| 183 | + "\n", |
| 184 | + " \"\"\"\n", |
| 185 | + "\n", |
| 186 | + " # write the OME-ZARR file\n", |
| 187 | + " ome_zarr.writer.write_image(\n", |
| 188 | + " image=array5d,\n", |
| 189 | + " group=root,\n", |
| 190 | + " axes=axes,\n", |
| 191 | + " storage_options=dict(chunks=array5d.shape),\n", |
| 192 | + " )\n", |
| 193 | + "\n", |
| 194 | + " logger.info(f\"Finished writing OME-ZARR to: {zarr_path}\")\n", |
| 195 | + "\n", |
| 196 | + " return zarr_path" |
| 197 | + ] |
| 198 | + }, |
| 199 | + { |
| 200 | + "cell_type": "code", |
| 201 | + "execution_count": 5, |
92 | 202 | "id": "5b96a0fa", |
93 | 203 | "metadata": {}, |
94 | 204 | "outputs": [], |
|
119 | 229 | }, |
120 | 230 | { |
121 | 231 | "cell_type": "code", |
122 | | - "execution_count": 5, |
| 232 | + "execution_count": 6, |
123 | 233 | "id": "2ab02d0c", |
124 | 234 | "metadata": {}, |
125 | 235 | "outputs": [ |
126 | 236 | { |
127 | 237 | "name": "stdout", |
128 | 238 | "output_type": "stream", |
129 | 239 | "text": [ |
130 | | - "/datadisk1/Github/czitools/data/CellDivision_T3_Z5_CH2_X240_Y170.ome.zarr\n" |
| 240 | + "\u001b[32m2025-08-25 15:18:12,909 - czitools - INFO - /datadisk1/Github/czitools/data/CellDivision_T3_Z5_CH2_X240_Y170.ome.zarr\u001b[0m\n" |
131 | 241 | ] |
132 | 242 | } |
133 | 243 | ], |
|
141 | 251 | " filepath = os.path.join(defaultdir, \"CellDivision_T3_Z5_CH2_X240_Y170.czi\")\n", |
142 | 252 | " zarr_path = defaultdir / Path(filepath[:-4] + \".ome.zarr\")\n", |
143 | 253 | "\n", |
144 | | - "print(zarr_path)\n", |
| 254 | + "logger.info(zarr_path)\n", |
145 | 255 | "\n", |
146 | 256 | "# check if path exists\n", |
147 | 257 | "remove = True\n", |
|
151 | 261 | }, |
152 | 262 | { |
153 | 263 | "cell_type": "code", |
154 | | - "execution_count": 6, |
| 264 | + "execution_count": 9, |
155 | 265 | "id": "68d184ed", |
156 | 266 | "metadata": {}, |
157 | 267 | "outputs": [ |
158 | 268 | { |
159 | 269 | "data": { |
160 | 270 | "application/vnd.jupyter.widget-view+json": { |
161 | | - "model_id": "b0087b111b7248de9e456bb662f75d3e", |
| 271 | + "model_id": "ecceeaa3a1c5428f94e965bd04c4932a", |
162 | 272 | "version_major": 2, |
163 | 273 | "version_minor": 0 |
164 | 274 | }, |
|
173 | 283 | "name": "stdout", |
174 | 284 | "output_type": "stream", |
175 | 285 | "text": [ |
176 | | - "Number of Scenes: None\n" |
| 286 | + "\u001b[32m2025-08-25 15:19:51,505 - czitools - INFO - Number of Scenes: None\u001b[0m\n" |
177 | 287 | ] |
178 | | - } |
179 | | - ], |
180 | | - "source": [ |
181 | | - "# get the metadata at once as one big class\n", |
182 | | - "mdata = czimd.CziMetadata(filepath)\n", |
183 | | - "print(\"Number of Scenes: \", mdata.image.SizeS)\n", |
184 | | - "scene_id = 0" |
185 | | - ] |
186 | | - }, |
187 | | - { |
188 | | - "cell_type": "code", |
189 | | - "execution_count": 7, |
190 | | - "id": "1e32b670", |
191 | | - "metadata": {}, |
192 | | - "outputs": [ |
| 288 | + }, |
193 | 289 | { |
194 | 290 | "data": { |
195 | 291 | "application/vnd.jupyter.widget-view+json": { |
196 | | - "model_id": "b7a21676cc0c4d659417a073ec8b5822", |
| 292 | + "model_id": "b9304e7cdf8c44378de6765920202915", |
197 | 293 | "version_major": 2, |
198 | 294 | "version_minor": 0 |
199 | 295 | }, |
|
207 | 303 | { |
208 | 304 | "data": { |
209 | 305 | "application/vnd.jupyter.widget-view+json": { |
210 | | - "model_id": "14a2b4e5d47e4f3c98521409cfb8f130", |
| 306 | + "model_id": "f462a65c4dbf464099307f28ac00ae1a", |
211 | 307 | "version_major": 2, |
212 | 308 | "version_minor": 0 |
213 | 309 | }, |
|
222 | 318 | "name": "stdout", |
223 | 319 | "output_type": "stream", |
224 | 320 | "text": [ |
225 | | - "Array Shape: (3, 2, 5, 170, 240)\n" |
| 321 | + "\u001b[32m2025-08-25 15:19:51,812 - czitools - INFO - Array Shape: (3, 2, 5, 170, 240)\u001b[0m\n" |
226 | 322 | ] |
227 | 323 | } |
228 | 324 | ], |
229 | 325 | "source": [ |
| 326 | + "# get the metadata at once as one big class\n", |
| 327 | + "mdata = czimd.CziMetadata(filepath)\n", |
| 328 | + "logger.info(f\"Number of Scenes: {mdata.image.SizeS}\")\n", |
| 329 | + "scene_id = 0\n", |
| 330 | + "\n", |
230 | 331 | "array, mdata = read_tools.read_6darray(filepath)\n", |
231 | 332 | "\n", |
232 | 333 | "array = array[scene_id, ...]\n", |
233 | | - "print(f\"Array Shape: {array.shape}\")" |
| 334 | + "logger.info(f\"Array Shape: {array.shape}\")" |
234 | 335 | ] |
235 | 336 | }, |
236 | 337 | { |
237 | 338 | "cell_type": "code", |
238 | | - "execution_count": 8, |
239 | | - "id": "c670b17c", |
| 339 | + "execution_count": null, |
| 340 | + "id": "e192c410", |
240 | 341 | "metadata": {}, |
241 | | - "outputs": [], |
| 342 | + "outputs": [ |
| 343 | + { |
| 344 | + "name": "stdout", |
| 345 | + "output_type": "stream", |
| 346 | + "text": [ |
| 347 | + "\u001b[32m2025-08-25 15:20:13,717 - czitools - INFO - Finished writing OME-ZARR to: /datadisk1/Github/czitools/data/CellDivision_T3_Z5_CH2_X240_Y170_1.ome.zarr\u001b[0m\n", |
| 348 | + "\u001b[32m2025-08-25 15:20:13,717 - czitools - INFO - Written OME-ZARR using ome-zarr.py: /datadisk1/Github/czitools/data/CellDivision_T3_Z5_CH2_X240_Y170_1.ome.zarr\u001b[0m\n" |
| 349 | + ] |
| 350 | + } |
| 351 | + ], |
242 | 352 | "source": [ |
243 | | - "# create NGFF image from the array\n", |
244 | | - "image = nz.to_ngff_image(array.data,\n", |
245 | | - " dims=[\"t\", \"c\", \"z\", \"y\", \"x\"],\n", |
246 | | - " scale={\"y\": mdata.scale.Y, \"x\": mdata.scale.X, \"z\": mdata.scale.Z},\n", |
247 | | - " name=mdata.filename)\n", |
248 | | - "\n" |
| 353 | + "# Approach 1: Use ome-zarr-py to write OME-ZARR\n", |
| 354 | + "zarr_path1 = Path(str(filepath)[:-4] + \"_1.ome.zarr\")\n", |
| 355 | + "\n", |
| 356 | + "# write OME-ZARR using utility function\n", |
| 357 | + "zarr_path1 = write_omezarr(\n", |
| 358 | + " array, zarr_path=str(zarr_path1), axes=\"tczyx\", overwrite=True\n", |
| 359 | + ")\n", |
| 360 | + "\n", |
| 361 | + "logger.info(f\"Written OME-ZARR using ome-zarr.py: {zarr_path1}\")\n", |
| 362 | + "\n", |
| 363 | + "write_omezarr1 = True" |
249 | 364 | ] |
250 | 365 | }, |
251 | 366 | { |
252 | 367 | "cell_type": "code", |
253 | | - "execution_count": 9, |
254 | | - "id": "24a93ccf-5cd4-4435-867b-190093bba3cc", |
| 368 | + "execution_count": null, |
| 369 | + "id": "c670b17c", |
255 | 370 | "metadata": {}, |
256 | | - "outputs": [], |
| 371 | + "outputs": [ |
| 372 | + { |
| 373 | + "name": "stdout", |
| 374 | + "output_type": "stream", |
| 375 | + "text": [ |
| 376 | + "\u001b[32m2025-08-25 15:20:19,409 - czitools - INFO - NGFF Image: NgffImage(data=dask.array<rechunk-merge, shape=(3, 2, 5, 170, 240), dtype=uint16, chunksize=(1, 2, 5, 128, 128), chunktype=numpy.ndarray>, dims=['t', 'c', 'z', 'y', 'x'], scale={'y': np.float64(0.091), 'x': np.float64(0.091), 'z': np.float64(0.32)}, translation={'z': 0.0, 'y': 0.0, 'x': 0.0}, name='CellDivision_T3_Z5_CH2_X240_Y170.czi', axes_units=None, axes_orientations=None, computed_callbacks=[])\u001b[0m\n", |
| 377 | + "\u001b[32m2025-08-25 15:20:19,409 - czitools - INFO - Written OME-ZARR using ngff-zarr: /datadisk1/Github/czitools/data/CellDivision_T3_Z5_CH2_X240_Y170_2.ome.zarr\u001b[0m\n" |
| 378 | + ] |
| 379 | + } |
| 380 | + ], |
257 | 381 | "source": [ |
| 382 | + "# Approach 2: Use ngff-zarr to create NGFF structure and write using ome-zarr-py\n", |
| 383 | + "zarr_path2 = Path(str(filepath)[:-4] + \"_2.ome.zarr\")\n", |
| 384 | + "\n", |
| 385 | + "# create NGFF image from the array\n", |
| 386 | + "image = nz.to_ngff_image(array.data,\n", |
| 387 | + " dims=[\"t\", \"c\", \"z\", \"y\", \"x\"],\n", |
| 388 | + " scale={\"y\": mdata.scale.Y, \"x\": mdata.scale.X, \"z\": mdata.scale.Z},\n", |
| 389 | + " name=mdata.filename)\n", |
| 390 | + "\n", |
258 | 391 | "# create multi-scaled, chunked data structure from the image\n", |
259 | | - "multiscales = nz.to_multiscales(image, [2, 4], method=nz.Methods.DASK_IMAGE_GAUSSIAN)" |
| 392 | + "multiscales = nz.to_multiscales(image, [2, 4], method=nz.Methods.DASK_IMAGE_GAUSSIAN)\n", |
| 393 | + "\n", |
| 394 | + "# write using ngff-zarr\n", |
| 395 | + "nz.to_ngff_zarr(zarr_path2, multiscales)\n", |
| 396 | + "logger.info(f\"NGFF Image: {image}\")\n", |
| 397 | + "logger.info(f\"Written OME-ZARR using ngff-zarr: {zarr_path2}\")\n", |
| 398 | + "\n", |
| 399 | + "write_omezarr2 = True" |
260 | 400 | ] |
261 | 401 | } |
262 | 402 | ], |
|
0 commit comments