diff --git a/.gitignore b/.gitignore index a27f08d..934c4b8 100644 --- a/.gitignore +++ b/.gitignore @@ -173,4 +173,5 @@ data/CellDivision_T=10_Z=15_CH=2_DCV_small.czi /data/Tumor_HE_Orig_small_Label.czi /data/Tumor_HE_Orig_small_SlidePreview.czi /data/est_ngff_plate.zarr -/data/est_ngff_plate.zarr \ No newline at end of file +/data/est_ngff_plate.zarr +**/czitools/*.old diff --git a/README.md b/README.md index 2071141..0759eba 100644 --- a/README.md +++ b/README.md @@ -8,16 +8,44 @@ This repository provides a collection of tools to simplify reading CZI (Carl Zeiss Image) pixel and metadata in Python. In addition it also contains other useful utilities to visualize CZI images inside Napari (needs to be installed). It is also available as a [Python Package on PyPi](https://pypi.org/project/czitools/) +## Installation + +To install the basic functionality (will not install Napari und plotting functionality) use: + +```text +pip install czitools" +``` + +To install the package with all optional dependencies use: + +```text +pip install czitools[all] +``` + ## Reading the metadata Please check [use_metadata_tools.py](https://github.com/sebi06/czitools/blob/main/demo/scripts/use_metadata_tools.py) for some examples. ```python -# get the metadata at once as one big class -mdata = czimd.CziMetadata(filepath) - -# get only specific metadata -czi_dimensions = czimd.CziDimensions(filepath) +from czitools.metadata_tools.czi_metadata import CziMetadata, writexml +from czitools.metadata_tools.dimension import CziDimensions +from czitools.metadata_tools.boundingbox import CziBoundingBox +from czitools.metadata_tools.channel import CziChannelInfo +from czitools.metadata_tools.scaling import CziScaling +from czitools.metadata_tools.sample import CziSampleInfo +from czitools.metadata_tools.objective import CziObjectives +from czitools.metadata_tools.microscope import CziMicroscope +from czitools.metadata_tools.add_metadata import CziAddMetaData +from czitools.metadata_tools.detector import CziDetector +from czitools.read_tools import read_tools +from czitools.napari_tools import napari_tools +import napari + +# get the metadata_tools at once as one big class +mdata = CziMetadata(filepath) + +# get only specific metadata_tools +czi_dimensions = CziDimensions(filepath) print("SizeS: ", czi_dimensions.SizeS) print("SizeT: ", czi_dimensions.SizeT) print("SizeZ: ", czi_dimensions.SizeZ) @@ -26,36 +54,36 @@ print("SizeY: ", czi_dimensions.SizeY) print("SizeX: ", czi_dimensions.SizeX) # try to write XML to file -xmlfile = czimd.writexml(filepath) +xmlfile = writexml(filepath) # get info about the channels -czi_channels = czimd.CziChannelInfo(filepath) +czi_channels = CziChannelInfo(filepath) -# get the complete metadata from the CZI as one big object -czimd_complete = czimd.get_metadata_as_object(filepath) +# get the complete metadata_tools from the CZI as one big object +czimd_complete = get_metadata_as_object(filepath) # get an object containing only the dimension information -czi_dimensions = czimd.CziDimensions(filepath) +czi_dimensions = CziDimensions(filepath) # get an object containing only the dimension information -czi_scale = czimd.CziScaling(filepath) +czi_scale = CziScaling(filepath) # get an object containing information about the sample -czi_sample = czimd.CziSampleInfo(filepath) +czi_sample = CziSampleInfo(filepath) # get info about the objective, the microscope and the detectors -czi_objectives = czimd.CziObjectives(filepath) -czi_detectors = czimd.CziDetector(filepath) -czi_microscope = czimd.CziMicroscope(filepath) +czi_objectives = CziObjectives(filepath) +czi_detectors = CziDetector(filepath) +czi_microscope = CziMicroscope(filepath) # get info about the sample carrier -czi_sample = czimd.CziSampleInfo(filepath) +czi_sample = CziSampleInfo(filepath) # get additional metainformation -czi_addmd = czimd.CziAddMetaData(filepath) +czi_addmd = CziAddMetaData(filepath) # get the complete data about the bounding boxes -czi_bbox = czimd.CziBoundingBox(filepath) +czi_bbox = CziBoundingBox(filepath) ``` ## Reading CZI pixel data @@ -127,3 +155,17 @@ The basic usage can be inferred from this sample notebook:  ## Remarks The code to read multi-dimensional with delayed reading using Dask array was heavily inspired by input from: [Pradeep Rajasekhar](https://github.com/pr4deepr). + +Local installation (base functionality only): + +```text +pip install -e . +``` + +### Local Installation + +Local installation (full functionality): + +```text +pip install -e ".[all]" +``` diff --git a/data/CellDivision_T3_Z5_CH2_X240_Y170_comp1.czi b/data/CellDivision_T3_Z5_CH2_X240_Y170_comp1.czi index dde79c8..eecaafa 100644 Binary files a/data/CellDivision_T3_Z5_CH2_X240_Y170_comp1.czi and b/data/CellDivision_T3_Z5_CH2_X240_Y170_comp1.czi differ diff --git a/data/CellDivision_T3_Z5_CH2_X240_Y170_comp2.czi b/data/CellDivision_T3_Z5_CH2_X240_Y170_comp2.czi index 889adcf..706d352 100644 Binary files a/data/CellDivision_T3_Z5_CH2_X240_Y170_comp2.czi and b/data/CellDivision_T3_Z5_CH2_X240_Y170_comp2.czi differ diff --git a/demo/notebooks/omezarr_from_czi_5d.ipynb b/demo/notebooks/omezarr_from_czi_5d.ipynb index 4b50e74..c9a9ce9 100644 --- a/demo/notebooks/omezarr_from_czi_5d.ipynb +++ b/demo/notebooks/omezarr_from_czi_5d.ipynb @@ -22,7 +22,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "id": "9873c5dc", "metadata": { "editable": true, @@ -43,7 +43,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "id": "67c73f91", "metadata": { "editable": true, @@ -62,7 +62,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 11, "id": "b236d2d9", "metadata": {}, "outputs": [ @@ -72,19 +72,21 @@ "'2.15.0'" ] }, - "execution_count": 4, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "from czitools import metadata_tools as czimd\n", - "from czitools import read_tools, write_tools\n", + "from czitools.read_tools import read_tools\n", + "from czitools.write_tools import write_tools\n", + "from czitools.metadata_tools import czi_metadata as czimd\n", "import napari\n", "from pathlib import Path\n", "import ome_zarr.reader\n", "import ome_zarr.scale\n", "import ome_zarr.writer\n", + "import ome_zarr.format\n", "from ome_zarr.io import parse_url\n", "import shutil\n", "import zarr\n", @@ -99,7 +101,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "5b96a0fa", "metadata": {}, "outputs": [], @@ -130,7 +132,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "id": "2ab02d0c", "metadata": {}, "outputs": [ @@ -162,7 +164,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "id": "68d184ed", "metadata": {}, "outputs": [ @@ -170,20 +172,39 @@ "name": "stderr", "output_type": "stream", "text": [ - "2024-02-11 14:19:52,010 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", - "2024-02-11 14:19:52,020 - \u001b[0;30;42mINFO\u001b[0m - Reading BoundingBoxes from CZI image data.\n", - "2024-02-11 14:19:52,021 - \u001b[0;30;42mINFO\u001b[0m - Reading Channel Information from CZI image data.\n", - "2024-02-11 14:19:52,022 - \u001b[0;30;42mINFO\u001b[0m - Reading Scaling from CZI image data.\n", - "2024-02-11 14:19:52,023 - \u001b[0;30;42mINFO\u001b[0m - Reading Objective Information from CZI image data.\n", - "2024-02-11 14:19:52,023 - \u001b[0;30;42mINFO\u001b[0m - Reading Detector Information from CZI image data.\n", - "2024-02-11 14:19:52,024 - \u001b[0;30;42mINFO\u001b[0m - Reading Microscope Information from CZI image data.\n", - "2024-02-11 14:19:52,025 - \u001b[0;30;42mINFO\u001b[0m - Reading SampleCarrier Information from CZI image data.\n", - "2024-02-11 14:19:52,026 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", - "2024-02-11 14:19:52,028 - \u001b[0;30;42mINFO\u001b[0m - No Scene or Well information found. Try to read XY Stage Coordinates from subblocks.\n", - "2024-02-11 14:19:52,281 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", - "2024-02-11 14:19:52,291 - \u001b[0;30;42mINFO\u001b[0m - Reading additional Metedata from CZI image data.\n", - "2024-02-11 14:19:52,291 - \u001b[0;30;42mINFO\u001b[0m - No Layers information found.\n", - "2024-02-11 14:19:52,292 - \u001b[0;30;42mINFO\u001b[0m - Reading AttachmentImages from CZI image data.\n" + "2024-05-10 21:28:00,682 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", + "2024-05-10 21:28:00,687 - \u001b[0;30;42mINFO\u001b[0m - Reading BoundingBoxes from CZI image data.\n", + "2024-05-10 21:28:00,688 - \u001b[0;30;42mINFO\u001b[0m - Reading Channel Information from CZI image data.\n", + "2024-05-10 21:28:00,689 - \u001b[0;30;42mINFO\u001b[0m - Reading Scaling from CZI image data.\n", + "2024-05-10 21:28:00,690 - \u001b[0;30;42mINFO\u001b[0m - Reading Objective Information from CZI image data.\n", + "2024-05-10 21:28:00,691 - \u001b[0;30;42mINFO\u001b[0m - Reading Detector Information from CZI image data.\n", + "2024-05-10 21:28:00,692 - \u001b[0;30;42mINFO\u001b[0m - Reading Microscope Information from CZI image data.\n", + "2024-05-10 21:28:00,692 - \u001b[0;30;42mINFO\u001b[0m - Reading SampleCarrier Information from CZI image data.\n", + "2024-05-10 21:28:00,693 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", + "2024-05-10 21:28:00,694 - \u001b[0;30;42mINFO\u001b[0m - No Scene or Well information found. Try to read XY Stage Coordinates from subblocks.\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "af4099e11f0549379c8fafba72149229", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Reading sublocks planes: 0 2Dplanes [00:00, ? 2Dplanes/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2024-05-10 21:28:00,721 - \u001b[0;30;42mINFO\u001b[0m - Reading additional Metedata from CZI image data.\n", + "2024-05-10 21:28:00,722 - \u001b[0;30;42mINFO\u001b[0m - No Layers information found.\n", + "2024-05-10 21:28:00,724 - \u001b[0;30;42mINFO\u001b[0m - Reading AttachmentImages from CZI image data.\n" ] }, { @@ -203,7 +224,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "id": "1e32b670", "metadata": {}, "outputs": [ @@ -211,26 +232,45 @@ "name": "stderr", "output_type": "stream", "text": [ - "2024-02-11 14:19:55,788 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", - "2024-02-11 14:19:55,790 - \u001b[0;30;42mINFO\u001b[0m - Reading BoundingBoxes from CZI image data.\n", - "2024-02-11 14:19:55,792 - \u001b[0;30;42mINFO\u001b[0m - Reading Channel Information from CZI image data.\n", - "2024-02-11 14:19:55,792 - \u001b[0;30;42mINFO\u001b[0m - Reading Scaling from CZI image data.\n", - "2024-02-11 14:19:55,793 - \u001b[0;30;42mINFO\u001b[0m - Reading Objective Information from CZI image data.\n", - "2024-02-11 14:19:55,794 - \u001b[0;30;42mINFO\u001b[0m - Reading Detector Information from CZI image data.\n", - "2024-02-11 14:19:55,795 - \u001b[0;30;42mINFO\u001b[0m - Reading Microscope Information from CZI image data.\n", - "2024-02-11 14:19:55,796 - \u001b[0;30;42mINFO\u001b[0m - Reading SampleCarrier Information from CZI image data.\n", - "2024-02-11 14:19:55,797 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", - "2024-02-11 14:19:55,798 - \u001b[0;30;42mINFO\u001b[0m - No Scene or Well information found. Try to read XY Stage Coordinates from subblocks.\n", - "2024-02-11 14:19:56,014 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", - "2024-02-11 14:19:56,021 - \u001b[0;30;42mINFO\u001b[0m - Reading additional Metedata from CZI image data.\n", - "2024-02-11 14:19:56,022 - \u001b[0;30;42mINFO\u001b[0m - No Layers information found.\n", - "2024-02-11 14:19:56,023 - \u001b[0;30;42mINFO\u001b[0m - Reading AttachmentImages from CZI image data.\n" + "2024-05-10 21:28:05,818 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", + "2024-05-10 21:28:05,820 - \u001b[0;30;42mINFO\u001b[0m - Reading BoundingBoxes from CZI image data.\n", + "2024-05-10 21:28:05,822 - \u001b[0;30;42mINFO\u001b[0m - Reading Channel Information from CZI image data.\n", + "2024-05-10 21:28:05,823 - \u001b[0;30;42mINFO\u001b[0m - Reading Scaling from CZI image data.\n", + "2024-05-10 21:28:05,824 - \u001b[0;30;42mINFO\u001b[0m - Reading Objective Information from CZI image data.\n", + "2024-05-10 21:28:05,825 - \u001b[0;30;42mINFO\u001b[0m - Reading Detector Information from CZI image data.\n", + "2024-05-10 21:28:05,827 - \u001b[0;30;42mINFO\u001b[0m - Reading Microscope Information from CZI image data.\n", + "2024-05-10 21:28:05,828 - \u001b[0;30;42mINFO\u001b[0m - Reading SampleCarrier Information from CZI image data.\n", + "2024-05-10 21:28:05,828 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", + "2024-05-10 21:28:05,830 - \u001b[0;30;42mINFO\u001b[0m - No Scene or Well information found. Try to read XY Stage Coordinates from subblocks.\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "a5ad1763a40b41cdb280c7ac8a233fc7", + "model_id": "c33ef83c98ff4acbbb09d0c46f11fc74", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Reading sublocks planes: 0 2Dplanes [00:00, ? 2Dplanes/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2024-05-10 21:28:05,852 - \u001b[0;30;42mINFO\u001b[0m - Reading additional Metedata from CZI image data.\n", + "2024-05-10 21:28:05,853 - \u001b[0;30;42mINFO\u001b[0m - No Layers information found.\n", + "2024-05-10 21:28:05,853 - \u001b[0;30;42mINFO\u001b[0m - Reading AttachmentImages from CZI image data.\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "3bd107cbfc7d46d79459e7c2d376ee51", "version_major": 2, "version_minor": 0 }, @@ -258,7 +298,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "id": "c670b17c", "metadata": {}, "outputs": [ diff --git a/demo/notebooks/read_czi_metadata.ipynb b/demo/notebooks/read_czi_metadata.ipynb index b51ae9d..a575426 100644 --- a/demo/notebooks/read_czi_metadata.ipynb +++ b/demo/notebooks/read_czi_metadata.ipynb @@ -33,8 +33,8 @@ "outputs": [], "source": [ "# import the required libraries\n", - "from czitools import metadata_tools as czimd\n", - "from czitools import misc_tools\n", + "from czitools.metadata_tools import czi_metadata as czimd\n", + "from czitools.utils import misc\n", "from ipyfilechooser import FileChooser\n", "from IPython.display import display, HTML\n", "from pathlib import Path\n", @@ -53,7 +53,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 4, "metadata": { "pycharm": { "name": "#%%\n" @@ -87,13 +87,13 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "b6230baab62b42bf9111a13348727ef5", + "model_id": "3ce83a82c5fa4dee807a5606fdd7a373", "version_major": 2, "version_minor": 0 }, @@ -126,7 +126,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 6, "metadata": { "pycharm": { "name": "#%%\n" @@ -137,7 +137,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Selected File: E:\\Github\\czitools\\data\\CellDivision_T10_Z15_CH2_DCV_small.czi\n" + "Selected File: F:\\Github\\czitools\\data\\CellDivision_T10_Z15_CH2_DCV_small.czi\n" ] } ], @@ -152,14 +152,14 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "2024-02-11 14:16:19,465 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n" + "2024-02-19 14:03:48,640 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n" ] }, { @@ -188,23 +188,29 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "2024-02-11 14:17:00,463 - \u001b[0;30;42mINFO\u001b[0m - Reading Scaling from CZI image data.\n", - "2024-02-11 14:17:00,715 - \u001b[0;30;42mINFO\u001b[0m - Reading Channel Information from CZI image data.\n", - "2024-02-11 14:17:00,925 - \u001b[0;30;42mINFO\u001b[0m - Reading BoundingBoxes from CZI image data.\n", - "2024-02-11 14:17:00,926 - \u001b[0;30;42mINFO\u001b[0m - Reading Objective Information from CZI image data.\n", - "2024-02-11 14:17:01,133 - \u001b[0;30;42mINFO\u001b[0m - Reading Detector Information from CZI image data.\n", - "2024-02-11 14:17:01,322 - \u001b[0;30;42mINFO\u001b[0m - Reading Microscope Information from CZI image data.\n", - "2024-02-11 14:17:01,516 - \u001b[0;30;42mINFO\u001b[0m - Reading SampleCarrier Information from CZI image data.\n", - "2024-02-11 14:17:01,735 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", - "2024-02-11 14:17:01,735 - \u001b[0;30;42mINFO\u001b[0m - No Scene or Well information found. Try to read XY Stage Coordinates from subblocks.\n", - "2024-02-11 14:17:01,940 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n" + "2024-02-19 14:03:51,459 - \u001b[0;30;42mINFO\u001b[0m - Reading Scaling from CZI image data.\n", + "2024-02-19 14:03:51,603 - \u001b[0;30;42mINFO\u001b[0m - Reading Channel Information from CZI image data.\n", + "2024-02-19 14:03:51,732 - \u001b[0;30;42mINFO\u001b[0m - Reading BoundingBoxes from CZI image data.\n", + "2024-02-19 14:03:51,734 - \u001b[0;30;42mINFO\u001b[0m - Reading Objective Information from CZI image data.\n", + "2024-02-19 14:03:51,876 - \u001b[0;30;42mINFO\u001b[0m - Reading Detector Information from CZI image data.\n", + "2024-02-19 14:03:52,051 - \u001b[0;30;42mINFO\u001b[0m - Reading Microscope Information from CZI image data.\n", + "2024-02-19 14:03:52,176 - \u001b[0;30;42mINFO\u001b[0m - Reading SampleCarrier Information from CZI image data.\n", + "2024-02-19 14:03:52,305 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", + "2024-02-19 14:03:52,306 - \u001b[0;30;42mINFO\u001b[0m - No Scene or Well information found. Try to read XY Stage Coordinates from subblocks.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "get_planetable() got an unexpected keyword argument 'read_one_only'\n" ] } ], @@ -221,33 +227,39 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "2024-02-11 14:17:08,873 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", - "2024-02-11 14:17:08,876 - \u001b[0;30;42mINFO\u001b[0m - Reading BoundingBoxes from CZI image data.\n", - "2024-02-11 14:17:08,878 - \u001b[0;30;42mINFO\u001b[0m - Reading Channel Information from CZI image data.\n", - "2024-02-11 14:17:08,878 - \u001b[0;30;42mINFO\u001b[0m - Reading Scaling from CZI image data.\n", - "2024-02-11 14:17:08,879 - \u001b[0;30;42mINFO\u001b[0m - Reading Objective Information from CZI image data.\n", - "2024-02-11 14:17:08,880 - \u001b[0;30;42mINFO\u001b[0m - Reading Detector Information from CZI image data.\n", - "2024-02-11 14:17:08,881 - \u001b[0;30;42mINFO\u001b[0m - Reading Microscope Information from CZI image data.\n", - "2024-02-11 14:17:08,882 - \u001b[0;30;42mINFO\u001b[0m - Reading SampleCarrier Information from CZI image data.\n", - "2024-02-11 14:17:08,883 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", - "2024-02-11 14:17:08,884 - \u001b[0;30;42mINFO\u001b[0m - No Scene or Well information found. Try to read XY Stage Coordinates from subblocks.\n", - "2024-02-11 14:17:09,118 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", - "2024-02-11 14:17:09,126 - \u001b[0;30;42mINFO\u001b[0m - Reading additional Metedata from CZI image data.\n", - "2024-02-11 14:17:09,126 - \u001b[0;30;42mINFO\u001b[0m - No Layers information found.\n", - "2024-02-11 14:17:09,127 - \u001b[0;30;42mINFO\u001b[0m - Reading AttachmentImages from CZI image data.\n" + "2024-02-19 14:03:55,340 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", + "2024-02-19 14:03:55,344 - \u001b[0;30;42mINFO\u001b[0m - Reading BoundingBoxes from CZI image data.\n", + "2024-02-19 14:03:55,345 - \u001b[0;30;42mINFO\u001b[0m - Reading Channel Information from CZI image data.\n", + "2024-02-19 14:03:55,346 - \u001b[0;30;42mINFO\u001b[0m - Reading Scaling from CZI image data.\n", + "2024-02-19 14:03:55,346 - \u001b[0;30;42mINFO\u001b[0m - Reading Objective Information from CZI image data.\n", + "2024-02-19 14:03:55,347 - \u001b[0;30;42mINFO\u001b[0m - Reading Detector Information from CZI image data.\n", + "2024-02-19 14:03:55,347 - \u001b[0;30;42mINFO\u001b[0m - Reading Microscope Information from CZI image data.\n", + "2024-02-19 14:03:55,348 - \u001b[0;30;42mINFO\u001b[0m - Reading SampleCarrier Information from CZI image data.\n", + "2024-02-19 14:03:55,350 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", + "2024-02-19 14:03:55,350 - \u001b[0;30;42mINFO\u001b[0m - No Scene or Well information found. Try to read XY Stage Coordinates from subblocks.\n", + "2024-02-19 14:03:55,351 - \u001b[0;30;42mINFO\u001b[0m - Reading additional Metedata from CZI image data.\n", + "2024-02-19 14:03:55,351 - \u001b[0;30;42mINFO\u001b[0m - No Layers information found.\n", + "2024-02-19 14:03:55,352 - \u001b[0;30;42mINFO\u001b[0m - Reading AttachmentImages from CZI image data.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "get_planetable() got an unexpected keyword argument 'read_one_only'\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "818184c9a2de4917b0cb1393db30070d", + "model_id": "8db9ed29bf134b49b5a7f330bde54704", "version_major": 2, "version_minor": 0 }, @@ -279,14 +291,14 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "XML File written to: E:\\Github\\czitools\\data\\CellDivision_T10_Z15_CH2_DCV_small_CZI_MetaData.xml\n" + "XML File written to: F:\\Github\\czitools\\data\\CellDivision_T10_Z15_CH2_DCV_small_CZI_MetaData.xml\n" ] } ], @@ -298,21 +310,27 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 12, "metadata": {}, "outputs": [ { - "name": "stderr", - "output_type": "stream", - "text": [ - "2024-02-11 14:17:24,800 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", - "2024-02-11 14:17:25,318 - \u001b[0;30;42mINFO\u001b[0m - PlaneTable saved as CSV file: E:\\Github\\czitools\\data\\CellDivision_T10_Z15_CH2_DCV_small_planetable.csv\n" - ] + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "f522933a565a4983aca4c70411d25073", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Reading sublocks planes: 0 2Dplanes [00:00, ? 2Dplanes/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" }, { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "6b91d698bf86434ea0aedfb56da6dfd5", + "model_id": "320d5015c2f4444ea0725e8423270a5c", "version_major": 2, "version_minor": 0 }, @@ -322,31 +340,23 @@ }, "metadata": {}, "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "PlaneTable CSV File written to: E:\\Github\\czitools\\data\\CellDivision_T10_Z15_CH2_DCV_small_planetable.csv\n" - ] } ], "source": [ "# get the planetable for the CZI file\n", - "pt, csvfile = misc_tools.get_planetable(filepath,\n", + "pt = misc_tools.get_planetable(filepath,\n", " norm_time=True,\n", - " savetable=True,\n", - " separator=',',\n", - " index=True)\n", + " pt_complete=True,\n", + " t=0,\n", + " c=0,\n", + " z=0)\n", "\n", "# create a ipywdiget to show the dataframe with the metadata\n", "wd2 = widgets.Output(layout={\"scrollY\": \"auto\", \"height\": \"300px\"})\n", "\n", "with wd2:\n", " display(HTML(pt.to_html()))\n", - "display(widgets.VBox(children=[wd2]))\n", - "\n", - "print(\"PlaneTable CSV File written to:\", csvfile)" + "display(widgets.VBox(children=[wd2]))" ] } ], diff --git a/demo/notebooks/read_czi_pixeldata.ipynb b/demo/notebooks/read_czi_pixeldata.ipynb index 0784423..30d2d7c 100644 --- a/demo/notebooks/read_czi_pixeldata.ipynb +++ b/demo/notebooks/read_czi_pixeldata.ipynb @@ -28,14 +28,14 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "# import the required libraries\n", - "from czitools import metadata_tools as czimd\n", - "from czitools import read_tools as czird\n", - "from czitools import misc_tools\n", + "from czitools.metadata_tools import czi_metadata as czimd\n", + "from czitools.read_tools import read_tools as czird\n", + "from czitools.utils import misc\n", "from ipyfilechooser import FileChooser\n", "from IPython.display import display, HTML\n", "from pathlib import Path\n", @@ -46,7 +46,7 @@ "import ipywidgets as widgets\n", "\n", "if not IN_COLAB:\n", - " from czitools import napari_tools\n", + " from czitools.napari_tools import napari_tools\n", " import napari\n" ] }, @@ -92,7 +92,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "0cedbdabce8d4b068e40686660d520c5", + "model_id": "1a6ebc6c2b3647a9b4e87b0ba6b0beee", "version_major": 2, "version_minor": 0 }, @@ -125,7 +125,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 7, "metadata": { "pycharm": { "name": "#%%\n" @@ -151,33 +151,52 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "2024-02-11 14:58:42,808 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", - "2024-02-11 14:58:42,810 - \u001b[0;30;42mINFO\u001b[0m - Reading BoundingBoxes from CZI image data.\n", - "2024-02-11 14:58:42,811 - \u001b[0;30;42mINFO\u001b[0m - Reading Channel Information from CZI image data.\n", - "2024-02-11 14:58:42,812 - \u001b[0;30;42mINFO\u001b[0m - Reading Scaling from CZI image data.\n", - "2024-02-11 14:58:42,813 - \u001b[0;30;42mINFO\u001b[0m - Reading Objective Information from CZI image data.\n", - "2024-02-11 14:58:42,814 - \u001b[0;30;42mINFO\u001b[0m - Reading Detector Information from CZI image data.\n", - "2024-02-11 14:58:42,815 - \u001b[0;30;42mINFO\u001b[0m - Reading Microscope Information from CZI image data.\n", - "2024-02-11 14:58:42,815 - \u001b[0;30;42mINFO\u001b[0m - Reading SampleCarrier Information from CZI image data.\n", - "2024-02-11 14:58:42,817 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", - "2024-02-11 14:58:42,818 - \u001b[0;30;42mINFO\u001b[0m - No Scene or Well information found. Try to read XY Stage Coordinates from subblocks.\n", - "2024-02-11 14:58:43,047 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", - "2024-02-11 14:58:43,054 - \u001b[0;30;42mINFO\u001b[0m - Reading additional Metedata from CZI image data.\n", - "2024-02-11 14:58:43,054 - \u001b[0;30;42mINFO\u001b[0m - No Layers information found.\n", - "2024-02-11 14:58:43,055 - \u001b[0;30;42mINFO\u001b[0m - Reading AttachmentImages from CZI image data.\n" + "2024-05-10 21:33:39,664 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", + "2024-05-10 21:33:39,666 - \u001b[0;30;42mINFO\u001b[0m - Reading BoundingBoxes from CZI image data.\n", + "2024-05-10 21:33:39,667 - \u001b[0;30;42mINFO\u001b[0m - Reading Channel Information from CZI image data.\n", + "2024-05-10 21:33:39,668 - \u001b[0;30;42mINFO\u001b[0m - Reading Scaling from CZI image data.\n", + "2024-05-10 21:33:39,669 - \u001b[0;30;42mINFO\u001b[0m - Reading Objective Information from CZI image data.\n", + "2024-05-10 21:33:39,670 - \u001b[0;30;42mINFO\u001b[0m - Reading Detector Information from CZI image data.\n", + "2024-05-10 21:33:39,671 - \u001b[0;30;42mINFO\u001b[0m - Reading Microscope Information from CZI image data.\n", + "2024-05-10 21:33:39,671 - \u001b[0;30;42mINFO\u001b[0m - Reading SampleCarrier Information from CZI image data.\n", + "2024-05-10 21:33:39,672 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", + "2024-05-10 21:33:39,674 - \u001b[0;30;42mINFO\u001b[0m - No Scene or Well information found. Try to read XY Stage Coordinates from subblocks.\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "f22fc188b69d471fbddeb8440b4ba3c6", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Reading sublocks planes: 0 2Dplanes [00:00, ? 2Dplanes/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2024-05-10 21:33:39,698 - \u001b[0;30;42mINFO\u001b[0m - Reading additional Metedata from CZI image data.\n", + "2024-05-10 21:33:39,699 - \u001b[0;30;42mINFO\u001b[0m - No Layers information found.\n", + "2024-05-10 21:33:39,700 - \u001b[0;30;42mINFO\u001b[0m - Reading AttachmentImages from CZI image data.\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "a9ca488ce02744298760e3a7542c5670", + "model_id": "e257485c86464f258801db4936c34874", "version_major": 2, "version_minor": 0 }, @@ -197,7 +216,7 @@ "mdict = czimd.create_md_dict_red(mdata, sort=False, remove_none=True)\n", "\n", "# convert metadata dictionary to a pandas dataframe\n", - "mdframe = misc_tools.md2dataframe(mdict)\n", + "mdframe = misc.md2dataframe(mdict)\n", "\n", "# create a ipywdiget to show the dataframe with the metadata\n", "wd = widgets.Output(layout={\"scrollY\": \"auto\", \"height\": \"300px\"})\n", @@ -209,33 +228,52 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "2024-02-11 14:58:53,773 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", - "2024-02-11 14:58:53,776 - \u001b[0;30;42mINFO\u001b[0m - Reading BoundingBoxes from CZI image data.\n", - "2024-02-11 14:58:53,777 - \u001b[0;30;42mINFO\u001b[0m - Reading Channel Information from CZI image data.\n", - "2024-02-11 14:58:53,778 - \u001b[0;30;42mINFO\u001b[0m - Reading Scaling from CZI image data.\n", - "2024-02-11 14:58:53,779 - \u001b[0;30;42mINFO\u001b[0m - Reading Objective Information from CZI image data.\n", - "2024-02-11 14:58:53,781 - \u001b[0;30;42mINFO\u001b[0m - Reading Detector Information from CZI image data.\n", - "2024-02-11 14:58:53,782 - \u001b[0;30;42mINFO\u001b[0m - Reading Microscope Information from CZI image data.\n", - "2024-02-11 14:58:53,783 - \u001b[0;30;42mINFO\u001b[0m - Reading SampleCarrier Information from CZI image data.\n", - "2024-02-11 14:58:53,786 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", - "2024-02-11 14:58:53,788 - \u001b[0;30;42mINFO\u001b[0m - No Scene or Well information found. Try to read XY Stage Coordinates from subblocks.\n", - "2024-02-11 14:58:53,992 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", - "2024-02-11 14:58:54,000 - \u001b[0;30;42mINFO\u001b[0m - Reading additional Metedata from CZI image data.\n", - "2024-02-11 14:58:54,002 - \u001b[0;30;42mINFO\u001b[0m - No Layers information found.\n", - "2024-02-11 14:58:54,002 - \u001b[0;30;42mINFO\u001b[0m - Reading AttachmentImages from CZI image data.\n" + "2024-05-10 21:33:46,397 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", + "2024-05-10 21:33:46,399 - \u001b[0;30;42mINFO\u001b[0m - Reading BoundingBoxes from CZI image data.\n", + "2024-05-10 21:33:46,400 - \u001b[0;30;42mINFO\u001b[0m - Reading Channel Information from CZI image data.\n", + "2024-05-10 21:33:46,401 - \u001b[0;30;42mINFO\u001b[0m - Reading Scaling from CZI image data.\n", + "2024-05-10 21:33:46,402 - \u001b[0;30;42mINFO\u001b[0m - Reading Objective Information from CZI image data.\n", + "2024-05-10 21:33:46,403 - \u001b[0;30;42mINFO\u001b[0m - Reading Detector Information from CZI image data.\n", + "2024-05-10 21:33:46,403 - \u001b[0;30;42mINFO\u001b[0m - Reading Microscope Information from CZI image data.\n", + "2024-05-10 21:33:46,404 - \u001b[0;30;42mINFO\u001b[0m - Reading SampleCarrier Information from CZI image data.\n", + "2024-05-10 21:33:46,406 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", + "2024-05-10 21:33:46,407 - \u001b[0;30;42mINFO\u001b[0m - No Scene or Well information found. Try to read XY Stage Coordinates from subblocks.\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "84edb193373145f2b00b2726d2438111", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Reading sublocks planes: 0 2Dplanes [00:00, ? 2Dplanes/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2024-05-10 21:33:46,427 - \u001b[0;30;42mINFO\u001b[0m - Reading additional Metedata from CZI image data.\n", + "2024-05-10 21:33:46,428 - \u001b[0;30;42mINFO\u001b[0m - No Layers information found.\n", + "2024-05-10 21:33:46,428 - \u001b[0;30;42mINFO\u001b[0m - Reading AttachmentImages from CZI image data.\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "267e99db454240b0834f02fee8bd8578", + "model_id": "59bcbc951a3d43b3a7e2dea40cffef7f", "version_major": 2, "version_minor": 0 }, @@ -267,29 +305,30 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "2024-01-08 15:36:42,327 - \u001b[0;30;42mINFO\u001b[0m - Adding Channel: LED555\n", - "08-Jan-24 15:36:42 - czitools.logger - INFO - Adding Channel: LED555\n", - "2024-01-08 15:36:42,329 - \u001b[0;30;42mINFO\u001b[0m - Shape Channel: 0 , (1, 10, 1, 15, 256, 256)\n", - "08-Jan-24 15:36:42 - czitools.logger - INFO - Shape Channel: 0 , (1, 10, 1, 15, 256, 256)\n", - "2024-01-08 15:36:42,330 - \u001b[0;30;42mINFO\u001b[0m - Scaling Factors: [1.0, 1.0, 1.0, 3.5365329999999995, 1.0, 1.0]\n", - "08-Jan-24 15:36:42 - czitools.logger - INFO - Scaling Factors: [1.0, 1.0, 1.0, 3.5365329999999995, 1.0, 1.0]\n", - "2024-01-08 15:36:42,331 - \u001b[0;30;42mINFO\u001b[0m - Display Scaling from CZI for CH: 0 Min-Max: 0.0-4902.0\n", - "08-Jan-24 15:36:42 - czitools.logger - INFO - Display Scaling from CZI for CH: 0 Min-Max: 0.0-4902.0\n", - "2024-01-08 15:36:42,569 - \u001b[0;30;42mINFO\u001b[0m - Adding Channel: LED470\n", - "08-Jan-24 15:36:42 - czitools.logger - INFO - Adding Channel: LED470\n", - "2024-01-08 15:36:42,570 - \u001b[0;30;42mINFO\u001b[0m - Shape Channel: 1 , (1, 10, 1, 15, 256, 256)\n", - "08-Jan-24 15:36:42 - czitools.logger - INFO - Shape Channel: 1 , (1, 10, 1, 15, 256, 256)\n", - "2024-01-08 15:36:42,571 - \u001b[0;30;42mINFO\u001b[0m - Scaling Factors: [1.0, 1.0, 1.0, 3.5365329999999995, 1.0, 1.0]\n", - "08-Jan-24 15:36:42 - czitools.logger - INFO - Scaling Factors: [1.0, 1.0, 1.0, 3.5365329999999995, 1.0, 1.0]\n", - "2024-01-08 15:36:42,572 - \u001b[0;30;42mINFO\u001b[0m - Display Scaling from CZI for CH: 1 Min-Max: 0.0-22360.0\n", - "08-Jan-24 15:36:42 - czitools.logger - INFO - Display Scaling from CZI for CH: 1 Min-Max: 0.0-22360.0\n" + "Invalid schema for package 'ome-types', please run 'npe2 validate ome-types' to check for manifest errors.\n", + "2024-05-10 21:34:01,917 - \u001b[0;30;42mINFO\u001b[0m - Adding Channel: LED555\n", + "10-May-24 21:34:01 - czitools.utils.logger - INFO - Adding Channel: LED555\n", + "2024-05-10 21:34:01,919 - \u001b[0;30;42mINFO\u001b[0m - Shape Channel: 0 , (1, 3, 1, 5, 170, 240)\n", + "10-May-24 21:34:01 - czitools.utils.logger - INFO - Shape Channel: 0 , (1, 3, 1, 5, 170, 240)\n", + "2024-05-10 21:34:01,921 - \u001b[0;30;42mINFO\u001b[0m - Scaling Factors: [1.0, 1.0, 1.0, 3.5195159999999994, 1.0, 1.0]\n", + "10-May-24 21:34:01 - czitools.utils.logger - INFO - Scaling Factors: [1.0, 1.0, 1.0, 3.5195159999999994, 1.0, 1.0]\n", + "2024-05-10 21:34:01,923 - \u001b[0;30;42mINFO\u001b[0m - Display Scaling from CZI for CH: 0 Min-Max: 0.0-3921.0\n", + "10-May-24 21:34:01 - czitools.utils.logger - INFO - Display Scaling from CZI for CH: 0 Min-Max: 0.0-3921.0\n", + "2024-05-10 21:34:02,471 - \u001b[0;30;42mINFO\u001b[0m - Adding Channel: LED470\n", + "10-May-24 21:34:02 - czitools.utils.logger - INFO - Adding Channel: LED470\n", + "2024-05-10 21:34:02,473 - \u001b[0;30;42mINFO\u001b[0m - Shape Channel: 1 , (1, 3, 1, 5, 170, 240)\n", + "10-May-24 21:34:02 - czitools.utils.logger - INFO - Shape Channel: 1 , (1, 3, 1, 5, 170, 240)\n", + "2024-05-10 21:34:02,476 - \u001b[0;30;42mINFO\u001b[0m - Scaling Factors: [1.0, 1.0, 1.0, 3.5195159999999994, 1.0, 1.0]\n", + "10-May-24 21:34:02 - czitools.utils.logger - INFO - Scaling Factors: [1.0, 1.0, 1.0, 3.5195159999999994, 1.0, 1.0]\n", + "2024-05-10 21:34:02,478 - \u001b[0;30;42mINFO\u001b[0m - Display Scaling from CZI for CH: 1 Min-Max: 0.0-16368.0\n", + "10-May-24 21:34:02 - czitools.utils.logger - INFO - Display Scaling from CZI for CH: 1 Min-Max: 0.0-16368.0\n" ] } ], diff --git a/demo/notebooks/read_czi_pixeldata_simple.ipynb b/demo/notebooks/read_czi_pixeldata_simple.ipynb index 35eebe0..aa35faa 100644 --- a/demo/notebooks/read_czi_pixeldata_simple.ipynb +++ b/demo/notebooks/read_czi_pixeldata_simple.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 47, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -15,7 +15,7 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -27,14 +27,14 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "# import the required libraries\n", - "from czitools import metadata_tools as czimd\n", - "from czitools import read_tools as czird\n", - "from czitools import misc_tools\n", + "from czitools.metadata_tools import czi_metadata as czimd\n", + "from czitools.read_tools import read_tools as czird\n", + "from czitools.utils import misc\n", "from IPython.display import display, HTML\n", "from pathlib import Path\n", "import os\n", @@ -42,7 +42,7 @@ "import ipywidgets as widgets\n", "\n", "if not IN_COLAB:\n", - " from czitools import napari_tools\n", + " from czitools.napari_tools import napari_tools\n", " import napari\n" ] }, diff --git a/demo/notebooks/read_czi_segment_voroni_otsu.ipynb b/demo/notebooks/read_czi_segment_voroni_otsu.ipynb index 93a475d..0827f25 100644 --- a/demo/notebooks/read_czi_segment_voroni_otsu.ipynb +++ b/demo/notebooks/read_czi_segment_voroni_otsu.ipynb @@ -22,7 +22,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -35,7 +35,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -49,7 +49,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": { "collapsed": false, "jupyter": { @@ -63,8 +63,8 @@ "outputs": [], "source": [ "# import the required libraries\n", - "from czitools import metadata_tools as czimd\n", - "from czitools import misc_tools\n", + "from czitools.metadata_tools import czi_metadata as czimd\n", + "from czitools.utils import misc\n", "from pylibCZIrw import czi as pyczi\n", "from pathlib import Path\n", "import os\n", @@ -79,7 +79,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" @@ -130,7 +130,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -160,17 +160,9 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Selected FilePath: F:\\Github\\czitools\\data\\w96_A1+A2.czi\n" - ] - } - ], + "outputs": [], "source": [ "if IN_COLAB:\n", " filepath = os.path.join(os.getcwd(), \"data/w96_A1+A2.czi\")\n", @@ -184,22 +176,13 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Available OpenCL devices:['NVIDIA RTX A3000 Laptop GPU', 'Intel(R) UHD Graphics']\n", - "Used GPU: \n" - ] - } - ], + "outputs": [], "source": [ "if IN_COLAB:\n", " # see: https://forum.image.sc/t/stackview-pyclesperanto-prototype-demo-on-colab-cant-use-gpu/80145/4?u=sebi06\n", @@ -217,7 +200,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" @@ -240,7 +223,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" @@ -263,76 +246,33 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2024-01-08 15:39:02,841 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", - "2024-01-08 15:39:02,845 - \u001b[0;30;42mINFO\u001b[0m - Reading BoundingBoxes from CZI image data.\n", - "2024-01-08 15:39:02,846 - \u001b[0;30;42mINFO\u001b[0m - Reading Channel Information from CZI image data.\n", - "2024-01-08 15:39:02,846 - \u001b[0;30;42mINFO\u001b[0m - Reading Scaling from CZI image data.\n", - "2024-01-08 15:39:02,847 - \u001b[0;30;42mINFO\u001b[0m - No Z-Scaling found. Using default = 1.0 [micron].\n", - "2024-01-08 15:39:02,847 - \u001b[0;30;42mINFO\u001b[0m - Reading Objective Information from CZI image data.\n", - "2024-01-08 15:39:02,848 - \u001b[0;30;42mINFO\u001b[0m - Reading Detector Information from CZI image data.\n", - "2024-01-08 15:39:02,849 - \u001b[0;30;42mINFO\u001b[0m - Reading Microscope Information from CZI image data.\n", - "2024-01-08 15:39:02,849 - \u001b[0;30;42mINFO\u001b[0m - Reading SampleCarrier Information from CZI image data.\n", - "2024-01-08 15:39:02,849 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", - "2024-01-08 15:39:02,850 - \u001b[0;30;42mINFO\u001b[0m - Reading additional Metedata from CZI image data.\n", - "2024-01-08 15:39:02,851 - \u001b[0;30;42mINFO\u001b[0m - Reading AttachmentImages from CZI image data.\n", - "2024-01-08 15:39:02,852 - \u001b[0;30;42mINFO\u001b[0m - Attachment SlidePreview found.\n" - ] - } - ], + "outputs": [], "source": [ "# get the complete metadata at once as one big class\n", "mdata = czimd.CziMetadata(filepath)\n", "\n", "# check if dimensions are None (because they do not exist for that image)\n", - "size_c = misc_tools.check_dimsize(mdata.image.SizeC, set2value=1)\n", - "size_z = misc_tools.check_dimsize(mdata.image.SizeZ, set2value=1)\n", - "size_t = misc_tools.check_dimsize(mdata.image.SizeT, set2value=1)\n", - "size_s = misc_tools.check_dimsize(mdata.image.SizeS, set2value=1)" + "size_c = misc.check_dimsize(mdata.image.SizeC, set2value=1)\n", + "size_z = misc.check_dimsize(mdata.image.SizeZ, set2value=1)\n", + "size_t = misc.check_dimsize(mdata.image.SizeT, set2value=1)\n", + "size_s = misc.check_dimsize(mdata.image.SizeS, set2value=1)" ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "29b3a1241b3e465fa7b22e13de537f1a", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - " 0%| | 0/2 [00:00\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
WellIdWell_ColIdWell_RowIdSTZCareacentroid-0centroid-1max_intensitymean_intensitymin_intensitybbox-0bbox-1bbox-2bbox-3
0A1110000218.066.52752315.9541281295.01091.706422964.054118023
1A1110000431.01121.25754116.0116013247.01988.218097830.011087113728
2A1110000360.0149.44166729.6527785566.03049.311111843.01362116439
3A1110000313.0266.40255651.6325881567.01131.194888964.02563927964
4A1110000471.0714.56687963.9087057089.03380.713376731.07044972878
\n", - "" - ], - "text/plain": [ - " WellId Well_ColId Well_RowId S T Z C area centroid-0 centroid-1 \\\n", - "0 A1 1 1 0 0 0 0 218.0 66.527523 15.954128 \n", - "1 A1 1 1 0 0 0 0 431.0 1121.257541 16.011601 \n", - "2 A1 1 1 0 0 0 0 360.0 149.441667 29.652778 \n", - "3 A1 1 1 0 0 0 0 313.0 266.402556 51.632588 \n", - "4 A1 1 1 0 0 0 0 471.0 714.566879 63.908705 \n", - "\n", - " max_intensity mean_intensity min_intensity bbox-0 bbox-1 bbox-2 \\\n", - "0 1295.0 1091.706422 964.0 54 11 80 \n", - "1 3247.0 1988.218097 830.0 1108 7 1137 \n", - "2 5566.0 3049.311111 843.0 136 21 164 \n", - "3 1567.0 1131.194888 964.0 256 39 279 \n", - "4 7089.0 3380.713376 731.0 704 49 728 \n", - "\n", - " bbox-3 \n", - "0 23 \n", - "1 28 \n", - "2 39 \n", - "3 64 \n", - "4 78 " - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# reorder dataframe with single objects and show some results\n", "new_order = list(results.columns[-7:]) + list(results.columns[:-7])\n", @@ -609,7 +376,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.5" + "version": "3.10.13" }, "toc": { "base_numbering": 1 diff --git a/demo/notebooks/save_with_ZSTD_compression.ipynb b/demo/notebooks/save_with_ZSTD_compression.ipynb index 51bc441..d05b153 100644 --- a/demo/notebooks/save_with_ZSTD_compression.ipynb +++ b/demo/notebooks/save_with_ZSTD_compression.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -15,7 +15,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -27,12 +27,12 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ - "from czitools import metadata_tools as czimd\n", - "from czitools import read_tools\n", + "from czitools.metadata_tools import czi_metadata as czimd\n", + "from czitools.read_tools import read_tools\n", "from pylibCZIrw import czi as pyczi\n", "import os\n", "from tqdm.contrib.itertools import product\n", @@ -43,7 +43,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -73,33 +73,52 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "2024-02-11 14:40:43,792 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", - "2024-02-11 14:40:43,805 - \u001b[0;30;42mINFO\u001b[0m - Reading BoundingBoxes from CZI image data.\n", - "2024-02-11 14:40:43,806 - \u001b[0;30;42mINFO\u001b[0m - Reading Channel Information from CZI image data.\n", - "2024-02-11 14:40:43,807 - \u001b[0;30;42mINFO\u001b[0m - Reading Scaling from CZI image data.\n", - "2024-02-11 14:40:43,808 - \u001b[0;30;42mINFO\u001b[0m - Reading Objective Information from CZI image data.\n", - "2024-02-11 14:40:43,810 - \u001b[0;30;42mINFO\u001b[0m - Reading Detector Information from CZI image data.\n", - "2024-02-11 14:40:43,811 - \u001b[0;30;42mINFO\u001b[0m - Reading Microscope Information from CZI image data.\n", - "2024-02-11 14:40:43,811 - \u001b[0;30;42mINFO\u001b[0m - Reading SampleCarrier Information from CZI image data.\n", - "2024-02-11 14:40:43,812 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", - "2024-02-11 14:40:43,813 - \u001b[0;30;42mINFO\u001b[0m - No Scene or Well information found. Try to read XY Stage Coordinates from subblocks.\n", - "2024-02-11 14:40:44,007 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", - "2024-02-11 14:40:44,016 - \u001b[0;30;42mINFO\u001b[0m - Reading additional Metedata from CZI image data.\n", - "2024-02-11 14:40:44,017 - \u001b[0;30;42mINFO\u001b[0m - No Layers information found.\n", - "2024-02-11 14:40:44,018 - \u001b[0;30;42mINFO\u001b[0m - Reading AttachmentImages from CZI image data.\n" + "2024-05-10 21:38:06,288 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", + "2024-05-10 21:38:06,292 - \u001b[0;30;42mINFO\u001b[0m - Reading BoundingBoxes from CZI image data.\n", + "2024-05-10 21:38:06,293 - \u001b[0;30;42mINFO\u001b[0m - Reading Channel Information from CZI image data.\n", + "2024-05-10 21:38:06,295 - \u001b[0;30;42mINFO\u001b[0m - Reading Scaling from CZI image data.\n", + "2024-05-10 21:38:06,296 - \u001b[0;30;42mINFO\u001b[0m - Reading Objective Information from CZI image data.\n", + "2024-05-10 21:38:06,296 - \u001b[0;30;42mINFO\u001b[0m - Reading Detector Information from CZI image data.\n", + "2024-05-10 21:38:06,297 - \u001b[0;30;42mINFO\u001b[0m - Reading Microscope Information from CZI image data.\n", + "2024-05-10 21:38:06,298 - \u001b[0;30;42mINFO\u001b[0m - Reading SampleCarrier Information from CZI image data.\n", + "2024-05-10 21:38:06,299 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n", + "2024-05-10 21:38:06,300 - \u001b[0;30;42mINFO\u001b[0m - No Scene or Well information found. Try to read XY Stage Coordinates from subblocks.\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "98c22a6ad5fb454e9d54159da01f6310", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Reading sublocks planes: 0 2Dplanes [00:00, ? 2Dplanes/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2024-05-10 21:38:06,366 - \u001b[0;30;42mINFO\u001b[0m - Reading additional Metedata from CZI image data.\n", + "2024-05-10 21:38:06,368 - \u001b[0;30;42mINFO\u001b[0m - No Layers information found.\n", + "2024-05-10 21:38:06,369 - \u001b[0;30;42mINFO\u001b[0m - Reading AttachmentImages from CZI image data.\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "690c631b8434417188193738a621d7cc", + "model_id": "d2e859e3a19f4ef0b10147408fcc3b5a", "version_major": 2, "version_minor": 0 }, @@ -140,7 +159,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -158,13 +177,13 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "45626c538f24458b96069d21d66273d4", + "model_id": "7c8386c6d029408bbede478c7c049080", "version_major": 2, "version_minor": 0 }, @@ -198,13 +217,13 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "697b95f691f04684a7546f9c6cc30b91", + "model_id": "d4a60ea013874b0cacbfd6e76b2d5fa8", "version_major": 2, "version_minor": 0 }, @@ -237,7 +256,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 14, "metadata": {}, "outputs": [ { diff --git a/demo/notebooks/show_czi_surface.ipynb b/demo/notebooks/show_czi_surface.ipynb index 5d19f42..3d059f9 100644 --- a/demo/notebooks/show_czi_surface.ipynb +++ b/demo/notebooks/show_czi_surface.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -15,7 +15,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -31,7 +31,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 5, "metadata": { "pycharm": { "name": "#%%\n" @@ -71,7 +71,7 @@ "source": [ "import pandas as pd\n", "import plotly.offline as pyo\n", - "from czitools import misc_tools\n", + "from czitools.utils import misc\n", "from ipyfilechooser import FileChooser\n", "from IPython.display import display\n", "from pathlib import Path\n", @@ -88,7 +88,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -214,7 +214,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -318,7 +318,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -348,7 +348,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 9, "metadata": { "pycharm": { "name": "#%%\n" @@ -358,7 +358,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "054b13f6543048d0af4b0480c84e004b", + "model_id": "9b59ca023bc74ce8aa0ad57b5ec43987", "version_major": 2, "version_minor": 0 }, @@ -391,7 +391,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -413,7 +413,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 12, "metadata": { "pycharm": { "name": "#%%\n" @@ -421,11 +421,18 @@ }, "outputs": [ { - "name": "stderr", - "output_type": "stream", - "text": [ - "2024-02-11 14:38:01,832 - \u001b[0;30;42mINFO\u001b[0m - Reading Dimensions from CZI image data.\n" - ] + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "5308010b0f32445eb6b9d97b89fecd10", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Reading sublocks planes: 0 2Dplanes [00:00, ? 2Dplanes/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" }, { "data": { @@ -532,20 +539,20 @@ "0 148118 96242 640 640 " ] }, - "execution_count": 9, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# read the data from CZICSV file\n", - "planetable, csvfile = misc_tools.get_planetable(filepath)\n", + "planetable = misc.get_planetable(filepath)\n", "planetable[:3]" ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 13, "metadata": { "pycharm": { "name": "#%%\n" @@ -569,7 +576,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 15, "metadata": { "pycharm": { "name": "#%%\n" @@ -719,14 +726,14 @@ "0 193118 78118 640 640 " ] }, - "execution_count": 14, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# filter the planetable for S, T, Z and C entries\n", - "planetable_filtered = misc_tools.filter_planetable(planetable, T=0, Z=0, C=0)\n", + "planetable_filtered = misc.filter_planetable(planetable, T=0, Z=0, C=0)\n", "planetable_filtered[:5]" ] }, diff --git a/demo/scripts/read_attachment_images.py b/demo/scripts/read_attachment_images.py index 8161d11..0188bcb 100644 --- a/demo/scripts/read_attachment_images.py +++ b/demo/scripts/read_attachment_images.py @@ -1,26 +1,27 @@ -from czitools import read_tools -from czitools import metadata_tools -from czitools.read_tools import AttachmentType +from czitools.read_tools import read_tools from pathlib import Path import os +from czitools.metadata_tools.czi_metadata import CziMetadata +from czitools.metadata_tools.attachment import CziAttachments +from czitools.metadata_tools.helper import AttachmentType # adapt to your needs defaultdir = Path(Path(__file__).resolve().parents[2]) / "data" os.chdir(defaultdir) # define the file location -# filepath = r"w96_A1+A2.czi" +filepath = r"w96_A1+A2.czi" # filepath = r"Tumor_HE_Orig_small.czi" -filepath = r"CellDivision_T=10_Z=15_CH=2_DCV_small.czi" +# filepath = r"CellDivision_T=10_Z=15_CH=2_DCV_small.czi" -# read all metadata and check for the attachment images -md = metadata_tools.CziMetadata(filepath) +# read all metadata_tools and check for the attachment images +md = CziMetadata(filepath) print(f"CZI Image has Label {md.attachments.has_label}") print(f"CZI Image has SlidePreview {md.attachments.has_preview}") print(f"CZI Image has Prescan {md.attachments.has_prescan}") # get info about attachments only -attachments = metadata_tools.CziAttachments(filepath) +attachments = CziAttachments(filepath) for k, v in attachments.__dict__.items(): print(k, v) diff --git a/demo/scripts/read_czi_aicsimageio.py b/demo/scripts/read_czi_aicsimageio.py index c4d822d..83b4c21 100644 --- a/demo/scripts/read_czi_aicsimageio.py +++ b/demo/scripts/read_czi_aicsimageio.py @@ -10,9 +10,10 @@ ################################################################# import napari -from czitools import metadata_tools as czimd from aicsimageio import AICSImage -from czitools import misc_tools, napari_tools +from czitools.napari_tools import napari_tools +from czitools.metadata_tools import czi_metadata as czimd +from czitools.utils import misc import os from pathlib import Path @@ -20,7 +21,7 @@ defaultdir = os.path.join(Path(__file__).resolve().parents[2], "data") # open s simple dialog to select a CZI file -filepath = misc_tools.openfile( +filepath = misc.openfile( directory=defaultdir, title="Open CZI Image File", ftypename="CZI Files", @@ -28,14 +29,12 @@ ) print(filepath) -# get the complete metadata using czitools +# get the complete metadata_tools using czitools mdata = czimd.CziMetadata(filepath) -# test using AICSImageIO +# test using AICSImageIO (needs to be installed) aics_img = AICSImage(filepath, reconstruct_mosaic=True) print(aics_img.shape) -for k, v in aics_img.dims.items(): - print(k, v) # get the stack as dask array using AICSImageIO stack = aics_img.get_dask_stack() diff --git a/demo/scripts/use_metadata_tools.py b/demo/scripts/use_metadata_tools.py index d8c0024..28b58c1 100644 --- a/demo/scripts/use_metadata_tools.py +++ b/demo/scripts/use_metadata_tools.py @@ -9,26 +9,39 @@ # ################################################################# -# from pylibCZIrw import czi as pyczi -from czitools import metadata_tools as czimd -from czitools import misc_tools +from czitools.metadata_tools.czi_metadata import ( + CziMetadata, + writexml, + get_metadata_as_object, + obj2dict +) +from czitools.metadata_tools.dimension import CziDimensions +from czitools.metadata_tools.boundingbox import CziBoundingBox +from czitools.metadata_tools.channel import CziChannelInfo +from czitools.metadata_tools.scaling import CziScaling +from czitools.metadata_tools.sample import CziSampleInfo +from czitools.metadata_tools.objective import CziObjectives +from czitools.metadata_tools.microscope import CziMicroscope +from czitools.metadata_tools.add_metadata import CziAddMetaData +from czitools.metadata_tools.detector import CziDetector +from czitools.utils import misc from pathlib import Path # adapt to your needs defaultdir = Path(Path(__file__).resolve().parents[2]) / "data" # open s simple dialog to select a CZI file -filepath = misc_tools.openfile(directory=str(defaultdir), - title="Open CZI Image File", - ftypename="CZI Files", - extension="*.czi") +filepath = misc.openfile(directory=str(defaultdir), + title="Open CZI Image File", + ftypename="CZI Files", + extension="*.czi") print(filepath) -# get the metadata at once as one big class -mdata = czimd.CziMetadata(filepath) +# get the metadata_tools at once as one big class +mdata = CziMetadata(filepath) -# get only specific metadata -czi_dimensions = czimd.CziDimensions(filepath) +# get only specific metadata_tools +czi_dimensions = CziDimensions(filepath) print("SizeS: ", czi_dimensions.SizeS) print("SizeT: ", czi_dimensions.SizeT) print("SizeZ: ", czi_dimensions.SizeZ) @@ -37,37 +50,37 @@ print("SizeX: ", czi_dimensions.SizeX) # try to write XML to file -xmlfile = czimd.writexml(filepath) +xmlfile = writexml(filepath) # get info about the channels -czi_channels = czimd.CziChannelInfo(filepath) +czi_channels = CziChannelInfo(filepath) -# get the complete metadata from the CZI as one big object -czimd_complete = czimd.get_metadata_as_object(filepath) +# get the complete metadata_tools from the CZI as one big object +czimd_complete = get_metadata_as_object(filepath) # get an object containing only the dimension information -czi_dimensions = czimd.CziDimensions(filepath) +czi_dimensions = CziDimensions(filepath) print("Number of Channels:", czi_dimensions.SizeC) # get an object containing only the dimension information -czi_scale = czimd.CziScaling(filepath) +czi_scale = CziScaling(filepath) # get an object containing information about the sample -czi_sample = czimd.CziSampleInfo(filepath) +czi_sample = CziSampleInfo(filepath) # get info about the objective, the microscope and the detectors -czi_objectives = czimd.CziObjectives(filepath) -czi_detectors = czimd.CziDetector(filepath) -czi_microscope = czimd.CziMicroscope(filepath) +czi_objectives = CziObjectives(filepath) +czi_detectors = CziDetector(filepath) +czi_microscope = CziMicroscope(filepath) # get info about the sample carrier -czi_sample = czimd.CziSampleInfo(filepath) +czi_sample = CziSampleInfo(filepath) # get additional metainformation -czi_addmd = czimd.CziAddMetaData(filepath) +czi_addmd = CziAddMetaData(filepath) # get the complete data about the bounding boxes -czi_bbox = czimd.CziBoundingBox(filepath) +czi_bbox = CziBoundingBox(filepath) # show the total bounding box, box per scene, total rectangle and num. of channels print(czi_bbox.total_bounding_box) @@ -75,9 +88,9 @@ print(czi_bbox.total_rect) print(czi_bbox.total_bounding_box["C"]) -# get selected metadata as a dictionary -mdata_dict = czimd.obj2dict(mdata) +# get selected metadata_tools as a dictionary +mdata_dict = obj2dict(mdata) # and convert to pd.DataFrame -df_md = misc_tools.md2dataframe(mdata_dict) +df_md = misc.md2dataframe(mdata_dict) print(df_md) diff --git a/demo/scripts/use_read_tools.py b/demo/scripts/use_read_tools.py index 94b01d5..425761a 100644 --- a/demo/scripts/use_read_tools.py +++ b/demo/scripts/use_read_tools.py @@ -9,9 +9,9 @@ # ################################################################# -from czitools import read_tools -from czitools import napari_tools -from czitools import misc_tools +from czitools.read_tools import read_tools +from czitools.napari_tools import napari_tools +from czitools.utils import misc import napari from pathlib import Path @@ -19,7 +19,7 @@ defaultdir = Path(Path(__file__).resolve().parents[2]) / "data" # open s simple dialog to select a CZI file -filepath = misc_tools.openfile( +filepath = misc.openfile( directory=defaultdir, title="Open CZI Image File", ftypename="CZI Files", diff --git a/demo/scripts/use_write_tools.py b/demo/scripts/use_write_tools.py index 3b7c902..2f2e54d 100644 --- a/demo/scripts/use_write_tools.py +++ b/demo/scripts/use_write_tools.py @@ -9,30 +9,29 @@ # ################################################################# -from czitools import read_tools, write_tools -from czitools import metadata_tools as czimd +from czitools.read_tools import read_tools +from czitools.write_tools import write_tools +from czitools.metadata_tools import czi_metadata as czimd import napari from pathlib import Path # open s simple dialog to select a CZI file -filepath = r"data/CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi" -zarr_path = Path(filepath[:-4] + ".ome.zarr") +defaultdir = Path(Path(__file__).resolve().parents[2]) / "data" +filepath = defaultdir / "CellDivision_T3_Z5_CH2_X240_Y170.czi" +zarr_path = Path(str(filepath)[:-4] + ".ome.zarr") -# get the metadata at once as one big class +# get the metadata_tools at once as one big class mdata = czimd.CziMetadata(filepath) print("Number of Scenes: ", mdata.image.SizeS) scene_id = 0 -# return a array with dimension order STCZYX(A) -array, mdata, dim_string6d = read_tools.read_6darray( - filepath, output_order="STCZYX", use_dask=True -) - +# return a 6D array with dimension order STCZYX(A) +array, mdata = read_tools.read_6darray(filepath, use_dask=True) array = array[scene_id, ...] # write OME-ZARR using utility function zarr_path = write_tools.write_omezarr( - array, zarr_path=zarr_path, axes="tczyx", overwrite=True + array, zarr_path=str(zarr_path), axes="tczyx", overwrite=True ) if array is None: diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..aff6d5d --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +norecursedirs = .git .vscode demo .idea \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index 2fb9f95..cf0327a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = czitools -version = 0.6.0 +version = 0.7.0 author = Sebastian Rhode author_email = sebrhode@gmail.com url = https://github.com/sebi06/czitools @@ -36,28 +36,35 @@ python_requires = >=3.9 # add your package requirements here install_requires = - numpy - xmltodict - aicsimageio[all]>=4.11.0 - aicspylibczi - fsspec>=2022.8.0 - tqdm pylibCZIrw>=4.1.0 - napari + #numpy + #xmltodict + #aicsimageio[all]>=4.11.0 + aicspylibczi>=3.1.2 + #fsspec>=2022.8.0 + tqdm + #napari pandas - seaborn - plotly - matplotlib - colormap + #seaborn + #plotly + #matplotlib + #colormap + ome-zarr zarr dask python-dateutil python-box[all] - scikit-image>=0.19.3 + #scikit-image>=0.19.3 czifile - pyqtgraph + #pyqtgraph - +[options.extras_require] +all = + napari[all] # will install PyQt5 + seaborn + plotly + pyqtgraph + colormap [options.packages.find] where = src diff --git a/src/czitools/__init__.py b/src/czitools/__init__.py index 1df735c..063bea5 100644 --- a/src/czitools/__init__.py +++ b/src/czitools/__init__.py @@ -1,3 +1,3 @@ # __init__.py # version of the czitools package -__version__ = "0.6.0" +__version__ = "0.7.0" diff --git a/src/czitools/_tests/test_bounding_box.py b/src/czitools/_tests/test_bounding_box.py index 792328c..94302f6 100644 --- a/src/czitools/_tests/test_bounding_box.py +++ b/src/czitools/_tests/test_bounding_box.py @@ -1,8 +1,8 @@ -from czitools import metadata_tools as czimd +from czitools.metadata_tools import czi_metadata as czimd from pathlib import Path from pylibCZIrw import czi import pytest -from typing import List, Dict, Tuple, Optional, Type, Any, Union, Mapping +from typing import Dict, Tuple basedir = Path(__file__).resolve().parents[3] / "data" diff --git a/src/czitools/_tests/test_channelinfo.py b/src/czitools/_tests/test_channelinfo.py index dad9ef9..a5f36dc 100644 --- a/src/czitools/_tests/test_channelinfo.py +++ b/src/czitools/_tests/test_channelinfo.py @@ -1,7 +1,7 @@ from pathlib import Path -from czitools import metadata_tools as czimd +from czitools.metadata_tools import czi_metadata as czimd import pytest -from typing import List, Dict, Tuple, Optional, Type, Any, Union, Mapping +from typing import List basedir = Path(__file__).resolve().parents[3] diff --git a/src/czitools/_tests/test_czimd_box.py b/src/czitools/_tests/test_czimd_box.py index 521ae95..0f4a6fc 100644 --- a/src/czitools/_tests/test_czimd_box.py +++ b/src/czitools/_tests/test_czimd_box.py @@ -1,8 +1,7 @@ -from czitools import metadata_tools as czimd -from pathlib import Path, PurePath +from czitools.metadata_tools import czi_metadata as czimd +from pathlib import Path import pytest -from typing import List, Dict, Tuple, Optional, Type, Any, Union, Mapping - +from typing import List # adapt to your needs defaultdir = Path(__file__).resolve().parents[3] / "data" diff --git a/src/czitools/_tests/test_dimensions.py b/src/czitools/_tests/test_dimensions.py index f286758..c3560e9 100644 --- a/src/czitools/_tests/test_dimensions.py +++ b/src/czitools/_tests/test_dimensions.py @@ -1,8 +1,7 @@ - -from czitools import metadata_tools as czimd +from czitools.metadata_tools import czi_metadata as czimd from pathlib import Path import pytest -from typing import List, Dict, Tuple, Optional, Type, Any, Union, Mapping +from typing import List, Any basedir = Path(__file__).resolve().parents[3] diff --git a/src/czitools/_tests/test_info.py b/src/czitools/_tests/test_info.py index 7c3cf46..f002a61 100644 --- a/src/czitools/_tests/test_info.py +++ b/src/czitools/_tests/test_info.py @@ -1,7 +1,7 @@ -from czitools import metadata_tools as czimd +from czitools.metadata_tools import czi_metadata as czimd from pathlib import Path import pytest -from typing import List, Dict, Tuple, Optional, Type, Any, Union, Mapping +from typing import List basedir = Path(__file__).resolve().parents[3] diff --git a/src/czitools/_tests/test_instrument.py b/src/czitools/_tests/test_instrument.py index 8397437..702b073 100644 --- a/src/czitools/_tests/test_instrument.py +++ b/src/czitools/_tests/test_instrument.py @@ -1,7 +1,7 @@ -from czitools import metadata_tools as czimd +from czitools.metadata_tools import czi_metadata as czimd from pathlib import Path import pytest -from typing import List, Dict, Tuple, Optional, Type, Any, Union, Mapping +from typing import Dict basedir = Path(__file__).resolve().parents[3] @@ -25,7 +25,7 @@ ) def test_instrument(czifile: str, result_mic: Dict, result_det: Dict) -> None: - # get the filepath and the metadata + # get the filepath and the metadata_tools filepath = basedir / "data" / czifile mic = czimd.CziMicroscope(filepath) det = czimd.CziDetector(filepath) @@ -120,7 +120,7 @@ def test_instrument(czifile: str, result_mic: Dict, result_det: Dict) -> None: ) def test_objectives(czifile: str, result: Dict) -> None: - # get the filepath and the metadata + # get the filepath and the metadata_tools filepath = basedir / "data" / czifile obj = czimd.CziObjectives(filepath) diff --git a/src/czitools/_tests/test_md_general.py b/src/czitools/_tests/test_md_general.py index d699250..c6cb99e 100644 --- a/src/czitools/_tests/test_md_general.py +++ b/src/czitools/_tests/test_md_general.py @@ -1,9 +1,8 @@ - -from czitools import metadata_tools as czimd +from czitools.metadata_tools import czi_metadata as czimd from pathlib import Path import numpy as np import pytest -from typing import List, Dict, Tuple, Optional, Type, Any, Union, Mapping +from typing import Dict basedir = Path(__file__).resolve().parents[3] @@ -114,7 +113,7 @@ def test_scene_shape(czifile: str, shape: bool) -> None: assert (Path.exists(filepath) is True) - # get the complete metadata at once as one big class + # get the complete metadata_tools at once as one big class md = czimd.CziMetadata(filepath) assert (md.scene_shape_is_consistent == shape) @@ -124,7 +123,7 @@ def test_reading_czi_fresh(): filepath = basedir / r"data/A01_segSD.czi" - # get the complete metadata at once as one big class + # get the complete metadata_tools at once as one big class mdata = czimd.CziMetadata(filepath) assert (mdata.sample.well_array_names == []) diff --git a/src/czitools/_tests/test_misc.py b/src/czitools/_tests/test_misc.py index c301d05..c4ad840 100644 --- a/src/czitools/_tests/test_misc.py +++ b/src/czitools/_tests/test_misc.py @@ -1,11 +1,12 @@ -from czitools import read_tools, misc_tools +from czitools.read_tools import read_tools +from czitools.utils import misc from pathlib import Path import dask.array as da import zarr import pandas as pd import pytest import numpy as np -from typing import List, Dict, Tuple, Optional, Type, Any, Union, Mapping +from typing import List, Tuple, Optional, Union basedir = Path(__file__).resolve().parents[3] @@ -29,7 +30,7 @@ def test_slicedim(czifile: str, dimindex: int, posdim: int, shape: Tuple[int]) - # mdarray, mdata, dimstring = read_tools.read_6darray(filepath, output_order="STCZYX") mdarray, mdata = read_tools.read_6darray(filepath) - dim_array = misc_tools.slicedim(mdarray, dimindex, posdim) + dim_array = misc.slicedim(mdarray, dimindex, posdim) assert dim_array.shape == shape @@ -67,16 +68,11 @@ def test_get_planetable( planetable = pd.read_csv(filepath, sep=separator) if isczi: # read the data from CZI file - planetable, csvfile = misc_tools.get_planetable( - filepath, norm_time=True, savetable=True, separator=",", index=True + planetable = misc.get_planetable( + filepath, norm_time=True, pt_complete=True ) - assert csvfile == (basedir / "data" / csvfile).as_posix() - - # remove the file - Path.unlink(Path(csvfile)) - - planetable_filtered = misc_tools.filter_planetable(planetable, S=0, T=0, Z=0, C=0) + planetable_filtered = misc.filter_planetable(planetable, S=0, T=0, Z=0, C=0) assert planetable_filtered["xstart"].values[0] == xstart[0] # assert planetable_filtered["xstart"].values[1] == xstart[1] @@ -99,7 +95,7 @@ def test_check_dimsize(entry: Optional[int], set2value: int, result: int) -> Non Returns: None. """ - assert misc_tools.check_dimsize(entry, set2value=set2value) == result + assert misc.check_dimsize(entry, set2value=set2value) == result @pytest.mark.parametrize( @@ -129,20 +125,20 @@ def test_calc_scaling( corr_min: float, corr_max: float, ) -> None: - minv, maxv = misc_tools.calc_scaling(array, corr_min=corr_min, corr_max=corr_max) + minv, maxv = misc.calc_scaling(array, corr_min=corr_min, corr_max=corr_max) assert min_value == minv assert max_value == maxv def test_norm_columns_min(df): - result = misc_tools.norm_columns(df, colname="Time [s]", mode="min") + result = misc.norm_columns(df, colname="Time [s]", mode="min") expected = pd.DataFrame({"Time [s]": [0, 1, 2, 3], "Value": [10, 20, 30, 40]}) pd.testing.assert_frame_equal(result, expected) def test_norm_columns_max(df): - result = misc_tools.norm_columns(df, colname="Time [s]", mode="max") + result = misc.norm_columns(df, colname="Time [s]", mode="max") expected = pd.DataFrame({"Time [s]": [-3, -2, -1, 0], "Value": [10, 20, 30, 40]}) pd.testing.assert_frame_equal(result, expected) @@ -150,21 +146,21 @@ def test_norm_columns_max(df): def test_filter_planetable(planetable): # Test for scene index - result = misc_tools.filter_planetable(planetable, S=1) + result = misc.filter_planetable(planetable, S=1) assert result["Scene"].eq(1).all(), "Scene index filter failed" # Test for time index - result = misc_tools.filter_planetable(planetable, T=1) + result = misc.filter_planetable(planetable, T=1) assert result["T"].eq(1).all(), "Time index filter failed" # Test for z-plane index - result = misc_tools.filter_planetable(planetable, Z=1) + result = misc.filter_planetable(planetable, Z=1) assert result["Z"].eq(1).all(), "Z-plane index filter failed" # Test for channel index - result = misc_tools.filter_planetable(planetable, C=5) + result = misc.filter_planetable(planetable, C=5) assert result["C"].eq(5).all(), "Channel index filter failed" # Test for invalid indices - result = misc_tools.filter_planetable(planetable, S=2, T=2, Z=2, C=2) + result = misc.filter_planetable(planetable, S=2, T=2, Z=2, C=2) assert result.empty, "Invalid index filter failed" diff --git a/src/czitools/_tests/test_napari_tools.py b/src/czitools/_tests/test_napari_tools.py index 815e1e2..b27f61f 100644 --- a/src/czitools/_tests/test_napari_tools.py +++ b/src/czitools/_tests/test_napari_tools.py @@ -1,9 +1,20 @@ import pytest -from czitools import napari_tools, metadata_tools, read_tools + +try: + import napari + from czitools.napari_tools import napari_tools + from czitools.metadata_tools import czi_metadata + + NAPARI_INSTALLED = True +except (ImportError, ModuleNotFoundError) as error: + NAPARI_INSTALLED = False + + +from czitools.read_tools import read_tools import numpy as np -import napari +from czitools.metadata_tools.czi_metadata import CziMetadata from pathlib import Path -from typing import List, Dict, Tuple, Optional, Type, Any, Union, Mapping, Literal +from typing import Dict, Tuple, Literal import os # check if the test in executed as part of a GITHUB action @@ -12,6 +23,7 @@ basedir = Path(__file__).resolve().parents[3] +@pytest.mark.skipif(not NAPARI_INSTALLED, reason="Napari is not installed.") @pytest.mark.parametrize( "dim_order, sliders, expected_sliders", [ @@ -31,6 +43,7 @@ def test_rename_sliders( # exclude the test when executed inside a GITHUB action +@pytest.mark.skipif(not NAPARI_INSTALLED, reason="Napari is not installed.") @pytest.mark.skipif(IN_GITHUB_ACTIONS, reason="Test doesn't work in Github Actions.") @pytest.mark.parametrize( "czifile, num_layers, show_metadata, wdname", @@ -48,7 +61,7 @@ def test_show_image( """Test that the `show` function correctly displays a two-channel image and the metadada widgets.""" filepath = basedir / "data" / czifile - md = metadata_tools.CziMetadata(filepath) + #md = CziMetadata(filepath) array6d, mdata = read_tools.read_6darray( filepath, diff --git a/src/czitools/_tests/test_read_attachments.py b/src/czitools/_tests/test_read_attachments.py index 34ee6c3..825760f 100644 --- a/src/czitools/_tests/test_read_attachments.py +++ b/src/czitools/_tests/test_read_attachments.py @@ -1,10 +1,10 @@ -from czitools import read_tools, metadata_tools -from czitools.read_tools import AttachmentType +from czitools.read_tools import read_tools +from czitools.metadata_tools import czi_metadata +from czitools.metadata_tools.helper import AttachmentType from pathlib import Path import numpy as np import pytest -from typing import List, Dict, Tuple, Optional, Type, Any, Union, Mapping, Literal - +from typing import Tuple basedir = Path(__file__).resolve().parents[3] @@ -43,11 +43,11 @@ def test_read_attachments_images( has_prescan: bool, array_shape: Tuple[int, int, int], ) -> None: - # get the filepath and the metadata + # get the filepath and the metadata_tools filepath = basedir / "data" / cziname # get info about attachments only - attachments = metadata_tools.CziAttachments(filepath) + attachments = czi_metadata.CziAttachments(filepath) assert attachments.has_label == has_label assert attachments.has_preview == has_preview diff --git a/src/czitools/_tests/test_read_mdarray.py b/src/czitools/_tests/test_read_mdarray.py index 2305712..8dd4d15 100644 --- a/src/czitools/_tests/test_read_mdarray.py +++ b/src/czitools/_tests/test_read_mdarray.py @@ -1,4 +1,4 @@ -from czitools import read_tools +from czitools.read_tools import read_tools from pathlib import Path import dask.array as da import numpy as np diff --git a/src/czitools/_tests/test_save_planetable.py b/src/czitools/_tests/test_save_planetable.py index 2350f56..6ac6eee 100644 --- a/src/czitools/_tests/test_save_planetable.py +++ b/src/czitools/_tests/test_save_planetable.py @@ -1,7 +1,7 @@ import pandas as pd import unittest import os -from czitools import misc_tools +from czitools.utils import misc class TestSavePlanetTable(unittest.TestCase): @@ -12,7 +12,7 @@ def test_save_planetable(self): # Define the test filename test_filename = 'test.csv' # Call the save_planetable function - result = misc_tools.save_planetable( + result = misc.save_planetable( df=test_df, filename=test_filename, separator=';', index=False) # Check that the result matches the expected filename self.assertEqual(result, 'test_planetable.csv') diff --git a/src/czitools/_tests/test_scaling.py b/src/czitools/_tests/test_scaling.py index 66c8a3f..678b17c 100644 --- a/src/czitools/_tests/test_scaling.py +++ b/src/czitools/_tests/test_scaling.py @@ -1,26 +1,27 @@ -from czitools import metadata_tools as czimd +from czitools.metadata_tools import czi_metadata as czimd from pathlib import Path -from box import Box, BoxList +from box import BoxList import pytest -from typing import List, Dict, Tuple, Optional, Type, Any, Union, Mapping +from typing import List, Dict basedir = Path(__file__).resolve().parents[3] + @pytest.mark.parametrize( "czifile, results", [ - ("CellDivision_T3_Z5_CH2_X240_Y170.czi", {'X': 0.091, 'X_sf': 0.091, 'Y': 0.091, 'Y_sf': 0.091, 'Z': 0.32, 'ratio': {'xy': 1.0, 'zx': 3.516}, 'unit': 'micron', 'zoom': 1.0}), - ("Al2O3_SE_020_sp.czi", {'X': 0.028, 'X_sf': 0.028, 'Y': 0.028, 'Y_sf': 0.028, 'Z': 1.0, 'ratio': {'xy': 1.0, 'zx': 35.714}, 'unit': 'micron', 'zoom': 1.0}), - ("w96_A1+A2.czi", {'X': 0.457, 'X_sf': 0.457, 'Y': 0.457, 'Y_sf': 0.457, 'Z': 1.0, 'ratio': {'xy': 1.0, 'zx': 2.188}, 'unit': 'micron', 'zoom': 1.0}), - ("Airyscan.czi", {'X': 0.044, 'X_sf': 0.044, 'Y': 0.044, 'Y_sf': 0.044, 'Z': 1.0, 'ratio': {'xy': 1.0, 'zx': 22.727}, 'unit': 'micron', 'zoom': 1.0}), - ("newCZI_zloc.czi", {'X': 1.0, 'X_sf': 1.0, 'Y': 1.0, 'Y_sf': 1.0, 'Z': 1.0, 'ratio': {'xy': 1.0, 'zx': 1.0}, 'unit': 'micron', 'zoom': 1.0}), - ("FOV7_HV110_P0500510000.czi", {'X': 1.0, 'X_sf': 1.0, 'Y': 1.0, 'Y_sf': 1.0, 'Z': 1.0, 'ratio': {'xy': 1.0, 'zx': 1.0}, 'unit': 'micron', 'zoom': 1.0}), - ("Tumor_HE_RGB.czi", {'X': 0.22, 'X_sf': 0.22, 'Y': 0.22, 'Y_sf': 0.22, 'Z': 1.0, 'ratio': {'xy': 1.0, 'zx': 4.545}, 'unit': 'micron', 'zoom': 1.0}) + ("CellDivision_T3_Z5_CH2_X240_Y170.czi", {'X': 0.091, 'X_sf': 0.091, 'Y': 0.091, 'Y_sf': 0.091, 'Z': 0.32, 'ratio': {'xy': 1.0, 'zx': 3.516, 'zx_sf': 3.516}, 'unit': 'micron', 'zoom': 1.0}), + ("Al2O3_SE_020_sp.czi", {'X': 0.028, 'X_sf': 0.028, 'Y': 0.028, 'Y_sf': 0.028, 'Z': 1.0, 'ratio': {'xy': 1.0, 'zx': 35.714, 'zx_sf': 35.714}, 'unit': 'micron', 'zoom': 1.0}), + ("w96_A1+A2.czi", {'X': 0.457, 'X_sf': 0.457, 'Y': 0.457, 'Y_sf': 0.457, 'Z': 1.0, 'ratio': {'xy': 1.0, 'zx': 2.188, 'zx_sf': 2.188}, 'unit': 'micron', 'zoom': 1.0}), + ("Airyscan.czi", {'X': 0.044, 'X_sf': 0.044, 'Y': 0.044, 'Y_sf': 0.044, 'Z': 1.0, 'ratio': {'xy': 1.0, 'zx': 22.727, 'zx_sf': 22.727}, 'unit': 'micron', 'zoom': 1.0}), + ("newCZI_zloc.czi", {'X': 1.0, 'X_sf': 1.0, 'Y': 1.0, 'Y_sf': 1.0, 'Z': 1.0, 'ratio': {'xy': 1.0, 'zx': 1.0, 'zx_sf': 1.0}, 'unit': 'micron', 'zoom': 1.0}), + ("FOV7_HV110_P0500510000.czi", {'X': 1.0, 'X_sf': 1.0, 'Y': 1.0, 'Y_sf': 1.0, 'Z': 1.0, 'ratio': {'xy': 1.0, 'zx': 1.0, 'zx_sf': 1.0}, 'unit': 'micron', 'zoom': 1.0}), + ("Tumor_HE_RGB.czi", {'X': 0.22, 'X_sf': 0.22, 'Y': 0.22, 'Y_sf': 0.22, 'Z': 1.0, 'ratio': {'xy': 1.0, 'zx': 4.545, 'zx_sf': 4.545}, 'unit': 'micron', 'zoom': 1.0}) ] ) def test_scaling1(czifile: str, results: Dict) -> None: - # get the filepath and the metadata + # get the filepath and the metadata_tools filepath = basedir / "data" / czifile czi_scaling = czimd.CziScaling(filepath) out = czi_scaling.__dict__ @@ -72,7 +73,7 @@ def test_safe_get_scale(dist: BoxList, expected: List[float]) -> None: @pytest.mark.parametrize( "czifile, x, y, z, ratio", [ - ("DAPI_GFP.czi", 1.0, 1.0, 1.0, {'xy': 1.0, 'zx': 1.0}) + ("DAPI_GFP.czi", 1.0, 1.0, 1.0, {'xy': 1.0, 'zx': 1.0, 'zx_sf': 1.0}) ] ) def test_scaling2(czifile: str, x: float, y: float, z: float, ratio: Dict[str, float]) -> None: diff --git a/src/czitools/_tests/test_stageXY.py b/src/czitools/_tests/test_stageXY.py index d7fb6e2..2db0894 100644 --- a/src/czitools/_tests/test_stageXY.py +++ b/src/czitools/_tests/test_stageXY.py @@ -1,7 +1,7 @@ -from czitools import metadata_tools as czimd +from czitools.metadata_tools import czi_metadata as czimd from pathlib import Path import pytest -from typing import List, Dict, Tuple, Optional, Type, Any, Union, Mapping +from typing import Dict basedir = Path(__file__).resolve().parents[3] @@ -28,7 +28,7 @@ def test_stage_xy(czifile: str, results: Dict) -> None: # get the CZI filepath filepath = basedir / "data" / czifile - # read the metadata + # read the metadata_tools md = czimd.CziMetadata(filepath) if md.image.SizeS is not None: diff --git a/src/czitools/_tests/test_writing.py b/src/czitools/_tests/test_writing.py index 65e55c8..6dd93d0 100644 --- a/src/czitools/_tests/test_writing.py +++ b/src/czitools/_tests/test_writing.py @@ -1,5 +1,7 @@ -from czitools import read_tools, misc_tools, write_tools -from czitools import metadata_tools as czimd +from czitools.read_tools import read_tools +from czitools.write_tools import write_tools +from czitools.metadata_tools import czi_metadata as czimd +from czitools.utils import misc from pylibCZIrw import czi as pyczi import shutil from pathlib import Path @@ -8,7 +10,7 @@ from tqdm.contrib import itertools as it from tqdm import tqdm import pytest -from typing import List, Dict, Tuple, Optional, Type, Any, Union, Mapping +from typing import Dict, Tuple basedir = Path(__file__).resolve().parents[3] / "data" @@ -21,7 +23,7 @@ def test_write_1(tiff_file: str, sp: int) -> None: filepath = basedir / tiff_file czi_path = filepath.parent / Path( - misc_tools.get_fname_woext(str(filepath)) + ".czi" + misc.get_fname_woext(str(filepath)) + ".czi" ) # remove the CZI if it already exits @@ -96,7 +98,7 @@ def test_write_2( # write the plane with shape (Y, X, 1) to the new CZI file czidoc_w.write(data=mdarray[0, 0, ch, z, ...], plane={"C": ch, "Z": z}) - # get the complete metadata at once as one big class + # get the complete metadata_tools at once as one big class mdata = czimd.CziMetadata(newczi) assert mdata.pyczi_dims == pyczi_dims @@ -152,7 +154,7 @@ def test_write_3( # change the x-position for the next round to write "side-by-side" xstart = xstart + 512 - # get the complete metadata at once as one big class + # get the complete metadata_tools at once as one big class mdata = czimd.CziMetadata(newczi_zloc) assert mdata.pyczi_dims == pyczi_dims @@ -224,7 +226,7 @@ def test_write_4( location=(locx[z], locy[z]), ) - # get the complete metadata at once as one big class + # get the complete metadata_tools at once as one big class mdata = czimd.CziMetadata(newczi_zscenes) assert mdata.pyczi_dims == pyczi_dims diff --git a/src/czitools/metadata_tools.py b/src/czitools/metadata_tools.py deleted file mode 100644 index 5d83f9a..0000000 --- a/src/czitools/metadata_tools.py +++ /dev/null @@ -1,1430 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################# -# File : metadata_tools.py -# Author : sebi06 -# -# Disclaimer: The code is purely experimental. Feel free to -# use it at your own risk. -# -################################################################# - -from __future__ import annotations -from typing import List, Dict, Tuple, Optional, Type, Any, Union, Mapping, Annotated -import os -from collections import Counter -import xml.etree.ElementTree as ET -from pylibCZIrw import czi as pyczi -from czitools import misc_tools -import numpy as np -from dataclasses import dataclass, field, fields, Field -from pathlib import Path -from box import Box, BoxList -from czitools import logger as LOGGER -import time -import validators -from dataclasses import asdict - - -logger = LOGGER.get_logger() - - -@dataclass -class ValueRange: - lo: float - hi: float - - -@dataclass -class CziMetadata: - filepath: Union[str, os.PathLike[str]] - filename: Optional[str] = field(init=False, default=None) - dirname: Optional[str] = field(init=False, default=None) - is_url: Optional[bool] = field(init=False, default=False) - software_name: Optional[str] = field(init=False, default=None) - software_version: Optional[str] = field(init=False, default=None) - acquisition_date: Optional[str] = field(init=False, default=None) - czi_box: Optional[Box] = field(init=False, default=None) - pyczi_dims: Optional[Dict[str, tuple]] = field( - init=False, default_factory=lambda: {} - ) - aics_dimstring: Optional[str] = field(init=False, default=None) - aics_dims_shape: Optional[List[Dict[str, tuple]]] = field( - init=False, default_factory=lambda: {} - ) - aics_size: Optional[Tuple[int]] = field(init=False, default_factory=lambda: ()) - aics_ismosaic: Optional[bool] = field(init=False, default=None) - aics_dim_order: Optional[Dict[str, int]] = field( - init=False, default_factory=lambda: {} - ) - aics_dim_index: Optional[List[int]] = field(init=False, default_factory=lambda: []) - aics_dim_valid: Optional[int] = field(init=False, default=None) - aics_posC: Optional[int] = field(init=False, default=None) - pixeltypes: Optional[Dict[int, str]] = field(init=False, default_factory=lambda: {}) - isRGB: Optional[bool] = field(init=False, default=False) - has_scenes: Optional[bool] = field(init=False, default=False) - has_label: Optional[bool] = field(init=False, default=False) - has_preview: Optional[bool] = field(init=False, default=False) - attachments: Optional[CziAttachments] = field(init=False, default=None) - npdtype: Optional[List[Any]] = field(init=False, default_factory=lambda: []) - maxvalue: Optional[List[int]] = field(init=False, default_factory=lambda: []) - image: Optional[CziDimensions] = field(init=False, default=None) - bbox: Optional[CziBoundingBox] = field(init=False, default=None) - channelinfo: Optional[CziChannelInfo] = field(init=False, default=None) - scale: Optional[CziScaling] = field(init=False, default=None) - objective: Optional[CziObjectives] = field(init=False, default=None) - detector: Optional[CziDetector] = field(init=False, default=None) - microscope: Optional[CziMicroscope] = field(init=False, default=None) - sample: Optional[CziSampleInfo] = field(init=False, default=None) - add_metadata: Optional[CziAddMetaData] = field(init=False, default=None) - array6d_size: Optional[Tuple[int]] = field(init=False, default_factory=lambda: ()) - scene_size_consistent: Optional[Tuple[int]] = field( - init=False, default_factory=lambda: () - ) - """ - Create a CziMetadata object from the filename of the CZI image file. - """ - - def __post_init__(self): - if validators.url(str(self.filepath)): - self.pyczi_readertype = pyczi.ReaderFileInputTypes.Curl - self.is_url = True - logger.info( - "FilePath is a valid link. Only pylibCZIrw functionality is available." - ) - else: - self.pyczi_readertype = pyczi.ReaderFileInputTypes.Standard - self.is_url = False - - if isinstance(self.filepath, Path): - # convert to string - self.filepath = str(self.filepath) - - # get directory and filename etc. - self.dirname = str(Path(self.filepath).parent) - self.filename = str(Path(self.filepath).name) - - # get the metadata as box - self.czi_box = get_czimd_box(self.filepath) - - # check for existence of scenes - self.has_scenes = self.czi_box.has_scenes - - # get acquisition data and SW version - if self.czi_box.ImageDocument.Metadata.Information.Application is not None: - self.software_name = ( - self.czi_box.ImageDocument.Metadata.Information.Application.Name - ) - self.software_version = ( - self.czi_box.ImageDocument.Metadata.Information.Application.Version - ) - - if self.czi_box.ImageDocument.Metadata.Information.Image is not None: - self.acquisition_date = ( - self.czi_box.ImageDocument.Metadata.Information.Image.AcquisitionDateAndTime - ) - - if self.czi_box.ImageDocument.Metadata.Information.Document is not None: - self.creation_date = ( - self.czi_box.ImageDocument.Metadata.Information.Document.CreationDate - ) - self.user_name = ( - self.czi_box.ImageDocument.Metadata.Information.Document.UserName - ) - - # get the dimensions and order - self.image = CziDimensions(self.czi_box) - - # get metadata using pylibCZIrw - with pyczi.open_czi(self.filepath, self.pyczi_readertype) as czidoc: - # get dimensions - self.pyczi_dims = czidoc.total_bounding_box - - # get the pixel typed for all channels - self.pixeltypes = czidoc.pixel_types - self.isRGB, self.consistent_pixeltypes = self.check_if_rgb(self.pixeltypes) - - # check for consistent scene shape - self.scene_shape_is_consistent = self.check_scenes_shape( - czidoc, size_s=self.image.SizeS - ) - - # if self.scene_shape_is_consistent: - # self.image.SizeX_scene = czidoc.scenes_bounding_rectangle[0].w - # self.image.SizeY_scene = czidoc.scenes_bounding_rectangle[0].h - - if not self.is_url: - # get some additional metadata using aicspylibczi - try: - from aicspylibczi import CziFile - - # get the general CZI object using aicspylibczi - aicsczi = CziFile(self.filepath) - - self.aics_dimstring = aicsczi.dims - self.aics_dims_shape = aicsczi.get_dims_shape() - self.aics_size = aicsczi.size - self.aics_ismosaic = aicsczi.is_mosaic() - ( - self.aics_dim_order, - self.aics_dim_index, - self.aics_dim_valid, - ) = self.get_dimorder(aicsczi.dims) - self.aics_posC = self.aics_dim_order["C"] - - except ImportError as e: - # print("Package aicspylibczi not found. Use Fallback values.") - logger.warning("Package aicspylibczi not found. Use Fallback values.") - - for ch, px in self.pixeltypes.items(): - npdtype, maxvalue = self.get_dtype_fromstring(px) - self.npdtype.append(npdtype) - self.maxvalue.append(maxvalue) - - # try to guess if the CZI is a mosaic file - if self.image.SizeM is None or self.image.SizeM == 1: - self.ismosaic = False - else: - self.ismosaic = True - - # get the bounding boxes - self.bbox = CziBoundingBox(self.czi_box) - - # get information about channels - self.channelinfo = CziChannelInfo(self.czi_box) - - # get scaling info - self.scale = CziScaling(self.czi_box) - - # get objective information - self.objective = CziObjectives(self.czi_box) - - # get detector information - self.detector = CziDetector(self.czi_box) - - # get detector information - self.microscope = CziMicroscope(self.czi_box) - - # get information about sample carrier and wells etc. - self.sample = CziSampleInfo(self.czi_box) - - # get additional metainformation - self.add_metadata = CziAddMetaData(self.czi_box) - - # check for attached label or preview image - self.attachments = CziAttachments(self.czi_box) - - # can be also used without creating an instance of the class - @staticmethod - def get_dtype_fromstring( - pixeltype: str, - ) -> Tuple[Optional[np.dtype], Optional[int]]: - dtype = None - maxvalue = None - - if pixeltype == "gray16" or pixeltype == "Gray16": - dtype = np.dtype(np.uint16) - maxvalue = 65535 - if pixeltype == "gray8" or pixeltype == "Gray8": - dtype = np.dtype(np.uint8) - maxvalue = 255 - if pixeltype == "bgr48" or pixeltype == "Bgr48": - dtype = np.dtype(np.uint16) - maxvalue = 65535 - if pixeltype == "bgr24" or pixeltype == "Bgr24": - dtype = np.dtype(np.uint8) - maxvalue = 255 - if pixeltype == "bgr96float" or pixeltype == "Bgr96Float": - dtype = np.dtype(np.uint16) - maxvalue = 65535 - - return dtype, maxvalue - - @staticmethod - def get_dimorder(dim_string: str) -> Tuple[Dict, List, int]: - """Get the order of dimensions from dimension string - - :param dim_string: string containing the dimensions - :type dim_string: str - :return: dims_dict - dictionary with the dimensions and its positions - :rtype: dict - :return: dimindex_list - list with indices of dimensions - :rtype: list - :return: numvalid_dims - number of valid dimensions - :rtype: integer - """ - - dimindex_list = [] - dims = ["R", "I", "M", "H", "V", "B", "S", "T", "C", "Z", "Y", "X", "A"] - dims_dict = {} - - # loop over all dimensions and find the index - for d in dims: - dims_dict[d] = dim_string.find(d) - dimindex_list.append(dim_string.find(d)) - - # check if a dimension really exists - numvalid_dims = sum(i >= 0 for i in dimindex_list) - - return dims_dict, dimindex_list, numvalid_dims - - @staticmethod - def check_scenes_shape(czidoc: pyczi.CziReader, size_s: Union[int, None]) -> bool: - """Check if all scenes have the same shape. - - Args: - czidoc (pyczi.CziReader): CziReader to read the properties - size_s (Union[int, None]): Size of scene dimension - - Returns: - bool: True is all scenes have identical XY shape - """ - scene_width = [] - scene_height = [] - scene_shape_is_consistent = False - - if size_s is not None: - for s in range(size_s): - scene_width.append(czidoc.scenes_bounding_rectangle[s].w) - scene_height.append(czidoc.scenes_bounding_rectangle[s].h) - - # check if all entries in list are the same - sw = scene_width.count(scene_width[0]) == len(scene_width) - sh = scene_height.count(scene_height[0]) == len(scene_height) - - # only if entries for X and Y are all the same as the shape is consistent - if sw is True and sh is True: - scene_shape_is_consistent = True - - else: - scene_shape_is_consistent = True - - return scene_shape_is_consistent - - @staticmethod - def check_if_rgb(pixeltypes: Dict) -> Tuple[bool, bool]: - is_rgb = False - - for k, v in pixeltypes.items(): - if "Bgr" in v: - is_rgb = True - - # flag to check if all elements are same - is_consistant = True - - # extracting value to compare - test_val = list(pixeltypes.values())[0] - - for ele in pixeltypes: - if pixeltypes[ele] != test_val: - is_consistant = False - break - - return is_rgb, is_consistant - - -@dataclass -class CziDimensions: - czisource: Union[str, os.PathLike[str], Box] - SizeX: Optional[int] = field( - init=False, default=None - ) # total size X including scenes - SizeY: Optional[int] = field( - init=False, default=None - ) # total size Y including scenes - SizeX_scene: Optional[int] = field( - init=False, default=None - ) # size X per scene (if equal scene sizes) - SizeY_scene: Optional[int] = field( - init=False, default=None - ) # size Y per scene (if equal scene sizes) - SizeS: Optional[int] = field(init=False, default=None) - SizeT: Optional[int] = field(init=False, default=None) - SizeZ: Optional[int] = field(init=False, default=None) - SizeC: Optional[int] = field(init=False, default=None) - SizeM: Optional[int] = field(init=False, default=None) - SizeR: Optional[int] = field(init=False, default=None) - SizeH: Optional[int] = field(init=False, default=None) - SizeI: Optional[int] = field(init=False, default=None) - SizeV: Optional[int] = field(init=False, default=None) - SizeB: Optional[int] = field(init=False, default=None) - """Dataclass containing the image dimensions. - - Information official CZI Dimension Characters: - "X":"Width" : width of image [pixel] - "Y":"Height" : height of image [pixel] - "C":"Channel" : number of channels - "Z":"Slice" : number of z-planes - "T":"Time" : number of time points - "R":"Rotation" : - "S":"Scene" : contiguous regions of interest in a mosaic image - "I":"Illumination" : SPIM direction for LightSheet - "B":"Block" : acquisition - "M":"Mosaic" : index of tile for compositing a scene - "H":"Phase" : e.g. Airy detector fibers - "V":"View" : e.g. for SPIM - """ - - def __post_init__(self): - self.set_dimensions() - logger.info("Reading Dimensions from CZI image data.") - - # set dimensions in XY with respect to possible down scaling - self.SizeX_sf = self.SizeX - self.SizeY_sf = self.SizeY - - def set_dimensions(self): - """Populate the image dimensions with the detected values from the metadata""" - - # get the Box and extract the relevant dimension metadata - if isinstance(self.czisource, Box): - czi_box = self.czisource - else: - czi_box = get_czimd_box(self.czisource) - - dimensions = czi_box.ImageDocument.Metadata.Information.Image - - # define the image dimensions to check for - dims = [ - "SizeX", - "SizeY", - "SizeS", - "SizeT", - "SizeZ", - "SizeC", - "SizeM", - "SizeR", - "SizeH", - "SizeI", - "SizeV", - "SizeB", - ] - - cls_fields: Tuple[Field, ...] = fields(self.__class__) - for fd in cls_fields: - if fd.name in dims: - if dimensions[fd.name] is not None: - setattr(self, fd.name, int(dimensions[fd.name])) - - if czi_box.has_scenes: - try: - with pyczi.open_czi(czi_box.filepath, czi_box.czi_open_arg) as czidoc: - self.SizeX_scene = czidoc.scenes_bounding_rectangle[0].w - self.SizeY_scene = czidoc.scenes_bounding_rectangle[0].h - except KeyError as e: - self.SizeX_scene = None - self.SizeY_scene = None - logger.warning("Scenes Dimension detected but no bounding rectangle information found.") - - -@dataclass -class CziBoundingBox: - czisource: Union[str, os.PathLike[str], Box] - scenes_bounding_rect: Optional[Dict[int, pyczi.Rectangle]] = field( - init=False, default_factory=lambda: [] - ) - total_rect: Optional[pyczi.Rectangle] = field(init=False, default=None) - total_bounding_box: Optional[Dict[str, tuple]] = field( - init=False, default_factory=lambda: [] - ) - - # TODO Is this really needed as a separate class or better integrate directly into CziMetadata class? - - def __post_init__(self): - logger.info("Reading BoundingBoxes from CZI image data.") - - pyczi_readertype = pyczi.ReaderFileInputTypes.Standard - - if isinstance(self.czisource, Box): - self.czisource = self.czisource.filepath - - if validators.url(str(self.czisource)): - pyczi_readertype = pyczi.ReaderFileInputTypes.Curl - logger.info( - "FilePath is a valid link. Only pylibCZIrw functionality is available." - ) - - with pyczi.open_czi(str(self.czisource), pyczi_readertype) as czidoc: - try: - self.scenes_bounding_rect = czidoc.scenes_bounding_rectangle - except Exception as e: - self.scenes_bounding_rect = None - # print("Scenes Bounding rectangle not found.") - logger.info("Scenes Bounding rectangle not found.") - - try: - self.total_rect = czidoc.total_bounding_rectangle - except Exception as e: - self.total_rect = None - # print("Total Bounding rectangle not found.") - logger.info("Total Bounding rectangle not found.") - - try: - self.total_bounding_box = czidoc.total_bounding_box - except Exception as e: - self.total_bounding_box = None - # print("Total Bounding Box not found.") - logger.info("Total Bounding Box not found.") - - -@dataclass -class CziAttachments: - czisource: Union[str, os.PathLike[str], Box] - has_label: Optional[bool] = field(init=False, default=False) - has_preview: Optional[bool] = field(init=False, default=False) - has_prescan: Optional[bool] = field(init=False, default=False) - names: Optional[List[str]] = field(init=False, default_factory=lambda: []) - - def __post_init__(self): - logger.info("Reading AttachmentImages from CZI image data.") - - try: - import czifile - - if isinstance(self.czisource, Path): - # convert to string - self.czisource = str(self.czisource) - elif isinstance(self.czisource, Box): - self.czisource = self.czisource.filepath - - if validators.url(self.czisource): - logger.warning( - "Reading Attachments from CZI via a link is not supported." - ) - else: - # create CZI-object using czifile library - with czifile.CziFile(self.czisource) as cz: - # iterate over attachments - for att in cz.attachments(): - self.names.append(att.attachment_entry.name) - - if "SlidePreview" in self.names: - self.has_preview = True - logger.info("Attachment SlidePreview found.") - if "Label" in self.names: - self.has_label = True - logger.info("Attachment Label found.") - if "Prescan" in self.names: - self.has_prescan = True - logger.info("Attachment Prescan found.") - - except ImportError as e: - logger.warning( - "Package czifile not found. Cannot extract information about attached images." - ) - - -@dataclass -class CziChannelInfo: - czisource: Union[str, os.PathLike[str], Box] - names: List[str] = field(init=False, default_factory=lambda: []) - dyes: List[str] = field(init=False, default_factory=lambda: []) - colors: List[str] = field(init=False, default_factory=lambda: []) - clims: List[List[float]] = field(init=False, default_factory=lambda: []) - gamma: List[float] = field(init=False, default_factory=lambda: []) - - def __post_init__(self): - logger.info("Reading Channel Information from CZI image data.") - - if isinstance(self.czisource, Box): - czi_box = self.czisource - else: - czi_box = get_czimd_box(self.czisource) - - # get channels part of dict - if czi_box.has_channels: - try: - # extract the relevant dimension metadata - channels = ( - czi_box.ImageDocument.Metadata.Information.Image.Dimensions.Channels.Channel - ) - if isinstance(channels, Box): - # get the data in case of only one channel - self.names.append( - "CH1" - ) if channels.Name is None else self.names.append(channels.Name) - elif isinstance(channels, BoxList): - # get the data in case multiple channels - for ch in range(len(channels)): - self.names.append("CH1") if channels[ - ch - ].Name is None else self.names.append(channels[ch].Name) - except AttributeError: - channels = None - elif not czi_box.has_channels: - logger.info("Channel(s) information not found.") - - if czi_box.has_disp: - try: - # extract the relevant dimension metadata - disp = czi_box.ImageDocument.Metadata.DisplaySetting.Channels.Channel - if isinstance(disp, Box): - self.get_channel_info(disp) - elif isinstance(disp, BoxList): - for ch in range(len(disp)): - self.get_channel_info(disp[ch]) - except AttributeError: - disp = None - - elif not czi_box.has_disp: - # print("DisplaySetting(s) not found.") - logger.info("DisplaySetting(s) not found.") - - def get_channel_info(self, display: Box): - if display is not None: - self.dyes.append( - "Dye-CH1" - ) if display.ShortName is None else self.dyes.append(display.ShortName) - self.colors.append( - "#80808000" - ) if display.Color is None else self.colors.append(display.Color) - - low = 0.0 if display.Low is None else float(display.Low) - high = 0.5 if display.High is None else float(display.High) - - self.clims.append([low, high]) - self.gamma.append(0.85) if display.Gamma is None else self.gamma.append( - float(display.Gamma) - ) - else: - self.dyes.append("Dye-CH1") - self.colors.append("#80808000") - self.clims.append([0.0, 0.5]) - self.gamma.append(0.85) - - -@dataclass -class CziScaling: - czisource: Union[str, os.PathLike[str], Box] - X: Optional[float] = field(init=False, default=None) - Y: Optional[float] = field(init=False, default=None) - Z: Optional[float] = field(init=False, default=None) - X_sf: Optional[float] = field(init=False, default=None) - Y_sf: Optional[float] = field(init=False, default=None) - ratio: Optional[Dict[str, float]] = field(init=False, default=None) - # ratio_sf: Optional[Dict[str, float]] = field(init=False, default=None) - # scalefactorXY: Optional[float] = field(init=False, default=None) - unit: Optional[str] = field(init=True, default="micron") - zoom: Annotated[float, ValueRange(0.01, 1.0)] = field(init=True, default=1.0) - - def __post_init__(self): - logger.info("Reading Scaling from CZI image data.") - - if isinstance(self.czisource, Box): - czi_box = self.czisource - else: - czi_box = get_czimd_box(self.czisource) - - if czi_box.has_scale: - distances = czi_box.ImageDocument.Metadata.Scaling.Items.Distance - - # get the scaling values for X,Y and Z - self.X = np.round(self.safe_get_scale(distances, 0), 3) - self.Y = np.round(self.safe_get_scale(distances, 1), 3) - self.Z = np.round(self.safe_get_scale(distances, 2), 3) - - # calc the scaling values for X,Y when applying downscaling - self.X_sf = np.round(self.X * (1 / self.zoom), 3) - self.Y_sf = np.round(self.Y * (1 / self.zoom), 3) - - # calc the scaling ratio - self.ratio = { - "xy": np.round(self.X / self.Y, 3), - "zx": np.round(self.Z / self.X, 3), - # "zx_sf": np.round(self.Z / self.X_sf, 3), - } - - elif not czi_box.has_scale: - logger.info("No scaling information found.") - - @staticmethod - def safe_get_scale(dist: BoxList, idx: int) -> Optional[float]: - scales = ["X", "Y", "Z"] - - try: - # get the scaling value in [micron] - sc = float(dist[idx].Value) * 1000000 - - # check for the value = 0.0 - if sc == 0.0: - sc = 1.0 - logger.info( - "Detected Scaling = 0.0 for " - + scales[idx] - + " Using default = 1.0 [micron]." - ) - return sc - - except (IndexError, TypeError, AttributeError): - logger.info( - "No " + scales[idx] + "-Scaling found. Using default = 1.0 [micron]." - ) - return 1.0 - - -@dataclass -class CziObjectives: - czisource: Union[str, os.PathLike[str], Box] - NA: List[Optional[float]] = field(init=False, default_factory=lambda: []) - objmag: List[Optional[float]] = field(init=False, default_factory=lambda: []) - Id: List[Optional[str]] = field(init=False, default_factory=lambda: []) - name: List[Optional[str]] = field(init=False, default_factory=lambda: []) - model: List[Optional[str]] = field(init=False, default_factory=lambda: []) - immersion: List[Optional[str]] = field(init=False, default_factory=lambda: []) - tubelensmag: List[Optional[float]] = field(init=False, default_factory=lambda: []) - totalmag: List[Optional[float]] = field(init=False, default_factory=lambda: []) - - def __post_init__(self): - logger.info("Reading Objective Information from CZI image data.") - - if isinstance(self.czisource, Box): - czi_box = self.czisource - else: - czi_box = get_czimd_box(self.czisource) - - # check if objective metadata actually exist - if czi_box.has_objectives: - try: - # get objective data - objective = ( - czi_box.ImageDocument.Metadata.Information.Instrument.Objectives.Objective - ) - if isinstance(objective, Box): - self.get_objective_info(objective) - elif isinstance(objective, BoxList): - for obj in range(len(objective)): - self.get_objective_info(objective[obj]) - except AttributeError: - objective = None - - elif not czi_box.has_objectives: - # print("No Objective Information found.") - logger.info("No Objective Information found.") - - # check if tubelens metadata exist - if czi_box.has_tubelenses: - # get tubelenes data - tubelens = ( - czi_box.ImageDocument.Metadata.Information.Instrument.TubeLenses.TubeLens - ) - - if isinstance(tubelens, Box): - if tubelens.Magnification is not None: - self.tubelensmag.append(float(tubelens.Magnification)) - elif tubelens.Magnification is None: - logger.warning("No tubelens magnification found. Use 1.0x instead.") - self.tubelensmag.append(1.0) - - elif isinstance(tubelens, BoxList): - for tl in range(len(tubelens)): - self.tubelensmag.append(float(tubelens[tl].Magnification)) - - # some additional checks to calc the total magnification - if self.objmag is not None and self.tubelensmag is not None: - self.totalmag = [i * j for i in self.objmag for j in self.tubelensmag] - - elif not czi_box.has_tubelens: - logger.info("No Tublens Information found.") - - if self.objmag is not None and self.tubelensmag == []: - self.totalmag = self.objmag - - def get_objective_info(self, objective: Box): - self.name.append(objective.Name) - self.immersion.append(objective.Immersion) - - if objective.LensNA is not None: - self.NA.append(float(objective.LensNA)) - - if objective.Id is not None: - self.Id.append(objective.Id) - - if objective.NominalMagnification is not None: - self.objmag.append(float(objective.NominalMagnification)) - - if None in self.name and self.name.count(None) == 1: - self.name.remove(None) - self.name.append(objective.Manufacturer.Model) - - -@dataclass -class CziDetector: - czisource: Union[str, os.PathLike[str], Box] - model: List[str] = field(init=False, default_factory=lambda: []) - name: List[str] = field(init=False, default_factory=lambda: []) - Id: List[str] = field(init=False, default_factory=lambda: []) - modeltype: List[str] = field(init=False, default_factory=lambda: []) - gain: List[float] = field(init=False, default_factory=lambda: []) - zoom: List[float] = field(init=False, default_factory=lambda: []) - amplificationgain: List[float] = field(init=False, default_factory=lambda: []) - - def __post_init__(self): - logger.info("Reading Detector Information from CZI image data.") - - if isinstance(self.czisource, Box): - czi_box = self.czisource - else: - czi_box = get_czimd_box(self.czisource) - - # check if there are any detector entries inside the dictionary - if czi_box.ImageDocument.Metadata.Information.Instrument is not None: - # get the data for the detectors - detectors = ( - czi_box.ImageDocument.Metadata.Information.Instrument.Detectors.Detector - ) - - # check for detector Id, Name, Model and Type - if isinstance(detectors, Box): - self.Id.append(detectors.Id) - self.name.append(detectors.Name) - self.model.append(detectors.Model) - self.modeltype.append(detectors.Type) - self.gain.append(detectors.Gain) - self.zoom.append(detectors.Zoom) - self.amplificationgain.append(detectors.AmplificationGain) - - # and do this differently in case of a list of detectors - elif isinstance(detectors, BoxList): - for d in range(len(detectors)): - self.Id.append(detectors[d].Id) - self.name.append(detectors[d].Name) - self.model.append(detectors[d].Model) - self.modeltype.append(detectors[d].Type) - self.gain.append(detectors[d].Gain) - self.zoom.append(detectors[d].Zoom) - self.amplificationgain.append(detectors[d].AmplificationGain) - - elif czi_box.ImageDocument.Metadata.Information.Instrument is None: - # print("No Detetctor(s) information found.") - logger.info("No Detetctor(s) information found.") - self.model = [None] - self.name = [None] - self.Id = [None] - self.modeltype = [None] - self.gain = [None] - self.zoom = [None] - self.amplificationgain = [None] - - -@dataclass -class CziMicroscope: - czisource: Union[str, os.PathLike[str], Box] - Id: Optional[str] = field(init=False) - Name: Optional[str] = field(init=False) - System: Optional[str] = field(init=False) - - def __post_init__(self): - logger.info("Reading Microscope Information from CZI image data.") - - if isinstance(self.czisource, Box): - czi_box = self.czisource - else: - czi_box = get_czimd_box(self.czisource) - - if czi_box.ImageDocument.Metadata.Information.Instrument is None: - self.Id = None - self.Name = None - self.System = None - # print("No Microscope information found.") - logger.info("No Microscope information found.") - - else: - self.Id = ( - czi_box.ImageDocument.Metadata.Information.Instrument.Microscopes.Microscope.Id - ) - self.Name = ( - czi_box.ImageDocument.Metadata.Information.Instrument.Microscopes.Microscope.Name - ) - self.System = ( - czi_box.ImageDocument.Metadata.Information.Instrument.Microscopes.Microscope.System - ) - - -@dataclass -class CziSampleInfo: - czisource: Union[str, os.PathLike[str], Box] - well_array_names: List[str] = field(init=False, default_factory=lambda: []) - well_indices: List[int] = field(init=False, default_factory=lambda: []) - well_position_names: List[str] = field(init=False, default_factory=lambda: []) - well_colID: List[int] = field(init=False, default_factory=lambda: []) - well_rowID: List[int] = field(init=False, default_factory=lambda: []) - well_counter: Dict = field(init=False, default_factory=lambda: {}) - scene_stageX: List[float] = field(init=False, default_factory=lambda: []) - scene_stageY: List[float] = field(init=False, default_factory=lambda: []) - image_stageX: float = field(init=False, default=None) - image_stageY: float = field(init=False, default=None) - - def __post_init__(self): - logger.info("Reading SampleCarrier Information from CZI image data.") - - if isinstance(self.czisource, Box): - czi_box = self.czisource - else: - czi_box = get_czimd_box(self.czisource) - - size_s = CziDimensions(czi_box).SizeS - - if size_s is not None: - try: - allscenes = ( - czi_box.ImageDocument.Metadata.Information.Image.Dimensions.S.Scenes.Scene - ) - - if isinstance(allscenes, Box): - self.get_well_info(allscenes) - - if isinstance(allscenes, BoxList): - for well in range(len(allscenes)): - self.get_well_info(allscenes[well]) - - except AttributeError: - # print("CZI contains no scene metadata.") - logger.info("CZI contains no scene metadata.") - - elif size_s is None: - logger.info( - "No Scene or Well information found. Try to read XY Stage Coordinates from subblocks." - ) - - try: - # read the data from CSV file - planetable, csvfile = misc_tools.get_planetable( - czi_box.filepath, read_one_only=True, savetable=False - ) - - self.image_stageX = float(planetable["X[micron]"][0]) - self.image_stageY = float(planetable["Y[micron]"][0]) - - except Exception as e: - print(e) - - def get_well_info(self, well: Box): - # logger = setup_log("CziSampleInfo") - - # check the ArrayName - if well.ArrayName is not None: - self.well_array_names.append(well.ArrayName) - # count the well instances - self.well_counter = Counter(self.well_array_names) - - if well.Index is not None: - self.well_indices.append(int(well.Index)) - elif well.Index is None: - # print("Well Index not found.") - logger.info("Well Index not found.") - self.well_indices.append(1) - - if well.Name is not None: - self.well_position_names.append(well.Name) - elif well.Name is None: - # print("Well Position Names not found.") - logger.info("Well Position Names not found.") - self.well_position_names.append("P1") - - if well.Shape is not None: - self.well_colID.append(int(well.Shape.ColumnIndex)) - self.well_rowID.append(int(well.Shape.RowIndex)) - elif well.Shape is None: - # print("Well Column or Row IDs not found.") - logger.info("Well Column or Row IDs not found.") - self.well_colID.append(0) - self.well_rowID.append(0) - - if well.CenterPosition is not None: - # get the SceneCenter Position - sx = well.CenterPosition.split(",")[0] - sy = well.CenterPosition.split(",")[1] - self.scene_stageX.append(np.double(sx)) - self.scene_stageY.append(np.double(sy)) - if well.CenterPosition is None: - # print("Stage Positions XY not found.") - logger.info("Stage Positions XY not found.") - self.scene_stageX.append(0.0) - self.scene_stageY.append(0.0) - - -@dataclass -class CziAddMetaData: - czisource: Union[str, os.PathLike[str], Box] - experiment: Optional[Box] = field(init=False, default=None) - hardwaresetting: Optional[Box] = field(init=False, default=None) - customattributes: Optional[Box] = field(init=False, default=None) - displaysetting: Optional[Box] = field(init=False, default=None) - layers: Optional[Box] = field(init=False, default=None) - - def __post_init__(self): - logger.info("Reading additional Metedata from CZI image data.") - - if isinstance(self.czisource, Box): - czi_box = self.czisource - else: - czi_box = get_czimd_box(self.czisource) - - if czi_box.has_experiment: - self.experiment = czi_box.ImageDocument.Metadata.Experiment - else: - # print("No Experiment information found.") - logger.info("No Experiment information found.") - - if czi_box.has_hardware: - self.hardwaresetting = czi_box.ImageDocument.Metadata.HardwareSetting - else: - # print("No HardwareSetting information found.") - logger.info("No HardwareSetting information found.") - - if czi_box.has_customattr: - self.customattributes = czi_box.ImageDocument.Metadata.CustomAttributes - else: - # print("No CustomAttributes information found.") - logger.info("No CustomAttributes information found.") - - if czi_box.has_disp: - self.displaysetting = czi_box.ImageDocument.Metadata.DisplaySetting - else: - # print("No DisplaySetting information found.") - logger.info("No DisplaySetting information found.") - - if czi_box.has_layers: - self.layers = czi_box.ImageDocument.Metadata.Layers - else: - # print("No Layers information found.") - logger.info("No Layers information found.") - - -@dataclass -class CziScene: - filepath: Union[str, os.PathLike[str]] - index: int - bbox: Optional[pyczi.Rectangle] = field(init=False, default=None) - xstart: Optional[int] = field(init=False, default=None) - ystart: Optional[int] = field(init=False, default=None) - width: Optional[int] = field(init=False, default=None) - height: Optional[int] = field(init=False, default=None) - - def __post_init__(self): - logger.info("Reading Scene Information from CZI image data.") - - if isinstance(self.filepath, Path): - # convert to string - self.filepath = str(self.filepath) - - # get scene information from the CZI file - with pyczi.open_czi(self.filepath) as czidoc: - try: - self.bbox = czidoc.scenes_bounding_rectangle[self.index] - self.xstart = self.bbox.x - self.ystart = self.bbox.y - self.width = self.bbox.w - self.height = self.bbox.h - except KeyError: - # in case an invalid index was used - # print("No Scenes detected.") - logger.info("No Scenes detected.") - - -def get_metadata_as_object(filepath: Union[str, os.PathLike[str]]) -> DictObj: - """ - Get the complete CZI metadata as an object created based on the - dictionary created from the XML data. - """ - - if isinstance(filepath, Path): - # convert to string - filepath = str(filepath) - - # get metadata dictionary using pylibCZIrw - with pyczi.open_czi(filepath) as czidoc: - md_dict = czidoc.metadata - - return DictObj(md_dict) - - -class DictObj: - """ - Create an object based on a dictionary. See https://joelmccune.com/python-dictionary-as-object/ - """ - - # TODO: is this class still neded because of suing python-box - - def __init__(self, in_dict: dict) -> None: - assert isinstance(in_dict, dict) - - for key, val in in_dict.items(): - if isinstance(val, (list, tuple)): - setattr( - self, key, [DictObj(x) if isinstance(x, dict) else x for x in val] - ) - else: - setattr(self, key, DictObj(val) if isinstance(val, dict) else val) - - -def obj2dict(obj: Any, sort: bool = True) -> Dict[str, Any]: - """ - obj2dict: Convert a class attributes and their values to a dictionary - - Args: - obj (Any): Class to be converted - sort (bool, optional): Sort the resulting dictionary by key names. Defaults to True. - - Returns: - Dict[str, Any]: The resulting disctionary. - """ - - # https://stackoverflow.com/questions/7963762/what-is-the-most-economical-way-to-convert-nested-python-objects-to-dictionaries - - if not hasattr(obj, "__dict__"): - return obj - - result = {} - - for key, val in obj.__dict__.items(): - if key.startswith("_"): - continue - - element = [] - - if isinstance(val, list): - for item in val: - element.append(obj2dict(item)) - else: - element = obj2dict(val) - - result[key] = element - - # delete key "czisource" - if "czisource" in result.keys(): - del result["czisource"] - - if sort: - return misc_tools.sort_dict_by_key(result) - - elif not sort: - return result - - -def writexml( - filepath: Union[str, os.PathLike[str]], xmlsuffix: str = "_CZI_MetaData.xml" -) -> str: - """ - writexml: Write XML information of CZI to disk - - Args: - filepath (Union[str, os.PathLike[str]]): CZI image filename - xmlsuffix (str, optional): suffix for the XML file that will be created, defaults to '_CZI_MetaData.xml'. Defaults to '_CZI_MetaData.xml'. - - Returns: - str: filename of the XML file - """ - - if isinstance(filepath, Path): - # convert to string - filepath = str(filepath) - - # get the raw metadata as XML or dictionary - with pyczi.open_czi(filepath) as czidoc: - metadata_xmlstr = czidoc.raw_metadata - - # change file name - xmlfile = filepath.replace(".czi", xmlsuffix) - - # get tree from string - tree = ET.ElementTree(ET.fromstring(metadata_xmlstr)) - - # write XML file to same folder - tree.write(xmlfile, encoding="utf-8", method="xml") - - return xmlfile - - -def create_md_dict_red( - metadata: CziMetadata, sort: bool = True, remove_none: bool = True -) -> Dict: - """ - create_mdict_red: Created a reduced metadata dictionary - - Args: - metadata: CziMetadata class - sort: sort the dictionary - remove_none: Remove None values from dictionary - - Returns: dictionary with the metadata - - """ - - # create a dictionary with the metadata - md_dict = { - "Directory": metadata.dirname, - "Filename": metadata.filename, - "AcqDate": metadata.acquisition_date, - "CreationDate": metadata.creation_date, - "UserName": metadata.user_name, - "SW-App": metadata.software_version, - "SW-Version": metadata.software_name, - "SizeX": metadata.image.SizeX, - "SizeY": metadata.image.SizeY, - "SizeZ": metadata.image.SizeZ, - "SizeC": metadata.image.SizeC, - "SizeT": metadata.image.SizeT, - "SizeS": metadata.image.SizeS, - "SizeB": metadata.image.SizeB, - "SizeM": metadata.image.SizeM, - "SizeH": metadata.image.SizeH, - "SizeI": metadata.image.SizeI, - "isRGB": metadata.isRGB, - "array6d_size": metadata.array6d_size, - "has_scenes": metadata.has_scenes, - "has_label": metadata.attachments.has_label, - "has_preview": metadata.attachments.has_preview, - "has_prescan": metadata.attachments.has_prescan, - "ismosaic": metadata.ismosaic, - "ObjNA": metadata.objective.NA, - "ObjMag": metadata.objective.totalmag, - "TubelensMag": metadata.objective.tubelensmag, - "XScale": metadata.scale.X, - "YScale": metadata.scale.Y, - "XScale_sf": metadata.scale.X_sf, - "YScale_sf": metadata.scale.Y_sf, - "ZScale": metadata.scale.Z, - "ScaleRatio_XYZ": metadata.scale.ratio, - "ChannelNames": metadata.channelinfo.names, - "ChannelDyes": metadata.channelinfo.dyes, - "ChannelColors": metadata.channelinfo.colors, - "WellArrayNames": metadata.sample.well_array_names, - "WellIndicies": metadata.sample.well_indices, - "WellPositionNames": metadata.sample.well_position_names, - "WellRowID": metadata.sample.well_rowID, - "WellColumnID": metadata.sample.well_colID, - "WellCounter": metadata.sample.well_counter, - "SceneCenterStageX": metadata.sample.scene_stageX, - "SceneCenterStageY": metadata.sample.scene_stageX, - "ImageStageX": metadata.sample.image_stageX, - "ImageStageY": metadata.sample.image_stageX, - "TotalBoundingBox": metadata.bbox.total_bounding_box, - } - - # check for extra entries when reading mosaic file with a scale factor - if hasattr(metadata.image, "SizeX_sf"): - md_dict["XScale_sf"] = metadata.scale.X_sf - md_dict["YScale_sf"] = metadata.scale.Y_sf - - if metadata.has_scenes: - md_dict["SizeX_scene"] = metadata.image.SizeX_scene - md_dict["SizeY_scene"] = metadata.image.SizeY_scene - - if remove_none: - md_dict = misc_tools.remove_none_from_dict(md_dict) - - if sort: - return misc_tools.sort_dict_by_key(md_dict) - if not sort: - return md_dict - - -def get_czimd_box( - filepath: Union[str, os.PathLike[str]], - readertype: pyczi.ReaderFileInputTypes = pyczi.ReaderFileInputTypes.Standard, -) -> Box: - """ - get_czimd_box: Get CZI metadata as a python-box. For details: https://pypi.org/project/python-box/ - - Args: - filepath (Union[str, os.PathLike[str]]): Filepath of the CZI file - - Returns: - Box: CZI metadata as a Box object - """ - - readertype = pyczi.ReaderFileInputTypes.Standard - - if validators.url(str(filepath)): - readertype = pyczi.ReaderFileInputTypes.Curl - - # get metadata dictionary using pylibCZIrw - with pyczi.open_czi(str(filepath), readertype) as czi_document: - metadata_dict = czi_document.metadata - - czimd_box = Box( - metadata_dict, - conversion_box=True, - default_box=True, - default_box_attr=None, - default_box_create_on_get=True, - # default_box_no_key_error=True - ) - - # add the filepath - czimd_box.filepath = filepath - czimd_box.is_url = validators.url(str(filepath)) - czimd_box.czi_open_arg = readertype - - # set the defaults to False - czimd_box.has_customattr = False - czimd_box.has_experiment = False - czimd_box.has_disp = False - czimd_box.has_hardware = False - czimd_box.has_scale = False - czimd_box.has_instrument = False - czimd_box.has_microscopes = False - czimd_box.has_detectors = False - czimd_box.has_objectives = False - czimd_box.has_tubelenses = False - czimd_box.has_disp = False - czimd_box.has_channels = False - czimd_box.has_info = False - czimd_box.has_app = False - czimd_box.has_doc = False - czimd_box.has_image = False - czimd_box.has_scenes = False - czimd_box.has_dims = False - czimd_box.has_layers = False - - if "Experiment" in czimd_box.ImageDocument.Metadata: - czimd_box.has_experiment = True - - if "HardwareSetting" in czimd_box.ImageDocument.Metadata: - czimd_box.has_hardware = True - - if "CustomAttributes" in czimd_box.ImageDocument.Metadata: - czimd_box.has_customattr = True - - if "Information" in czimd_box.ImageDocument.Metadata: - czimd_box.has_info = True - - if "Application" in czimd_box.ImageDocument.Metadata.Information: - czimd_box.has_app = True - - if "Document" in czimd_box.ImageDocument.Metadata.Information: - czimd_box.has_doc = True - - if "Image" in czimd_box.ImageDocument.Metadata.Information: - czimd_box.has_image = True - - if "Dimensions" in czimd_box.ImageDocument.Metadata.Information.Image: - czimd_box.has_dims = True - - if ( - "Channels" - in czimd_box.ImageDocument.Metadata.Information.Image.Dimensions - ): - czimd_box.has_channels = True - - if "S" in czimd_box.ImageDocument.Metadata.Information.Image.Dimensions: - czimd_box.has_scenes = True - - if "Instrument" in czimd_box.ImageDocument.Metadata.Information: - czimd_box.has_instrument = True - - if "Detectors" in czimd_box.ImageDocument.Metadata.Information.Instrument: - czimd_box.has_detectors = True - - if "Microscopes" in czimd_box.ImageDocument.Metadata.Information.Instrument: - czimd_box.has_microscopes = True - - if "Objectives" in czimd_box.ImageDocument.Metadata.Information.Instrument: - czimd_box.has_objectives = True - - if "TubeLenses" in czimd_box.ImageDocument.Metadata.Information.Instrument: - czimd_box.has_tubelenses = True - - if "Scaling" in czimd_box.ImageDocument.Metadata: - czimd_box.has_scale = True - - if "DisplaySetting" in czimd_box.ImageDocument.Metadata: - czimd_box.has_disp = True - - if "Layers" in czimd_box.ImageDocument.Metadata: - czimd_box.has_layers = True - - return czimd_box - - -def create_md_dict_nested( - metadata: CziMetadata, sort: bool = True, remove_none: bool = True -) -> Dict: - """ - Create nested dictionary from metadata - - Args: - metadata (CziMetadata): CzIMetaData object_ - sort (bool, optional): Sort the dictionary_. Defaults to True. - remove_none (bool, optional): Remove None values from dictionary. Defaults to True. - - Returns: - Dict: Nested dictionary with reduced set of metadata - """ - - md_array6d = {"array6d": metadata.array6d_size} - - md_box_image = Box(asdict(metadata.image)) - del md_box_image.czisource - - md_box_scale = Box(asdict(metadata.scale)) - del md_box_scale.czisource - - md_box_sample = Box(asdict(metadata.sample)) - del md_box_sample.czisource - - md_box_objective = Box(asdict(metadata.objective)) - del md_box_objective.czisource - - md_box_channels = Box(asdict(metadata.channelinfo)) - del md_box_channels.czisource - - md_box_bbox = Box(metadata.bbox.total_bounding_box) - #del md_box_bbox.czisource - - md_box_info = Box( - { - "Directory": metadata.dirname, - "Filename": metadata.filename, - "AcqDate": metadata.acquisition_date, - "CreationDate": metadata.creation_date, - "UserName": metadata.user_name, - "SW-App": metadata.software_version, - "SW-Version": metadata.software_name, - } - ) - - md_box_image_add = Box( - { - "isRGB": metadata.isRGB, - "has_scenes": metadata.has_scenes, - "ismosaic": metadata.ismosaic, - } - ) - - md_box_image += md_box_image_add - - IDs = [ - "array6d", - "image", - "scale", - "sample", - "objectives", - "channels", - "bbox", - "info", - ] - - mds = [ - md_array6d, - md_box_image.to_dict(), - md_box_scale.to_dict(), - md_box_sample.to_dict(), - md_box_objective.to_dict(), - md_box_channels.to_dict(), - md_box_bbox.to_dict(), - md_box_info.to_dict(), - ] - - md_dict = dict(zip(IDs, mds)) - - if remove_none: - md_dict = misc_tools.remove_none_from_dict(md_dict) - - if sort: - return misc_tools.sort_dict_by_key(md_dict) - if not sort: - return md_dict - - return md_dict diff --git a/src/czitools/metadata_tools/__init__.py b/src/czitools/metadata_tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/czitools/metadata_tools/add_metadata.py b/src/czitools/metadata_tools/add_metadata.py new file mode 100644 index 0000000..d561741 --- /dev/null +++ b/src/czitools/metadata_tools/add_metadata.py @@ -0,0 +1,56 @@ +from typing import Union, Optional +from dataclasses import dataclass, field +from box import Box +import os +from czitools.utils.logger import get_logger +from czitools.utils.box import get_czimd_box + +logger = get_logger() + + +@dataclass +class CziAddMetaData: + czisource: Union[str, os.PathLike[str], Box] + experiment: Optional[Box] = field(init=False, default=None) + hardwaresetting: Optional[Box] = field(init=False, default=None) + customattributes: Optional[Box] = field(init=False, default=None) + displaysetting: Optional[Box] = field(init=False, default=None) + layers: Optional[Box] = field(init=False, default=None) + + def __post_init__(self): + logger.info("Reading additional Metedata from CZI image data.") + + if isinstance(self.czisource, Box): + czi_box = self.czisource + else: + czi_box = get_czimd_box(self.czisource) + + if czi_box.has_experiment: + self.experiment = czi_box.ImageDocument.Metadata.Experiment + else: + # print("No Experiment information found.") + logger.info("No Experiment information found.") + + if czi_box.has_hardware: + self.hardwaresetting = czi_box.ImageDocument.Metadata.HardwareSetting + else: + # print("No HardwareSetting information found.") + logger.info("No HardwareSetting information found.") + + if czi_box.has_customattr: + self.customattributes = czi_box.ImageDocument.Metadata.CustomAttributes + else: + # print("No CustomAttributes information found.") + logger.info("No CustomAttributes information found.") + + if czi_box.has_disp: + self.displaysetting = czi_box.ImageDocument.Metadata.DisplaySetting + else: + # print("No DisplaySetting information found.") + logger.info("No DisplaySetting information found.") + + if czi_box.has_layers: + self.layers = czi_box.ImageDocument.Metadata.Layers + else: + # print("No Layers information found.") + logger.info("No Layers information found.") diff --git a/src/czitools/metadata_tools/attachment.py b/src/czitools/metadata_tools/attachment.py new file mode 100644 index 0000000..5182e6f --- /dev/null +++ b/src/czitools/metadata_tools/attachment.py @@ -0,0 +1,56 @@ +from typing import Optional, Union, List +from dataclasses import dataclass, field +from box import Box +import os +from czitools.utils.logger import get_logger +from pathlib import Path +import validators + +logger = get_logger() + + +@dataclass +class CziAttachments: + czisource: Union[str, os.PathLike[str], Box] + has_label: Optional[bool] = field(init=False, default=False) + has_preview: Optional[bool] = field(init=False, default=False) + has_prescan: Optional[bool] = field(init=False, default=False) + names: Optional[List[str]] = field(init=False, default_factory=lambda: []) + + def __post_init__(self): + logger.info("Reading AttachmentImages from CZI image data.") + + try: + import czifile + + if isinstance(self.czisource, Path): + # convert to string + self.czisource = str(self.czisource) + elif isinstance(self.czisource, Box): + self.czisource = self.czisource.filepath + + if validators.url(self.czisource): + logger.warning( + "Reading Attachments from CZI via a link is not supported." + ) + else: + # create CZI-object using czifile library + with czifile.CziFile(self.czisource) as cz: + # iterate over attachments + for att in cz.attachments(): + self.names.append(att.attachment_entry.name) + + if "SlidePreview" in self.names: + self.has_preview = True + logger.info("Attachment SlidePreview found.") + if "Label" in self.names: + self.has_label = True + logger.info("Attachment Label found.") + if "Prescan" in self.names: + self.has_prescan = True + logger.info("Attachment Prescan found.") + + except ImportError as e: + logger.warning( + "Package czifile not found. Cannot extract information about attached images." + ) \ No newline at end of file diff --git a/src/czitools/metadata_tools/boundingbox.py b/src/czitools/metadata_tools/boundingbox.py new file mode 100644 index 0000000..39f1a6f --- /dev/null +++ b/src/czitools/metadata_tools/boundingbox.py @@ -0,0 +1,59 @@ +from typing import Optional, Union, Dict +from dataclasses import dataclass, field +from box import Box +import os +from czitools.utils.logger import get_logger +from pylibCZIrw import czi as pyczi +import validators + +logger = get_logger() + + +@dataclass +class CziBoundingBox: + czisource: Union[str, os.PathLike[str], Box] + scenes_bounding_rect: Optional[Dict[int, pyczi.Rectangle]] = field( + init=False, default_factory=lambda: [] + ) + total_rect: Optional[pyczi.Rectangle] = field(init=False, default=None) + total_bounding_box: Optional[Dict[str, tuple]] = field( + init=False, default_factory=lambda: [] + ) + + # TODO Is this really needed as a separate class or better integrate directly into CziMetadata class? + + def __post_init__(self): + logger.info("Reading BoundingBoxes from CZI image data.") + + pyczi_readertype = pyczi.ReaderFileInputTypes.Standard + + if isinstance(self.czisource, Box): + self.czisource = self.czisource.filepath + + if validators.url(str(self.czisource)): + pyczi_readertype = pyczi.ReaderFileInputTypes.Curl + logger.info( + "FilePath is a valid link. Only pylibCZIrw functionality is available." + ) + + with pyczi.open_czi(str(self.czisource), pyczi_readertype) as czidoc: + try: + self.scenes_bounding_rect = czidoc.scenes_bounding_rectangle + except Exception as e: + self.scenes_bounding_rect = None + # print("Scenes Bounding rectangle not found.") + logger.info("Scenes Bounding rectangle not found.") + + try: + self.total_rect = czidoc.total_bounding_rectangle + except Exception as e: + self.total_rect = None + # print("Total Bounding rectangle not found.") + logger.info("Total Bounding rectangle not found.") + + try: + self.total_bounding_box = czidoc.total_bounding_box + except Exception as e: + self.total_bounding_box = None + # print("Total Bounding Box not found.") + logger.info("Total Bounding Box not found.") \ No newline at end of file diff --git a/src/czitools/metadata_tools/channel.py b/src/czitools/metadata_tools/channel.py new file mode 100644 index 0000000..44d404f --- /dev/null +++ b/src/czitools/metadata_tools/channel.py @@ -0,0 +1,98 @@ +from typing import Union, List +from dataclasses import dataclass, field +from box import Box, BoxList +import os +from czitools.utils.logger import get_logger +from czitools.utils.box import get_czimd_box + +logger = get_logger() + + +@dataclass +class CziChannelInfo: + czisource: Union[str, os.PathLike[str], Box] + names: List[str] = field(init=False, default_factory=lambda: []) + dyes: List[str] = field(init=False, default_factory=lambda: []) + colors: List[str] = field(init=False, default_factory=lambda: []) + clims: List[List[float]] = field(init=False, default_factory=lambda: []) + gamma: List[float] = field(init=False, default_factory=lambda: []) + + def __post_init__(self): + logger.info("Reading Channel Information from CZI image data.") + + if isinstance(self.czisource, Box): + czi_box = self.czisource + else: + czi_box = get_czimd_box(self.czisource) + + # get channels part of dict + if czi_box.has_channels: + try: + # extract the relevant dimension metadata_tools + channels = ( + czi_box.ImageDocument.Metadata.Information.Image.Dimensions.Channels.Channel + ) + if isinstance(channels, Box): + # get the data in case of only one channel + ( + self.names.append("CH1") + if channels.Name is None + else self.names.append(channels.Name) + ) + elif isinstance(channels, BoxList): + # get the data in case multiple channels + for ch in range(len(channels)): + ( + self.names.append("CH1") + if channels[ch].Name is None + else self.names.append(channels[ch].Name) + ) + except AttributeError: + channels = None + elif not czi_box.has_channels: + logger.info("Channel(s) information not found.") + + if czi_box.has_disp: + try: + # extract the relevant dimension metadata_tools + disp = czi_box.ImageDocument.Metadata.DisplaySetting.Channels.Channel + if isinstance(disp, Box): + self.get_channel_info(disp) + elif isinstance(disp, BoxList): + for ch in range(len(disp)): + self.get_channel_info(disp[ch]) + except AttributeError: + disp = None + + elif not czi_box.has_disp: + # print("DisplaySetting(s) not found.") + logger.info("DisplaySetting(s) not found.") + + def get_channel_info(self, display: Box): + if display is not None: + ( + self.dyes.append("Dye-CH1") + if display.ShortName is None + else self.dyes.append(display.ShortName) + ) + ( + self.colors.append("#80808000") + if display.Color is None + else self.colors.append(display.Color) + ) + + low = 0.0 if display.Low is None else float(display.Low) + high = 0.5 if display.High is None else float(display.High) + + self.clims.append([low, high]) + ( + self.gamma.append(0.85) + if display.Gamma is None + else self.gamma.append(float(display.Gamma)) + ) + else: + self.dyes.append("Dye-CH1") + self.colors.append("#80808000") + self.clims.append([0.0, 0.5]) + self.gamma.append(0.85) + diff --git a/src/czitools/metadata_tools/czi_metadata.py b/src/czitools/metadata_tools/czi_metadata.py new file mode 100644 index 0000000..fa6ec96 --- /dev/null +++ b/src/czitools/metadata_tools/czi_metadata.py @@ -0,0 +1,712 @@ +# -*- coding: utf-8 -*- + +################################################################# +# File : czi_metadata.py +# Author : sebi06 +# +# Disclaimer: The code is purely experimental. Feel free to +# use it at your own risk. +# +################################################################# + +#from __future__ import annotations +from typing import List, Dict, Tuple, Optional, Any, Union +import os +import xml.etree.ElementTree as ET +from pylibCZIrw import czi as pyczi +from czitools.utils import logger, misc +import numpy as np +from dataclasses import dataclass, field +from pathlib import Path +from box import Box +import validators +from dataclasses import asdict +from czitools.metadata_tools.dimension import CziDimensions +from czitools.metadata_tools.boundingbox import CziBoundingBox +from czitools.metadata_tools.attachment import CziAttachments +from czitools.metadata_tools.channel import CziChannelInfo +from czitools.metadata_tools.scaling import CziScaling +from czitools.metadata_tools.sample import CziSampleInfo +from czitools.metadata_tools.objective import CziObjectives +from czitools.metadata_tools.microscope import CziMicroscope +from czitools.metadata_tools.add_metadata import CziAddMetaData +from czitools.metadata_tools.detector import CziDetector +from czitools.utils.box import get_czimd_box +from czitools.metadata_tools.helper import DictObj + +logger = logger.get_logger() + + +@dataclass +class CziMetadata: + filepath: Union[str, os.PathLike[str]] + filename: Optional[str] = field(init=False, default=None) + dirname: Optional[str] = field(init=False, default=None) + is_url: Optional[bool] = field(init=False, default=False) + software_name: Optional[str] = field(init=False, default=None) + software_version: Optional[str] = field(init=False, default=None) + acquisition_date: Optional[str] = field(init=False, default=None) + czi_box: Optional[Box] = field(init=False, default=None) + pyczi_dims: Optional[Dict[str, tuple]] = field( + init=False, default_factory=lambda: {} + ) + aics_dimstring: Optional[str] = field(init=False, default=None) + aics_dims_shape: Optional[List[Dict[str, tuple]]] = field( + init=False, default_factory=lambda: {} + ) + aics_size: Optional[Tuple[int]] = field(init=False, default_factory=lambda: ()) + aics_ismosaic: Optional[bool] = field(init=False, default=None) + aics_dim_order: Optional[Dict[str, int]] = field( + init=False, default_factory=lambda: {} + ) + aics_dim_index: Optional[List[int]] = field(init=False, default_factory=lambda: []) + aics_dim_valid: Optional[int] = field(init=False, default=None) + aics_posC: Optional[int] = field(init=False, default=None) + pixeltypes: Optional[Dict[int, str]] = field(init=False, default_factory=lambda: {}) + isRGB: Optional[bool] = field(init=False, default=False) + has_scenes: Optional[bool] = field(init=False, default=False) + has_label: Optional[bool] = field(init=False, default=False) + has_preview: Optional[bool] = field(init=False, default=False) + attachments: Optional[CziAttachments] = field(init=False, default=None) + npdtype: Optional[List[Any]] = field(init=False, default_factory=lambda: []) + maxvalue: Optional[List[int]] = field(init=False, default_factory=lambda: []) + image: Optional[CziDimensions] = field(init=False, default=None) + bbox: Optional[CziBoundingBox] = field(init=False, default=None) + channelinfo: Optional[CziChannelInfo] = field(init=False, default=None) + scale: Optional[CziScaling] = field(init=False, default=None) + objective: Optional[CziObjectives] = field(init=False, default=None) + detector: Optional[CziDetector] = field(init=False, default=None) + microscope: Optional[CziMicroscope] = field(init=False, default=None) + sample: Optional[CziSampleInfo] = field(init=False, default=None) + add_metadata: Optional[CziAddMetaData] = field(init=False, default=None) + array6d_size: Optional[Tuple[int]] = field(init=False, default_factory=lambda: ()) + scene_size_consistent: Optional[Tuple[int]] = field( + init=False, default_factory=lambda: () + ) + """ + Create a CziMetadata object from the filename of the CZI image file. + """ + + def __post_init__(self): + if validators.url(str(self.filepath)): + self.pyczi_readertype = pyczi.ReaderFileInputTypes.Curl + self.is_url = True + logger.info( + "FilePath is a valid link. Only pylibCZIrw functionality is available." + ) + else: + self.pyczi_readertype = pyczi.ReaderFileInputTypes.Standard + self.is_url = False + + if isinstance(self.filepath, Path): + # convert to string + self.filepath = str(self.filepath) + + # get directory and filename etc. + self.dirname = str(Path(self.filepath).parent) + self.filename = str(Path(self.filepath).name) + + # get the metadata_tools as box + self.czi_box = get_czimd_box(self.filepath) + + # check for existence of scenes + self.has_scenes = self.czi_box.has_scenes + + # get acquisition data and SW version + if self.czi_box.ImageDocument.Metadata.Information.Application is not None: + self.software_name = ( + self.czi_box.ImageDocument.Metadata.Information.Application.Name + ) + self.software_version = ( + self.czi_box.ImageDocument.Metadata.Information.Application.Version + ) + + if self.czi_box.ImageDocument.Metadata.Information.Image is not None: + self.acquisition_date = ( + self.czi_box.ImageDocument.Metadata.Information.Image.AcquisitionDateAndTime + ) + + if self.czi_box.ImageDocument.Metadata.Information.Document is not None: + self.creation_date = ( + self.czi_box.ImageDocument.Metadata.Information.Document.CreationDate + ) + self.user_name = ( + self.czi_box.ImageDocument.Metadata.Information.Document.UserName + ) + + # get the dimensions and order + self.image = CziDimensions(self.czi_box) + + # get metadata_tools using pylibCZIrw + with pyczi.open_czi(self.filepath, self.pyczi_readertype) as czidoc: + # get dimensions + self.pyczi_dims = czidoc.total_bounding_box + + # get the pixel typed for all channels + self.pixeltypes = czidoc.pixel_types + self.isRGB, self.consistent_pixeltypes = self.check_if_rgb(self.pixeltypes) + + # check for consistent scene shape + self.scene_shape_is_consistent = self.check_scenes_shape( + czidoc, size_s=self.image.SizeS + ) + + if not self.is_url: + # get some additional metadata_tools using aicspylibczi + try: + from aicspylibczi import CziFile + + # get the general CZI object using aicspylibczi + aicsczi = CziFile(self.filepath) + + self.aics_dimstring = aicsczi.dims + self.aics_dims_shape = aicsczi.get_dims_shape() + self.aics_size = aicsczi.size + self.aics_ismosaic = aicsczi.is_mosaic() + ( + self.aics_dim_order, + self.aics_dim_index, + self.aics_dim_valid, + ) = self.get_dimorder(aicsczi.dims) + self.aics_posC = self.aics_dim_order["C"] + + except ImportError as e: + # print("Package aicspylibczi not found. Use Fallback values.") + logger.warning("Package aicspylibczi not found. Use Fallback values.") + + for ch, px in self.pixeltypes.items(): + npdtype, maxvalue = self.get_dtype_fromstring(px) + self.npdtype.append(npdtype) + self.maxvalue.append(maxvalue) + + # try to guess if the CZI is a mosaic file + if self.image.SizeM is None or self.image.SizeM == 1: + self.ismosaic = False + else: + self.ismosaic = True + + # get the bounding boxes + self.bbox = CziBoundingBox(self.czi_box) + + # get information about channels + self.channelinfo = CziChannelInfo(self.czi_box) + + # get scaling info + self.scale = CziScaling(self.czi_box) + + # get objective information + self.objective = CziObjectives(self.czi_box) + + # get detector information + self.detector = CziDetector(self.czi_box) + + # get detector information + self.microscope = CziMicroscope(self.czi_box) + + # get information about sample carrier and wells etc. + self.sample = CziSampleInfo(self.czi_box) + + # get additional metainformation + self.add_metadata = CziAddMetaData(self.czi_box) + + # check for attached label or preview image + self.attachments = CziAttachments(self.czi_box) + + # can be also used without creating an instance of the class + @staticmethod + def get_dtype_fromstring( + pixeltype: str, + ) -> Tuple[Optional[np.dtype], Optional[int]]: + dtype = None + maxvalue = None + + if pixeltype == "gray16" or pixeltype == "Gray16": + dtype = np.dtype(np.uint16) + maxvalue = 65535 + if pixeltype == "gray8" or pixeltype == "Gray8": + dtype = np.dtype(np.uint8) + maxvalue = 255 + if pixeltype == "bgr48" or pixeltype == "Bgr48": + dtype = np.dtype(np.uint16) + maxvalue = 65535 + if pixeltype == "bgr24" or pixeltype == "Bgr24": + dtype = np.dtype(np.uint8) + maxvalue = 255 + if pixeltype == "bgr96float" or pixeltype == "Bgr96Float": + dtype = np.dtype(np.uint16) + maxvalue = 65535 + + return dtype, maxvalue + + @staticmethod + def get_dimorder(dim_string: str) -> Tuple[Dict, List, int]: + """Get the order of dimensions from dimension string + + :param dim_string: string containing the dimensions + :type dim_string: str + :return: dims_dict - dictionary with the dimensions and its positions + :rtype: dict + :return: dimindex_list - list with indices of dimensions + :rtype: list + :return: numvalid_dims - number of valid dimensions + :rtype: integer + """ + + dimindex_list = [] + dims = ["R", "I", "M", "H", "V", "B", "S", "T", "C", "Z", "Y", "X", "A"] + dims_dict = {} + + # loop over all dimensions and find the index + for d in dims: + dims_dict[d] = dim_string.find(d) + dimindex_list.append(dim_string.find(d)) + + # check if a dimension really exists + numvalid_dims = sum(i >= 0 for i in dimindex_list) + + return dims_dict, dimindex_list, numvalid_dims + + @staticmethod + def check_scenes_shape(czidoc: pyczi.CziReader, size_s: Union[int, None]) -> bool: + """Check if all scenes have the same shape. + + Args: + czidoc (pyczi.CziReader): CziReader to read the properties + size_s (Union[int, None]): Size of scene dimension + + Returns: + bool: True is all scenes have identical XY shape + """ + scene_width = [] + scene_height = [] + scene_shape_is_consistent = False + + if size_s is not None: + for s in range(size_s): + scene_width.append(czidoc.scenes_bounding_rectangle[s].w) + scene_height.append(czidoc.scenes_bounding_rectangle[s].h) + + # check if all entries in list are the same + sw = scene_width.count(scene_width[0]) == len(scene_width) + sh = scene_height.count(scene_height[0]) == len(scene_height) + + # only if entries for X and Y are all the same as the shape is consistent + if sw is True and sh is True: + scene_shape_is_consistent = True + + else: + scene_shape_is_consistent = True + + return scene_shape_is_consistent + + @staticmethod + def check_if_rgb(pixeltypes: Dict) -> Tuple[bool, bool]: + is_rgb = False + + for k, v in pixeltypes.items(): + if "Bgr" in v: + is_rgb = True + + # flag to check if all elements are same + is_consistant = True + + # extracting value to compare + test_val = list(pixeltypes.values())[0] + + for ele in pixeltypes: + if pixeltypes[ele] != test_val: + is_consistant = False + break + + return is_rgb, is_consistant + + +def get_metadata_as_object(filepath: Union[str, os.PathLike[str]]) -> DictObj: + """ + Get the complete CZI metadata_tools as an object created based on the + dictionary created from the XML data. + """ + + if isinstance(filepath, Path): + # convert to string + filepath = str(filepath) + + # get metadata_tools dictionary using pylibCZIrw + with pyczi.open_czi(filepath) as czidoc: + md_dict = czidoc.metadata + + return DictObj(md_dict) + + + + + +def obj2dict(obj: Any, sort: bool = True) -> Dict[str, Any]: + """ + obj2dict: Convert a class attributes and their values to a dictionary + + Args: + obj (Any): Class to be converted + sort (bool, optional): Sort the resulting dictionary by key names. Defaults to True. + + Returns: + Dict[str, Any]: The resulting disctionary. + """ + + # https://stackoverflow.com/questions/7963762/what-is-the-most-economical-way-to-convert-nested-python-objects-to-dictionaries + + if not hasattr(obj, "__dict__"): + return obj + + result = {} + + for key, val in obj.__dict__.items(): + if key.startswith("_"): + continue + + element = [] + + if isinstance(val, list): + for item in val: + element.append(obj2dict(item)) + else: + element = obj2dict(val) + + result[key] = element + + # delete key "czisource" + if "czisource" in result.keys(): + del result["czisource"] + + if sort: + return misc.sort_dict_by_key(result) + + elif not sort: + return result + + +def writexml( + filepath: Union[str, os.PathLike[str]], xmlsuffix: str = "_CZI_MetaData.xml" +) -> str: + """ + writexml: Write XML information of CZI to disk + + Args: + filepath (Union[str, os.PathLike[str]]): CZI image filename + xmlsuffix (str, optional): suffix for the XML file that will be created, defaults to '_CZI_MetaData.xml'. Defaults to '_CZI_MetaData.xml'. + + Returns: + str: filename of the XML file + """ + + if isinstance(filepath, Path): + # convert to string + filepath = str(filepath) + + # get the raw metadata_tools as XML or dictionary + with pyczi.open_czi(filepath) as czidoc: + metadata_xmlstr = czidoc.raw_metadata + + # change file name + xmlfile = filepath.replace(".czi", xmlsuffix) + + # get tree from string + tree = ET.ElementTree(ET.fromstring(metadata_xmlstr)) + + # write XML file to same folder + tree.write(xmlfile, encoding="utf-8", method="xml") + + return xmlfile + + +def create_md_dict_red( + metadata: CziMetadata, sort: bool = True, remove_none: bool = True +) -> Dict: + """ + create_mdict_red: Created a reduced metadata_tools dictionary + + Args: + metadata: CziMetadata class + sort: sort the dictionary + remove_none: Remove None values from dictionary + + Returns: dictionary with the metadata_tools + + """ + + # create a dictionary with the metadata_tools + md_dict = { + "Directory": metadata.dirname, + "Filename": metadata.filename, + "AcqDate": metadata.acquisition_date, + "CreationDate": metadata.creation_date, + "UserName": metadata.user_name, + "SW-App": metadata.software_version, + "SW-Version": metadata.software_name, + "SizeX": metadata.image.SizeX, + "SizeY": metadata.image.SizeY, + "SizeZ": metadata.image.SizeZ, + "SizeC": metadata.image.SizeC, + "SizeT": metadata.image.SizeT, + "SizeS": metadata.image.SizeS, + "SizeB": metadata.image.SizeB, + "SizeM": metadata.image.SizeM, + "SizeH": metadata.image.SizeH, + "SizeI": metadata.image.SizeI, + "isRGB": metadata.isRGB, + "array6d_size": metadata.array6d_size, + "has_scenes": metadata.has_scenes, + "has_label": metadata.attachments.has_label, + "has_preview": metadata.attachments.has_preview, + "has_prescan": metadata.attachments.has_prescan, + "ismosaic": metadata.ismosaic, + "ObjNA": metadata.objective.NA, + "ObjMag": metadata.objective.totalmag, + "TubelensMag": metadata.objective.tubelensmag, + "XScale": metadata.scale.X, + "YScale": metadata.scale.Y, + "XScale_sf": metadata.scale.X_sf, + "YScale_sf": metadata.scale.Y_sf, + "ZScale": metadata.scale.Z, + "ScaleRatio_XYZ": metadata.scale.ratio, + "ChannelNames": metadata.channelinfo.names, + "ChannelDyes": metadata.channelinfo.dyes, + "ChannelColors": metadata.channelinfo.colors, + "WellArrayNames": metadata.sample.well_array_names, + "WellIndicies": metadata.sample.well_indices, + "WellPositionNames": metadata.sample.well_position_names, + "WellRowID": metadata.sample.well_rowID, + "WellColumnID": metadata.sample.well_colID, + "WellCounter": metadata.sample.well_counter, + "SceneCenterStageX": metadata.sample.scene_stageX, + "SceneCenterStageY": metadata.sample.scene_stageX, + "ImageStageX": metadata.sample.image_stageX, + "ImageStageY": metadata.sample.image_stageX, + "TotalBoundingBox": metadata.bbox.total_bounding_box, + } + + # check for extra entries when reading mosaic file with a scale factor + if hasattr(metadata.image, "SizeX_sf"): + md_dict["XScale_sf"] = metadata.scale.X_sf + md_dict["YScale_sf"] = metadata.scale.Y_sf + + if metadata.has_scenes: + md_dict["SizeX_scene"] = metadata.image.SizeX_scene + md_dict["SizeY_scene"] = metadata.image.SizeY_scene + + if remove_none: + md_dict = misc.remove_none_from_dict(md_dict) + + if sort: + return misc.sort_dict_by_key(md_dict) + if not sort: + return md_dict + + +# def get_czimd_box( +# filepath: Union[str, os.PathLike[str]] +# ) -> Box: +# """ +# get_czimd_box: Get CZI metadata_tools as a python-box. For details: https://pypi.org/project/python-box/ +# +# Args: +# filepath (Union[str, os.PathLike[str]]): Filepath of the CZI file +# +# Returns: +# Box: CZI metadata_tools as a Box object +# """ +# +# readertype = pyczi.ReaderFileInputTypes.Standard +# +# if validators.url(str(filepath)): +# readertype = pyczi.ReaderFileInputTypes.Curl +# +# # get metadata_tools dictionary using pylibCZIrw +# with pyczi.open_czi(str(filepath), readertype) as czi_document: +# metadata_dict = czi_document.metadata_tools +# +# czimd_box = Box( +# metadata_dict, +# conversion_box=True, +# default_box=True, +# default_box_attr=None, +# default_box_create_on_get=True, +# # default_box_no_key_error=True +# ) +# +# # add the filepath +# czimd_box.filepath = filepath +# czimd_box.is_url = validators.url(str(filepath)) +# czimd_box.czi_open_arg = readertype +# +# # set the defaults to False +# czimd_box.has_customattr = False +# czimd_box.has_experiment = False +# czimd_box.has_disp = False +# czimd_box.has_hardware = False +# czimd_box.has_scale = False +# czimd_box.has_instrument = False +# czimd_box.has_microscopes = False +# czimd_box.has_detectors = False +# czimd_box.has_objectives = False +# czimd_box.has_tubelenses = False +# czimd_box.has_disp = False +# czimd_box.has_channels = False +# czimd_box.has_info = False +# czimd_box.has_app = False +# czimd_box.has_doc = False +# czimd_box.has_image = False +# czimd_box.has_scenes = False +# czimd_box.has_dims = False +# czimd_box.has_layers = False +# +# if "Experiment" in czimd_box.ImageDocument.Metadata: +# czimd_box.has_experiment = True +# +# if "HardwareSetting" in czimd_box.ImageDocument.Metadata: +# czimd_box.has_hardware = True +# +# if "CustomAttributes" in czimd_box.ImageDocument.Metadata: +# czimd_box.has_customattr = True +# +# if "Information" in czimd_box.ImageDocument.Metadata: +# czimd_box.has_info = True +# +# if "Application" in czimd_box.ImageDocument.Metadata.Information: +# czimd_box.has_app = True +# +# if "Document" in czimd_box.ImageDocument.Metadata.Information: +# czimd_box.has_doc = True +# +# if "Image" in czimd_box.ImageDocument.Metadata.Information: +# czimd_box.has_image = True +# +# if "Dimensions" in czimd_box.ImageDocument.Metadata.Information.Image: +# czimd_box.has_dims = True +# +# if ( +# "Channels" +# in czimd_box.ImageDocument.Metadata.Information.Image.Dimensions +# ): +# czimd_box.has_channels = True +# +# if "S" in czimd_box.ImageDocument.Metadata.Information.Image.Dimensions: +# czimd_box.has_scenes = True +# +# if "Instrument" in czimd_box.ImageDocument.Metadata.Information: +# czimd_box.has_instrument = True +# +# if "Detectors" in czimd_box.ImageDocument.Metadata.Information.Instrument: +# czimd_box.has_detectors = True +# +# if "Microscopes" in czimd_box.ImageDocument.Metadata.Information.Instrument: +# czimd_box.has_microscopes = True +# +# if "Objectives" in czimd_box.ImageDocument.Metadata.Information.Instrument: +# czimd_box.has_objectives = True +# +# if "TubeLenses" in czimd_box.ImageDocument.Metadata.Information.Instrument: +# czimd_box.has_tubelenses = True +# +# if "Scaling" in czimd_box.ImageDocument.Metadata: +# czimd_box.has_scale = True +# +# if "DisplaySetting" in czimd_box.ImageDocument.Metadata: +# czimd_box.has_disp = True +# +# if "Layers" in czimd_box.ImageDocument.Metadata: +# czimd_box.has_layers = True +# +# return czimd_box + + +def create_md_dict_nested( + metadata: CziMetadata, sort: bool = True, remove_none: bool = True +) -> Dict: + """ + Create nested dictionary from metadata_tools + + Args: + metadata (CziMetadata): CzIMetaData object_ + sort (bool, optional): Sort the dictionary_. Defaults to True. + remove_none (bool, optional): Remove None values from dictionary. Defaults to True. + + Returns: + Dict: Nested dictionary with reduced set of metadata_tools + """ + + md_array6d = {"array6d": metadata.array6d_size} + + md_box_image = Box(asdict(metadata.image)) + del md_box_image.czisource + + md_box_scale = Box(asdict(metadata.scale)) + del md_box_scale.czisource + + md_box_sample = Box(asdict(metadata.sample)) + del md_box_sample.czisource + + md_box_objective = Box(asdict(metadata.objective)) + del md_box_objective.czisource + + md_box_channels = Box(asdict(metadata.channelinfo)) + del md_box_channels.czisource + + md_box_bbox = Box(metadata.bbox.total_bounding_box) + # del md_box_bbox.czisource + + md_box_info = Box( + { + "Directory": metadata.dirname, + "Filename": metadata.filename, + "AcqDate": metadata.acquisition_date, + "CreationDate": metadata.creation_date, + "UserName": metadata.user_name, + "SW-App": metadata.software_version, + "SW-Version": metadata.software_name, + } + ) + + md_box_image_add = Box( + { + "isRGB": metadata.isRGB, + "has_scenes": metadata.has_scenes, + "ismosaic": metadata.ismosaic, + } + ) + + md_box_image += md_box_image_add + + IDs = [ + "array6d", + "image", + "scale", + "sample", + "objectives", + "channels", + "bbox", + "info", + ] + + mds = [ + md_array6d, + md_box_image.to_dict(), + md_box_scale.to_dict(), + md_box_sample.to_dict(), + md_box_objective.to_dict(), + md_box_channels.to_dict(), + md_box_bbox.to_dict(), + md_box_info.to_dict(), + ] + + md_dict = dict(zip(IDs, mds)) + + if remove_none: + md_dict = misc.remove_none_from_dict(md_dict) + + if sort: + return misc.sort_dict_by_key(md_dict) + if not sort: + return md_dict + + return md_dict diff --git a/src/czitools/metadata_tools/detector.py b/src/czitools/metadata_tools/detector.py new file mode 100644 index 0000000..0f557d1 --- /dev/null +++ b/src/czitools/metadata_tools/detector.py @@ -0,0 +1,67 @@ +from typing import Union, List +from dataclasses import dataclass, field +from box import Box, BoxList +import os +from czitools.utils.logger import get_logger +from czitools.utils.box import get_czimd_box + +logger = get_logger() + + +@dataclass +class CziDetector: + czisource: Union[str, os.PathLike[str], Box] + model: List[str] = field(init=False, default_factory=lambda: []) + name: List[str] = field(init=False, default_factory=lambda: []) + Id: List[str] = field(init=False, default_factory=lambda: []) + modeltype: List[str] = field(init=False, default_factory=lambda: []) + gain: List[float] = field(init=False, default_factory=lambda: []) + zoom: List[float] = field(init=False, default_factory=lambda: []) + amplificationgain: List[float] = field(init=False, default_factory=lambda: []) + + def __post_init__(self): + logger.info("Reading Detector Information from CZI image data.") + + if isinstance(self.czisource, Box): + czi_box = self.czisource + else: + czi_box = get_czimd_box(self.czisource) + + # check if there are any detector entries inside the dictionary + if czi_box.ImageDocument.Metadata.Information.Instrument is not None: + # get the data for the detectors + detectors = ( + czi_box.ImageDocument.Metadata.Information.Instrument.Detectors.Detector + ) + + # check for detector Id, Name, Model and Type + if isinstance(detectors, Box): + self.Id.append(detectors.Id) + self.name.append(detectors.Name) + self.model.append(detectors.Model) + self.modeltype.append(detectors.Type) + self.gain.append(detectors.Gain) + self.zoom.append(detectors.Zoom) + self.amplificationgain.append(detectors.AmplificationGain) + + # and do this differently in case of a list of detectors + elif isinstance(detectors, BoxList): + for d in range(len(detectors)): + self.Id.append(detectors[d].Id) + self.name.append(detectors[d].Name) + self.model.append(detectors[d].Model) + self.modeltype.append(detectors[d].Type) + self.gain.append(detectors[d].Gain) + self.zoom.append(detectors[d].Zoom) + self.amplificationgain.append(detectors[d].AmplificationGain) + + elif czi_box.ImageDocument.Metadata.Information.Instrument is None: + logger.info("No Detetctor(s) information found.") + self.model = [None] + self.name = [None] + self.Id = [None] + self.modeltype = [None] + self.gain = [None] + self.zoom = [None] + self.amplificationgain = [None] + diff --git a/src/czitools/metadata_tools/dimension.py b/src/czitools/metadata_tools/dimension.py new file mode 100644 index 0000000..84687d6 --- /dev/null +++ b/src/czitools/metadata_tools/dimension.py @@ -0,0 +1,106 @@ +from typing import Tuple, Optional, Union +from dataclasses import dataclass, field, fields, Field +from box import Box +import os +from czitools.utils.logger import get_logger +from czitools.utils.box import get_czimd_box +from pylibCZIrw import czi as pyczi + +logger = get_logger() + + +@dataclass +class CziDimensions: + czisource: Union[str, os.PathLike[str], Box] + SizeX: Optional[int] = field( + init=False, default=None + ) # total size X including scenes + SizeY: Optional[int] = field( + init=False, default=None + ) # total size Y including scenes + SizeX_scene: Optional[int] = field( + init=False, default=None + ) # size X per scene (if equal scene sizes) + SizeY_scene: Optional[int] = field( + init=False, default=None + ) # size Y per scene (if equal scene sizes) + SizeS: Optional[int] = field(init=False, default=None) + SizeT: Optional[int] = field(init=False, default=None) + SizeZ: Optional[int] = field(init=False, default=None) + SizeC: Optional[int] = field(init=False, default=None) + SizeM: Optional[int] = field(init=False, default=None) + SizeR: Optional[int] = field(init=False, default=None) + SizeH: Optional[int] = field(init=False, default=None) + SizeI: Optional[int] = field(init=False, default=None) + SizeV: Optional[int] = field(init=False, default=None) + SizeB: Optional[int] = field(init=False, default=None) + """Dataclass containing the image dimensions. + + Information official CZI Dimension Characters: + "X":"Width" : width of image [pixel] + "Y":"Height" : height of image [pixel] + "C":"Channel" : number of channels + "Z":"Slice" : number of z-planes + "T":"Time" : number of time points + "R":"Rotation" : + "S":"Scene" : contiguous regions of interest in a mosaic image + "I":"Illumination" : SPIM direction for LightSheet + "B":"Block" : acquisition + "M":"Mosaic" : index of tile for compositing a scene + "H":"Phase" : e.g. Airy detector fibers + "V":"View" : e.g. for SPIM + """ + + def __post_init__(self): + + self.set_dimensions() + logger.info("Reading Dimensions from CZI image data.") + + # set dimensions in XY with respect to possible down scaling + self.SizeX_sf = self.SizeX + self.SizeY_sf = self.SizeY + + def set_dimensions(self): + """Populate the image dimensions with the detected values from the metadata_tools""" + + # get the Box and extract the relevant dimension metadata_tools + if isinstance(self.czisource, Box): + czi_box = self.czisource + else: + czi_box = get_czimd_box(self.czisource) + + dimensions = czi_box.ImageDocument.Metadata.Information.Image + + # define the image dimensions to check for + dims = [ + "SizeX", + "SizeY", + "SizeS", + "SizeT", + "SizeZ", + "SizeC", + "SizeM", + "SizeR", + "SizeH", + "SizeI", + "SizeV", + "SizeB", + ] + + cls_fields: Tuple[Field, ...] = fields(self.__class__) + for fd in cls_fields: + if fd.name in dims: + if dimensions[fd.name] is not None: + setattr(self, fd.name, int(dimensions[fd.name])) + + if czi_box.has_scenes: + try: + with pyczi.open_czi(czi_box.filepath, czi_box.czi_open_arg) as czidoc: + self.SizeX_scene = czidoc.scenes_bounding_rectangle[0].w + self.SizeY_scene = czidoc.scenes_bounding_rectangle[0].h + except KeyError as e: + self.SizeX_scene = None + self.SizeY_scene = None + logger.warning( + "Scenes Dimension detected but no bounding rectangle information found." + ) \ No newline at end of file diff --git a/src/czitools/metadata_tools/helper.py b/src/czitools/metadata_tools/helper.py new file mode 100644 index 0000000..e290d2f --- /dev/null +++ b/src/czitools/metadata_tools/helper.py @@ -0,0 +1,31 @@ +from dataclasses import dataclass, field +from enum import Enum + +@dataclass +class ValueRange: + lo: float + hi: float + + +class AttachmentType(Enum): + SlidePreview = 1 + Label = 2 + Prescan = 3 + +class DictObj: + """ + Create an object based on a dictionary. See https://joelmccune.com/python-dictionary-as-object/ + """ + + # TODO: is this class still needed because of using python-box + + def __init__(self, in_dict: dict) -> None: + assert isinstance(in_dict, dict) + + for key, val in in_dict.items(): + if isinstance(val, (list, tuple)): + setattr( + self, key, [DictObj(x) if isinstance(x, dict) else x for x in val] + ) + else: + setattr(self, key, DictObj(val) if isinstance(val, dict) else val) \ No newline at end of file diff --git a/src/czitools/metadata_tools/microscope.py b/src/czitools/metadata_tools/microscope.py new file mode 100644 index 0000000..aea4cbc --- /dev/null +++ b/src/czitools/metadata_tools/microscope.py @@ -0,0 +1,41 @@ +from typing import Union, Optional +from dataclasses import dataclass, field +from box import Box +import os +from czitools.utils.logger import get_logger +from czitools.utils.box import get_czimd_box + + +logger = get_logger() + +@dataclass +class CziMicroscope: + czisource: Union[str, os.PathLike[str], Box] + Id: Optional[str] = field(init=False) + Name: Optional[str] = field(init=False) + System: Optional[str] = field(init=False) + + def __post_init__(self): + logger.info("Reading Microscope Information from CZI image data.") + + if isinstance(self.czisource, Box): + czi_box = self.czisource + else: + czi_box = get_czimd_box(self.czisource) + + if czi_box.ImageDocument.Metadata.Information.Instrument is None: + self.Id = None + self.Name = None + self.System = None + logger.info("No Microscope information found.") + + else: + self.Id = ( + czi_box.ImageDocument.Metadata.Information.Instrument.Microscopes.Microscope.Id + ) + self.Name = ( + czi_box.ImageDocument.Metadata.Information.Instrument.Microscopes.Microscope.Name + ) + self.System = ( + czi_box.ImageDocument.Metadata.Information.Instrument.Microscopes.Microscope.System + ) diff --git a/src/czitools/metadata_tools/objective.py b/src/czitools/metadata_tools/objective.py new file mode 100644 index 0000000..a24c373 --- /dev/null +++ b/src/czitools/metadata_tools/objective.py @@ -0,0 +1,94 @@ +from typing import Union, Optional, List +from dataclasses import dataclass, field +from box import Box, BoxList +import os +from czitools.utils.logger import get_logger +from czitools.utils.box import get_czimd_box + +logger = get_logger() + + +@dataclass +class CziObjectives: + czisource: Union[str, os.PathLike[str], Box] + NA: List[Optional[float]] = field(init=False, default_factory=lambda: []) + objmag: List[Optional[float]] = field(init=False, default_factory=lambda: []) + Id: List[Optional[str]] = field(init=False, default_factory=lambda: []) + name: List[Optional[str]] = field(init=False, default_factory=lambda: []) + model: List[Optional[str]] = field(init=False, default_factory=lambda: []) + immersion: List[Optional[str]] = field(init=False, default_factory=lambda: []) + tubelensmag: List[Optional[float]] = field(init=False, default_factory=lambda: []) + totalmag: List[Optional[float]] = field(init=False, default_factory=lambda: []) + + def __post_init__(self): + logger.info("Reading Objective Information from CZI image data.") + + if isinstance(self.czisource, Box): + czi_box = self.czisource + else: + czi_box = get_czimd_box(self.czisource) + + # check if objective metadata_tools actually exist + if czi_box.has_objectives: + try: + # get objective data + objective = ( + czi_box.ImageDocument.Metadata.Information.Instrument.Objectives.Objective + ) + if isinstance(objective, Box): + self.get_objective_info(objective) + elif isinstance(objective, BoxList): + for obj in range(len(objective)): + self.get_objective_info(objective[obj]) + except AttributeError: + objective = None + + elif not czi_box.has_objectives: + logger.info("No Objective Information found.") + + # check if tubelens metadata_tools exist + if czi_box.has_tubelenses: + # get tubelenes data + tubelens = ( + czi_box.ImageDocument.Metadata.Information.Instrument.TubeLenses.TubeLens + ) + + if isinstance(tubelens, Box): + if tubelens.Magnification is not None: + self.tubelensmag.append(float(tubelens.Magnification)) + elif tubelens.Magnification is None: + logger.warning("No tubelens magnification found. Use 1.0x instead.") + self.tubelensmag.append(1.0) + + elif isinstance(tubelens, BoxList): + for tl in range(len(tubelens)): + self.tubelensmag.append(float(tubelens[tl].Magnification)) + + # some additional checks to calc the total magnification + if self.objmag is not None and self.tubelensmag is not None: + self.totalmag = [i * j for i in self.objmag for j in self.tubelensmag] + + elif not czi_box.has_tubelens: + logger.info("No Tublens Information found.") + + if self.objmag is not None and self.tubelensmag == []: + self.totalmag = self.objmag + + def get_objective_info(self, objective: Box): + self.name.append(objective.Name) + self.immersion.append(objective.Immersion) + + if objective.LensNA is not None: + self.NA.append(float(objective.LensNA)) + + if objective.Id is not None: + self.Id.append(objective.Id) + + if objective.NominalMagnification is not None: + self.objmag.append(float(objective.NominalMagnification)) + + if None in self.name and self.name.count(None) == 1: + self.name.remove(None) + self.name.append(objective.Manufacturer.Model) + + diff --git a/src/czitools/metadata_tools/sample.py b/src/czitools/metadata_tools/sample.py new file mode 100644 index 0000000..dd66b06 --- /dev/null +++ b/src/czitools/metadata_tools/sample.py @@ -0,0 +1,109 @@ +from typing import Union, List, Dict +from dataclasses import dataclass, field +from box import Box, BoxList +import os +import numpy as np +from collections import Counter +from czitools.utils.logger import get_logger +from czitools.utils.box import get_czimd_box +from czitools.utils.misc import get_planetable +from czitools.metadata_tools.dimension import CziDimensions + +logger = get_logger() + + +@dataclass +class CziSampleInfo: + czisource: Union[str, os.PathLike[str], Box] + well_array_names: List[str] = field(init=False, default_factory=lambda: []) + well_indices: List[int] = field(init=False, default_factory=lambda: []) + well_position_names: List[str] = field(init=False, default_factory=lambda: []) + well_colID: List[int] = field(init=False, default_factory=lambda: []) + well_rowID: List[int] = field(init=False, default_factory=lambda: []) + well_counter: Dict = field(init=False, default_factory=lambda: {}) + scene_stageX: List[float] = field(init=False, default_factory=lambda: []) + scene_stageY: List[float] = field(init=False, default_factory=lambda: []) + image_stageX: float = field(init=False, default=None) + image_stageY: float = field(init=False, default=None) + + def __post_init__(self): + logger.info("Reading SampleCarrier Information from CZI image data.") + + if isinstance(self.czisource, Box): + czi_box = self.czisource + else: + czi_box = get_czimd_box(self.czisource) + + size_s = CziDimensions(czi_box).SizeS + + if size_s is not None: + try: + allscenes = ( + czi_box.ImageDocument.Metadata.Information.Image.Dimensions.S.Scenes.Scene + ) + + if isinstance(allscenes, Box): + self.get_well_info(allscenes) + + if isinstance(allscenes, BoxList): + for well in range(len(allscenes)): + self.get_well_info(allscenes[well]) + + except AttributeError: + logger.info("CZI contains no scene metadata_tools.") + + elif size_s is None: + logger.info( + "No Scene or Well information found. Try to read XY Stage Coordinates from subblocks." + ) + + try: + # read the data from CSV file + planetable = get_planetable( + czi_box.filepath, pt_complete=False, t=0, c=0, z=0 + ) + + self.image_stageX = float(planetable["X[micron]"][0]) + self.image_stageY = float(planetable["Y[micron]"][0]) + + except Exception as e: + logger.error(e) + + def get_well_info(self, well: Box): + + # check the ArrayName + if well.ArrayName is not None: + self.well_array_names.append(well.ArrayName) + # count the well instances + self.well_counter = Counter(self.well_array_names) + + if well.Index is not None: + self.well_indices.append(int(well.Index)) + elif well.Index is None: + logger.info("Well Index not found.") + self.well_indices.append(1) + + if well.Name is not None: + self.well_position_names.append(well.Name) + elif well.Name is None: + logger.info("Well Position Names not found.") + self.well_position_names.append("P1") + + if well.Shape is not None: + self.well_colID.append(int(well.Shape.ColumnIndex)) + self.well_rowID.append(int(well.Shape.RowIndex)) + elif well.Shape is None: + logger.info("Well Column or Row IDs not found.") + self.well_colID.append(0) + self.well_rowID.append(0) + + if well.CenterPosition is not None: + # get the SceneCenter Position + sx = well.CenterPosition.split(",")[0] + sy = well.CenterPosition.split(",")[1] + self.scene_stageX.append(np.double(sx)) + self.scene_stageY.append(np.double(sy)) + elif well.CenterPosition is None: + logger.info("Stage Positions XY not found.") + self.scene_stageX.append(0.0) + self.scene_stageY.append(0.0) diff --git a/src/czitools/metadata_tools/scaling.py b/src/czitools/metadata_tools/scaling.py new file mode 100644 index 0000000..20570cb --- /dev/null +++ b/src/czitools/metadata_tools/scaling.py @@ -0,0 +1,79 @@ +from typing import Union, Optional, Annotated, Dict +from dataclasses import dataclass, field +from box import Box, BoxList +import os +import numpy as np +from czitools.utils.logger import get_logger +from czitools.utils.box import get_czimd_box +from czitools.metadata_tools.helper import ValueRange + +logger = get_logger() + + +@dataclass +class CziScaling: + czisource: Union[str, os.PathLike[str], Box] + X: Optional[float] = field(init=False, default=None) + Y: Optional[float] = field(init=False, default=None) + Z: Optional[float] = field(init=False, default=None) + X_sf: Optional[float] = field(init=False, default=None) + Y_sf: Optional[float] = field(init=False, default=None) + ratio: Optional[Dict[str, float]] = field(init=False, default=None) + # ratio_sf: Optional[Dict[str, float]] = field(init=False, default=None) + # scalefactorXY: Optional[float] = field(init=False, default=None) + unit: Optional[str] = field(init=True, default="micron") + zoom: Annotated[float, ValueRange(0.01, 1.0)] = field(init=True, default=1.0) + + def __post_init__(self): + logger.info("Reading Scaling from CZI image data.") + + if isinstance(self.czisource, Box): + czi_box = self.czisource + else: + czi_box = get_czimd_box(self.czisource) + + if czi_box.has_scale: + distances = czi_box.ImageDocument.Metadata.Scaling.Items.Distance + + # get the scaling values for X,Y and Z + self.X = np.round(self.safe_get_scale(distances, 0), 3) + self.Y = np.round(self.safe_get_scale(distances, 1), 3) + self.Z = np.round(self.safe_get_scale(distances, 2), 3) + + # calc the scaling values for X,Y when applying downscaling + self.X_sf = np.round(self.X * (1 / self.zoom), 3) + self.Y_sf = np.round(self.Y * (1 / self.zoom), 3) + + # calc the scaling ratio + self.ratio = { + "xy": np.round(self.X / self.Y, 3), + "zx": np.round(self.Z / self.X, 3), + "zx_sf": np.round(self.Z / self.X_sf, 3), + } + + elif not czi_box.has_scale: + logger.info("No scaling information found.") + + @staticmethod + def safe_get_scale(dist: BoxList, idx: int) -> Optional[float]: + scales = ["X", "Y", "Z"] + + try: + # get the scaling value in [micron] + sc = float(dist[idx].Value) * 1000000 + + # check for the value = 0.0 + if sc == 0.0: + sc = 1.0 + logger.info( + "Detected Scaling = 0.0 for " + + scales[idx] + + " Using default = 1.0 [micron]." + ) + return sc + + except (IndexError, TypeError, AttributeError): + logger.info( + "No " + scales[idx] + "-Scaling found. Using default = 1.0 [micron]." + ) + return 1.0 diff --git a/src/czitools/metadata_tools/scene.py b/src/czitools/metadata_tools/scene.py new file mode 100644 index 0000000..38a080a --- /dev/null +++ b/src/czitools/metadata_tools/scene.py @@ -0,0 +1,38 @@ +from typing import Optional, Union +from dataclasses import dataclass, field +import os +from czitools.utils.logger import get_logger +from pylibCZIrw import czi as pyczi +from pathlib import Path + +logger = get_logger() + + +@dataclass +class CziScene: + filepath: Union[str, os.PathLike[str]] + index: int + bbox: Optional[pyczi.Rectangle] = field(init=False, default=None) + xstart: Optional[int] = field(init=False, default=None) + ystart: Optional[int] = field(init=False, default=None) + width: Optional[int] = field(init=False, default=None) + height: Optional[int] = field(init=False, default=None) + + def __post_init__(self): + logger.info("Reading Scene Information from CZI image data.") + + if isinstance(self.filepath, Path): + # convert to string + self.filepath = str(self.filepath) + + # get scene information from the CZI file + with pyczi.open_czi(self.filepath) as czidoc: + try: + self.bbox = czidoc.scenes_bounding_rectangle[self.index] + self.xstart = self.bbox.x + self.ystart = self.bbox.y + self.width = self.bbox.w + self.height = self.bbox.h + except KeyError: + # in case an invalid index was used + logger.info("No Scenes detected.") diff --git a/src/czitools/napari_tools/__init__.py b/src/czitools/napari_tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/czitools/napari_tools.py b/src/czitools/napari_tools/napari_tools.py similarity index 86% rename from src/czitools/napari_tools.py rename to src/czitools/napari_tools/napari_tools.py index f821e6e..a543fe7 100644 --- a/src/czitools/napari_tools.py +++ b/src/czitools/napari_tools/napari_tools.py @@ -9,47 +9,51 @@ # ################################################################# -import napari - -from PyQt5.QtWidgets import ( - QVBoxLayout, - QWidget, - QTableWidget, - QTableWidgetItem, -) - -from PyQt5.QtCore import Qt -from PyQt5 import QtWidgets -from PyQt5.QtGui import QFont -from czitools import metadata_tools as czimd -from czitools import misc_tools -from czitools.datatreewiget import DataTreeWidget -import numpy as np -from typing import ( - List, - Dict, - Tuple, - Optional, - Type, - Any, - Union, - Literal, - Mapping, - Annotated, -) -from napari.utils.colormaps import Colormap -from napari.utils import resize_dask_cache -import dask.array as da -from dataclasses import dataclass -from czitools import logger as LOGGER - -logger = LOGGER.get_logger() - - -@dataclass -class ValueRange: - lo: float - hi: float +import sys +from czitools.utils import logger +from czitools.metadata_tools.helper import ValueRange + +logger = logger.get_logger() + +# check if Napari is actually installed +try: + import napari +except (ImportError, ModuleNotFoundError) as error: + # Output expected ImportErrors. + logger.error(error.__class__.__name__ + ": " + error.msg) + sys.exit(1) +else: + + from PyQt5.QtWidgets import ( + QVBoxLayout, + QWidget, + QTableWidget, + QTableWidgetItem, + ) + + from PyQt5.QtCore import Qt + from PyQt5 import QtWidgets + from PyQt5.QtGui import QFont + from czitools.metadata_tools import czi_metadata as czimd + from czitools.utils import misc + from czitools.utils.datatreewiget import DataTreeWidget + import numpy as np + from typing import ( + List, + Dict, + Tuple, + Optional, + Type, + Any, + Union, + Literal, + Mapping, + Annotated, + ) + from napari.utils.colormaps import Colormap + from napari.utils import resize_dask_cache + import dask.array as da + from dataclasses import dataclass class MdTableWidget(QWidget): @@ -65,13 +69,13 @@ def __init__(self) -> None: header.setDefaultAlignment(Qt.AlignLeft) def update_metadata(self, md_dict: Dict) -> None: - """Update the table with the metadata from the dictionary + """Update the table with the metadata_tools from the dictionary Args: md_dict (Dict): Metadata dictionary """ - # number of rows is set to number of metadata entries + # number of rows is set to number of metadata_tools entries row_count = len(md_dict) col_count = 2 self.mdtable.setColumnCount(col_count) @@ -79,7 +83,7 @@ def update_metadata(self, md_dict: Dict) -> None: row = 0 - # update the table with the entries from metadata dictionary + # update the table with the entries from metadata_tools dictionary for key, value in md_dict.items(): newkey = QTableWidgetItem(key) self.mdtable.setItem(row, 0, newkey) @@ -137,7 +141,7 @@ def show( dask_cache_size: Annotated[float, ValueRange(0.5, 0.9)] = 0.5, ) -> List: """Display the multidimensional array inside the Napari viewer. - Optionally the CziMetadata class will be used show a table with the metadata. + Optionally the CziMetadata class will be used show a table with the metadata_tools. Every channel will be added as a new layer to the viewer. Args: @@ -149,9 +153,9 @@ def show( contrast (Literal[str], optional): method to be used to calculate an appropriate display scaling. - "calc" : real min & max calculation (might be slow) be calculated (slow) - "napari_auto" : Let Napari figure out a display scaling. Will look in the center of an image! - - "from_czi" : use the display scaling from ZEN stored inside the CZI metadata. Defaults to "calc". + - "from_czi" : use the display scaling from ZEN stored inside the CZI metadata_tools. Defaults to "calc". gamma (float, optional): gamma value for the Viewer for all layers Defaults to 0.85. - show_metadata (mdviewoption, optional): Option to show metadata as tree or table. Defaults to "tree". + show_metadata (mdviewoption, optional): Option to show metadata_tools as tree or table. Defaults to "tree". name_sliders (bool, optional): option to use the dimension letters as slider labels for the viewer. Defaults to False. dask_cache_size(float, optional): option to resize the dask cache used for opportunistic caching. Range [0 - 1] @@ -176,7 +180,7 @@ def show( scalefactors = [1.0] * len(array.shape) # testing - #scalefactors = [1.0] * 6 + # scalefactors = [1.0] * 6 # modify the tuple for the scales for napari @@ -187,13 +191,13 @@ def show( scalefactors[dim_order["Z"]] = metadata.scale.ratio["zx_sf"] * 1.001 if show_metadata.lower != "none": - # add PyQTGraph DataTreeWidget to Napari viewer to show the metadata + # add PyQTGraph DataTreeWidget to Napari viewer to show the metadata_tools if show_metadata == "tree": md_dict = czimd.create_md_dict_nested(metadata, sort=True, remove_none=True) mdtree = MdTreeWidget(data=md_dict, expandlevel=1) viewer.window.add_dock_widget(mdtree, name="MetadataTree", area="right") - # add QTableWidget DataTreeWidget to Napari viewer to show the metadata + # add QTableWidget DataTreeWidget to Napari viewer to show the metadata_tools if show_metadata == "table": md_dict = czimd.create_md_dict_red(metadata, sort=True, remove_none=True) mdtable = MdTableWidget() @@ -212,7 +216,7 @@ def show( try: # get the channel name chname = metadata.channelinfo.names[ch] - # inside the CZI metadata colors are defined as ARGB hexstring + # inside the CZI metadata_tools colors are defined as ARGB hexstring rgb = "#" + metadata.channelinfo.colors[ch][3:] ncmap = Colormap(["#000000", rgb], name="cm_" + chname) except (KeyError, IndexError) as e: @@ -223,7 +227,7 @@ def show( # cut out channel if metadata.image.SizeC is not None: - channel = misc_tools.slicedim(array, ch, dim_order["C"]) + channel = misc.slicedim(array, ch, dim_order["C"]) if metadata.image.SizeC is None: channel = array @@ -234,7 +238,7 @@ def show( if contrast == "calc": # really calculate the min and max values - might be slow - sc = misc_tools.calc_scaling(channel, corr_min=1.1, corr_max=0.9) + sc = misc.calc_scaling(channel, corr_min=1.1, corr_max=0.9) logger.info(f"Calculated Display Scaling (min & max): {sc}") # add channel to napari viewer @@ -317,7 +321,7 @@ def show( def rename_sliders(sliders: Tuple, dim_order: Dict) -> Tuple: - """Rename the sliders inside the Napari viewer based on the metadata + """Rename the sliders inside the Napari viewer based on the metadata_tools Args: diff --git a/src/czitools/read_tools/__init__.py b/src/czitools/read_tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/czitools/read_tools.py b/src/czitools/read_tools/read_tools.py similarity index 93% rename from src/czitools/read_tools.py rename to src/czitools/read_tools/read_tools.py index 7064fb9..312fee3 100644 --- a/src/czitools/read_tools.py +++ b/src/czitools/read_tools/read_tools.py @@ -10,36 +10,17 @@ ################################################################# from typing import ( - List, Dict, Tuple, Optional, - Type, - Any, Union, - Mapping, - Literal, - Annotated, -) -from typing import ( - List, - Dict, - Tuple, - Optional, - Type, - Any, - Union, - Mapping, - Literal, Annotated, ) from pylibCZIrw import czi as pyczi -from czitools import metadata_tools as czimd -from czitools import misc_tools -from dataclasses import dataclass, field, fields, Field +from czitools.metadata_tools import czi_metadata as czimd +from czitools.utils import misc import numpy as np from pathlib import Path -import dask import dask.array as da import dask.delayed import os @@ -47,25 +28,13 @@ from tqdm.contrib.itertools import product import tempfile import shutil -from czitools import logger as LOGGER -from enum import Enum -from dataclasses import dataclass +from czitools.utils import logger +from czitools.metadata_tools.helper import ValueRange +from czitools.metadata_tools.helper import AttachmentType # from memory_profiler import profile -logger = LOGGER.get_logger() - - -class AttachmentType(Enum): - SlidePreview = 1 - Label = 2 - Prescan = 3 - - -@dataclass -class ValueRange: - lo: float - hi: float +logger = logger.get_logger() # code for which memory has to be monitored @@ -93,7 +62,7 @@ def read_6darray( zoom (float, options): Downsample CZI image by a factor [0.01 - 1.0]. Defaults to 1.0. Returns: - Tuple[array6d, mdata]: output as 6D dask array and metadata + Tuple[array6d, mdata]: output as 6D dask array and metadata_tools """ if isinstance(filepath, Path): @@ -112,7 +81,7 @@ def read_6darray( ) zoom = 0.01 - # get the complete metadata at once as one big class + # get the complete metadata_tools at once as one big class mdata = czimd.CziMetadata(filepath) if not mdata.consistent_pixeltypes: @@ -168,10 +137,10 @@ def read_6darray( # size_y = czidoc.total_bounding_rectangle.h # check if dimensions are None (because they do not exist for that image) - size_c = misc_tools.check_dimsize(mdata.image.SizeC, set2value=1) - size_z = misc_tools.check_dimsize(mdata.image.SizeZ, set2value=1) - size_t = misc_tools.check_dimsize(mdata.image.SizeT, set2value=1) - size_s = misc_tools.check_dimsize(mdata.image.SizeS, set2value=1) + size_c = misc.check_dimsize(mdata.image.SizeC, set2value=1) + size_z = misc.check_dimsize(mdata.image.SizeZ, set2value=1) + size_t = misc.check_dimsize(mdata.image.SizeT, set2value=1) + size_s = misc.check_dimsize(mdata.image.SizeS, set2value=1) s_start = 0 s_end = size_s @@ -278,7 +247,7 @@ def read_6darray( # for testing array6d = array6d.rechunk(chunks=(1, 1, 1, size_z, image2d.shape[0], image2d.shape[1], 3)) - # update metadata + # update metadata_tools mdata.array6d_size = array6d.shape return array6d, mdata @@ -304,19 +273,19 @@ def read_6darray_lazy( Respectively {"Z":(5, 5)} will return a single z-plane with index 5. Returns: - Tuple[array6d, mdata]: output as 6D dask array and metadata + Tuple[array6d, mdata]: output as 6D dask array and metadata_tools """ if isinstance(filepath, Path): # convert to string filepath = str(filepath) - # get the complete metadata at once as one big class + # get the complete metadata_tools at once as one big class mdata = czimd.CziMetadata(filepath) if not mdata.consistent_pixeltypes: logger.info("Detected PixelTypes ar not consistent. Cannot create array6d") - return None, mdata, "" + return None, mdata # check planes if not planes is False: @@ -326,7 +295,7 @@ def read_6darray_lazy( logger.info( f"Planes indices (zero-based) for {planes[k]} are invalid. BBox for {[k]}: {mdata.bbox.total_bounding_box[k]}" ) - return None, mdata, "" + return None, mdata if not mdata.scene_shape_is_consistent and not "S" in planes.keys(): logger.info("Scenes have inconsistent shape. Cannot read 6D array") @@ -348,10 +317,10 @@ def read_6darray_lazy( size_y = czidoc.total_bounding_rectangle.h # check if dimensions are None (because they do not exist for that image) - size_c = misc_tools.check_dimsize(mdata.image.SizeC, set2value=1) - size_z = misc_tools.check_dimsize(mdata.image.SizeZ, set2value=1) - size_t = misc_tools.check_dimsize(mdata.image.SizeT, set2value=1) - size_s = misc_tools.check_dimsize(mdata.image.SizeS, set2value=1) + size_c = misc.check_dimsize(mdata.image.SizeC, set2value=1) + size_z = misc.check_dimsize(mdata.image.SizeZ, set2value=1) + size_t = misc.check_dimsize(mdata.image.SizeT, set2value=1) + size_s = misc.check_dimsize(mdata.image.SizeS, set2value=1) s_start = 0 s_end = size_s @@ -488,7 +457,7 @@ def read_6darray_lazy( # for testing array6d = array6d.rechunk(chunks=(1, 1, 1, size_z, size_y, size_x, 3)) - return array6d, mdata # , dim_string + return array6d, mdata @dask.delayed diff --git a/src/czitools/utils/__init__.py b/src/czitools/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/czitools/utils/box.py b/src/czitools/utils/box.py new file mode 100644 index 0000000..e86231c --- /dev/null +++ b/src/czitools/utils/box.py @@ -0,0 +1,123 @@ +from __future__ import annotations +from typing import Union +import os +from pylibCZIrw import czi as pyczi +from box import Box +import validators + + +def get_czimd_box( + filepath: Union[str, os.PathLike[str]] +) -> Box: + """ + get_czimd_box: Get CZI metadata_tools as a python-box. For details: https://pypi.org/project/python-box/ + + Args: + filepath (Union[str, os.PathLike[str]]): Filepath of the CZI file + + Returns: + Box: CZI metadata_tools as a Box object + """ + + readertype = pyczi.ReaderFileInputTypes.Standard + + if validators.url(str(filepath)): + readertype = pyczi.ReaderFileInputTypes.Curl + + # get metadata_tools dictionary using pylibCZIrw + with pyczi.open_czi(str(filepath), readertype) as czi_document: + metadata_dict = czi_document.metadata + + czimd_box = Box( + metadata_dict, + conversion_box=True, + default_box=True, + default_box_attr=None, + default_box_create_on_get=True, + # default_box_no_key_error=True + ) + + # add the filepath + czimd_box.filepath = filepath + czimd_box.is_url = validators.url(str(filepath)) + czimd_box.czi_open_arg = readertype + + # set the defaults to False + czimd_box.has_customattr = False + czimd_box.has_experiment = False + czimd_box.has_disp = False + czimd_box.has_hardware = False + czimd_box.has_scale = False + czimd_box.has_instrument = False + czimd_box.has_microscopes = False + czimd_box.has_detectors = False + czimd_box.has_objectives = False + czimd_box.has_tubelenses = False + czimd_box.has_disp = False + czimd_box.has_channels = False + czimd_box.has_info = False + czimd_box.has_app = False + czimd_box.has_doc = False + czimd_box.has_image = False + czimd_box.has_scenes = False + czimd_box.has_dims = False + czimd_box.has_layers = False + + if "Experiment" in czimd_box.ImageDocument.Metadata: + czimd_box.has_experiment = True + + if "HardwareSetting" in czimd_box.ImageDocument.Metadata: + czimd_box.has_hardware = True + + if "CustomAttributes" in czimd_box.ImageDocument.Metadata: + czimd_box.has_customattr = True + + if "Information" in czimd_box.ImageDocument.Metadata: + czimd_box.has_info = True + + if "Application" in czimd_box.ImageDocument.Metadata.Information: + czimd_box.has_app = True + + if "Document" in czimd_box.ImageDocument.Metadata.Information: + czimd_box.has_doc = True + + if "Image" in czimd_box.ImageDocument.Metadata.Information: + czimd_box.has_image = True + + if "Dimensions" in czimd_box.ImageDocument.Metadata.Information.Image: + czimd_box.has_dims = True + + if ( + "Channels" + in czimd_box.ImageDocument.Metadata.Information.Image.Dimensions + ): + czimd_box.has_channels = True + + if "S" in czimd_box.ImageDocument.Metadata.Information.Image.Dimensions: + czimd_box.has_scenes = True + + if "Instrument" in czimd_box.ImageDocument.Metadata.Information: + czimd_box.has_instrument = True + + if "Detectors" in czimd_box.ImageDocument.Metadata.Information.Instrument: + czimd_box.has_detectors = True + + if "Microscopes" in czimd_box.ImageDocument.Metadata.Information.Instrument: + czimd_box.has_microscopes = True + + if "Objectives" in czimd_box.ImageDocument.Metadata.Information.Instrument: + czimd_box.has_objectives = True + + if "TubeLenses" in czimd_box.ImageDocument.Metadata.Information.Instrument: + czimd_box.has_tubelenses = True + + if "Scaling" in czimd_box.ImageDocument.Metadata: + czimd_box.has_scale = True + + if "DisplaySetting" in czimd_box.ImageDocument.Metadata: + czimd_box.has_disp = True + + if "Layers" in czimd_box.ImageDocument.Metadata: + czimd_box.has_layers = True + + return czimd_box diff --git a/src/czitools/datatreewiget.py b/src/czitools/utils/datatreewiget.py similarity index 100% rename from src/czitools/datatreewiget.py rename to src/czitools/utils/datatreewiget.py diff --git a/src/czitools/log.ini b/src/czitools/utils/log.ini.old similarity index 100% rename from src/czitools/log.ini rename to src/czitools/utils/log.ini.old diff --git a/src/czitools/logger.py b/src/czitools/utils/logger.py similarity index 97% rename from src/czitools/logger.py rename to src/czitools/utils/logger.py index d5faf40..77650c4 100644 --- a/src/czitools/logger.py +++ b/src/czitools/utils/logger.py @@ -58,7 +58,7 @@ def get_logger(log_to_file: bool = False): - file_handler: logging to a file (logs all five levels) if `log_to_file` is True Args: - log_to_file (bool): Whether or not to log to file. Defaults to False. + log_to_file (bool): Whether to log to file. Defaults to False. Returns: logging.Logger: A custom logger with two handlers. diff --git a/src/czitools/misc_tools.py b/src/czitools/utils/misc.py similarity index 62% rename from src/czitools/misc_tools.py rename to src/czitools/utils/misc.py index 4b2d7ab..74313e6 100644 --- a/src/czitools/misc_tools.py +++ b/src/czitools/utils/misc.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- ################################################################# -# File : misc_tools.py +# File : misc.py # Author : sebi06 # # Disclaimer: The code is purely experimental. Feel free to @@ -19,16 +19,14 @@ import time from pathlib import Path import dateutil.parser as dt -from itertools import product -from czitools import metadata_tools as czimd -from typing import List, Dict, Tuple, Optional, Type, Any, Union, Mapping -from dataclasses import make_dataclass, fields, dataclass -from czitools import logger as LOGGER +from tqdm.contrib.itertools import product +from typing import Dict, Tuple, Any, Union import validators -import re -#from urllib.parse import urlparse +from aicspylibczi import CziFile +from czitools.utils import logger -logger = LOGGER.get_logger() + +logger = logger.get_logger() def openfile( @@ -145,15 +143,15 @@ def calc_scaling( def md2dataframe( md_dict: Dict, paramcol: str = "Parameter", keycol: str = "Value" ) -> pd.DataFrame: - """Converts the given metadata dictionary to a Pandas DataFrame. + """Converts the given metadata_tools dictionary to a Pandas DataFrame. Args: - md_dict (dict): A dictionary containing metadata. - paramcol (str, optional): The name of the column for metadata parameters. Defaults to "Parameter". - keycol (str, optional): The name of the column for metadata values. Defaults to "Value". + md_dict (dict): A dictionary containing metadata_tools. + paramcol (str, optional): The name of the column for metadata_tools parameters. Defaults to "Parameter". + keycol (str, optional): The name of the column for metadata_tools values. Defaults to "Value". Returns: - pd.DataFrame: A Pandas DataFrame containing all the metadata. + pd.DataFrame: A Pandas DataFrame containing all the metadata_tools. """ mdframe = pd.DataFrame(columns=[paramcol, keycol]) @@ -253,224 +251,249 @@ def check_dimsize( def get_planetable( czifile: Union[str, os.PathLike[str]], norm_time: bool = True, - savetable: bool = False, - separator: str = ",", - read_one_only: bool = False, - index: bool = True, -) -> Tuple[pd.DataFrame, Optional[str]]: + pt_complete: bool = True, + t: int = 0, + c: int = 0, + z: int = 0, +) -> pd.DataFrame: """Get the planetable from the individual subblocks Args: czifile: the source for the CZI image file norm_time: normalize the timestamps - savetable: option save the planetable as CSV file - separator: specify the separator for the CSV file - read_one_only: option to read only the first entry - index: option to save CSV file with an index + pt_complete: Read data from all subblocks or only use the ones to be shown in the plot Returns: - Planetable as pd.DataFrame or np.recarray and the location of the CSV file + Planetable as pd.DataFrame """ - try: - from aicspylibczi import CziFile - - if isinstance(czifile, Path): - # convert to string - czifile = str(czifile) - - if validators.url(czifile): - logger.warning("Reading PlaneTable from CZI via a link is not supported.") - - # get the czi metadata - czi_dimensions = czimd.CziDimensions(czifile) - aicsczi = CziFile(czifile) - - # initialize the plane table - df_czi = pd.DataFrame( - columns=[ - "Subblock", - "Scene", - "Tile", - "T", - "Z", - "C", - "X[micron]", - "Y[micron]", - "Z[micron]", - "Time[s]", - "xstart", - "ystart", - "width", - "height", - ] - ) - - # define subblock counter - sbcount = -1 - - # check if dimensions are None (because they do not exist for that image) - size_c = check_dimsize(czi_dimensions.SizeC, set2value=1) - size_z = check_dimsize(czi_dimensions.SizeZ, set2value=1) - size_t = check_dimsize(czi_dimensions.SizeT, set2value=1) - size_s = check_dimsize(czi_dimensions.SizeS, set2value=1) - size_m = check_dimsize(czi_dimensions.SizeM, set2value=1) - - def getsbinfo(subblock: Any) -> Tuple[float, float, float, float]: - try: - time = subblock.findall(".//AcquisitionTime")[0].text - timestamp = dt.parse(time).timestamp() - except IndexError as e: - timestamp = 0.0 - - try: - xpos = np.double(subblock.findall(".//StageXPosition")[0].text) - except IndexError as e: - xpos = 0.0 - - try: - ypos = np.double(subblock.findall(".//StageYPosition")[0].text) - except IndexError as e: - ypos = 0.0 - - try: - zpos = np.double(subblock.findall(".//FocusPosition")[0].text) - except IndexError as e: - zpos = 0.0 - - return timestamp, xpos, ypos, zpos - - # do if the data is not a mosaic - if size_m > 1: - for s, m, t, z, c in product( - range(size_s), - range(size_m), - range(size_t), - range(size_z), - range(size_c), - ): - sbcount += 1 - - # get x, y, width and height for a specific tile - tilebbox = aicsczi.get_mosaic_tile_bounding_box(S=s, M=m, T=t, Z=z, C=c) - - # read information from subblock - sb = aicsczi.read_subblock_metadata( - unified_xml=True, B=0, S=s, M=m, T=t, Z=z, C=c - ) - - # get information from subblock - timestamp, xpos, ypos, zpos = getsbinfo(sb) - - plane = pd.DataFrame( - { - "Subblock": sbcount, - "Scene": s, - "Tile": m, - "T": t, - "Z": z, - "C": c, - "X[micron]": xpos, - "Y[micron]": ypos, - "Z[micron]": zpos, - "Time[s]": timestamp, - "xstart": tilebbox.x, - "ystart": tilebbox.y, - "width": tilebbox.w, - "height": tilebbox.h, - }, - index=[0], - ) - - # df_czi = pd.concat([df_czi, plane], ignore_index=True) - - df_czi = pd.concat([df_czi if not df_czi.empty else None, plane]) - - if read_one_only: - break - - # do if the data is not a mosaic - if size_m == 1: - for s, t, z, c in product( - range(size_s), range(size_t), range(size_z), range(size_c) - ): - sbcount += 1 - - # get x, y, width and height for a specific tile - tilebbox = aicsczi.get_tile_bounding_box(S=s, T=t, Z=z, C=c) - - # read information from subblocks - sb = aicsczi.read_subblock_metadata( - unified_xml=True, B=0, S=s, T=t, Z=z, C=c - ) - - # get information from subblock - timestamp, xpos, ypos, zpos = getsbinfo(sb) - - plane = pd.DataFrame( - { - "Subblock": sbcount, - "Scene": s, - "Tile": 0, - "T": t, - "Z": z, - "C": c, - "X[micron]": xpos, - "Y[micron]": ypos, - "Z[micron]": zpos, - "Time[s]": timestamp, - "xstart": tilebbox.x, - "ystart": tilebbox.y, - "width": tilebbox.w, - "height": tilebbox.h, - }, - index=[0], - ) - - # df_czi = pd.concat([df_czi, plane], ignore_index=True) - - df_czi = pd.concat([df_czi if not df_czi.empty else None, plane]) - - if read_one_only: - break - - # cast data types - df_czi = df_czi.astype( - { - "Subblock": "int32", - "Scene": "int32", - "Tile": "int32", - "T": "int32", - "Z": "int32", - "C": "int16", - "X[micron]": "float", - "Y[micron]": "float", - "Z[micron]": "float", - "xstart": "int32", - "ystart": "int32", - "width": "int32", - "height": "int32", - }, - copy=False, - errors="ignore", - ) - - # normalize time stamps - if norm_time: - df_czi = norm_columns(df_czi, colname="Time[s]", mode="min") - - # save planetable as CSV file - if savetable: - csvfile = save_planetable(df_czi, czifile, separator=separator, index=index) - logger.info(f"PlaneTable saved as CSV file: {csvfile}") - if not savetable: - csvfile = None - - except ImportError as e: - # print("Package aicspylibczi not found. Use Fallback values.") - logger.warning("Package aicspylibczi not found. Cannot extract planetable.") + if isinstance(czifile, Path): + # convert to string + czifile = str(czifile) + + if validators.url(czifile): + logger.warning("Reading PlaneTable from CZI via a link is not supported.") return None, None - return df_czi, csvfile + # initialize the plane table + df_czi = pd.DataFrame( + columns=[ + "Subblock", + "Scene", + "Tile", + "T", + "Z", + "C", + "X[micron]", + "Y[micron]", + "Z[micron]", + "Time[s]", + "xstart", + "ystart", + "width", + "height", + ] + ) + + # define subblock counter + sbcount = -1 + + aicsczi = CziFile(czifile) + dims = aicsczi.get_dims_shape() + + if "S" in dims[0].keys(): + size_s = dims[0]["S"][1] + else: + size_s = 1 + + if "M" in dims[0].keys(): + size_m = dims[0]["M"][1] + else: + size_m = 1 + + if "T" in dims[0].keys(): + size_t = dims[0]["T"][1] + else: + size_t = 1 + + if "C" in dims[0].keys(): + size_c = dims[0]["C"][1] + else: + size_c = 1 + + if "Z" in dims[0].keys(): + size_z = dims[0]["Z"][1] + else: + size_z = 1 + + def getsbinfo(subblock: Any) -> Tuple[float, float, float, float]: + try: + time = subblock.findall(".//AcquisitionTime")[0].text + timestamp = dt.parse(time).timestamp() + except IndexError as e: + timestamp = 0.0 + + try: + xpos = np.double(subblock.findall(".//StageXPosition")[0].text) + except IndexError as e: + xpos = 0.0 + + try: + ypos = np.double(subblock.findall(".//StageYPosition")[0].text) + except IndexError as e: + ypos = 0.0 + + try: + zpos = np.double(subblock.findall(".//FocusPosition")[0].text) + except IndexError as e: + zpos = 0.0 + + return timestamp, xpos, ypos, zpos + + if pt_complete: + t_start = 0 + t_end = size_t + c_start = 0 + c_end = size_c + z_start = 0 + z_end = size_z + + elif not pt_complete: + t_start = t + t_end = t + 1 + c_start = c + c_end = c + 1 + z_start = 0 + z_end = z + 1 + + # do if the data is not a mosaic + if size_m > 1: + # for s, m, t, z, c in product( + # range(size_s), + # range(size_m), + # range(size_t), + # range(size_z), + # range(size_c), + # ): + for s, m, t, c, z in product( + range(size_s), + range(size_m), + enumerate(range(t_start, t_end)), + enumerate(range(c_start, c_end)), + enumerate(range(z_start, z_end)), + desc="Reading sublocks planes", + unit=" 2Dplanes", + ): + sbcount += 1 + + # get x, y, width and height for a specific tile + tilebbox = aicsczi.get_mosaic_tile_bounding_box( + S=s, M=m, T=t[1], Z=z[1], C=c[1] + ) + + # read information from subblock + sb = aicsczi.read_subblock_metadata( + unified_xml=True, B=0, S=s, M=m, T=t[1], Z=z[1], C=c[1] + ) + + # get information from subblock + timestamp, xpos, ypos, zpos = getsbinfo(sb) + + plane = pd.DataFrame( + { + "Subblock": sbcount, + "Scene": s, + "Tile": m, + "T": t[1], + "Z": z[1], + "C": c[1], + "X[micron]": xpos, + "Y[micron]": ypos, + "Z[micron]": zpos, + "Time[s]": timestamp, + "xstart": tilebbox.x, + "ystart": tilebbox.y, + "width": tilebbox.w, + "height": tilebbox.h, + }, + index=[0], + ) + + df_czi = pd.concat([df_czi if not df_czi.empty else None, plane]) + + # do if the data is not a mosaic + if size_m == 1: + # for s, t, z, c in product( + # range(size_s), range(size_t), range(size_z), range(size_c) + # ): + for s, t, c, z in product( + range(size_s), + enumerate(range(t_start, t_end)), + enumerate(range(c_start, c_end)), + enumerate(range(z_start, z_end)), + desc="Reading sublocks planes", + unit=" 2Dplanes", + ): + sbcount += 1 + + # get x, y, width and height for a specific tile + tilebbox = aicsczi.get_tile_bounding_box(S=s, T=t[1], Z=z[1], C=c[1]) + + # read information from subblocks + sb = aicsczi.read_subblock_metadata( + unified_xml=True, B=0, S=s, T=t[1], Z=z[1], C=c[1] + ) + + # get information from subblock + timestamp, xpos, ypos, zpos = getsbinfo(sb) + + plane = pd.DataFrame( + { + "Subblock": sbcount, + "Scene": s, + "Tile": 0, + "T": t[1], + "Z": z[1], + "C": c[1], + "X[micron]": xpos, + "Y[micron]": ypos, + "Z[micron]": zpos, + "Time[s]": timestamp, + "xstart": tilebbox.x, + "ystart": tilebbox.y, + "width": tilebbox.w, + "height": tilebbox.h, + }, + index=[0], + ) + + df_czi = pd.concat([df_czi if not df_czi.empty else None, plane]) + + # cast data types + df_czi = df_czi.astype( + { + "Subblock": "int32", + "Scene": "int32", + "Tile": "int32", + "T": "int32", + "Z": "int32", + "C": "int16", + "X[micron]": "float", + "Y[micron]": "float", + "Z[micron]": "float", + "xstart": "int32", + "ystart": "int32", + "width": "int32", + "height": "int32", + }, + copy=False, + errors="ignore", + ) + + # normalize time stamps + if norm_time: + df_czi = norm_columns(df_czi, colname="Time[s]", mode="min") + + return df_czi def norm_columns( @@ -659,21 +682,13 @@ def download_zip(source_link: str) -> str: import io import zipfile - compressed_data = os .path.join(os.getcwd(), os.path.basename(source_link)) + compressed_data = os.path.join(os.getcwd(), os.path.basename(source_link)) if not os.path.isfile(compressed_data): response = requests.get(GITHUB_IMAGES_PATH, stream=True) compressed_data = io.BytesIO(response.content) - with zipfile.ZipFile(compressed_data, 'r') as zip_accessor: - zip_accessor.extractall('./') + with zipfile.ZipFile(compressed_data, "r") as zip_accessor: + zip_accessor.extractall("./") return compressed_data[:-4] - - - - - - - - diff --git a/src/czitools/visu_tools/__init__.py b/src/czitools/visu_tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/czitools/visu_tools/vis_tools.py b/src/czitools/visu_tools/vis_tools.py new file mode 100644 index 0000000..7e21b75 --- /dev/null +++ b/src/czitools/visu_tools/vis_tools.py @@ -0,0 +1,242 @@ +# -*- coding: utf-8 -*- + +################################################################# +# File : vis_tools.py +# Author : sebi06 +# +# Disclaimer: This code is purely experimental. Feel free to +# use it at your own risk. +# +################################################################# + +import numpy as np +import matplotlib.pyplot as plt +from matplotlib import cm +import plotly.graph_objects as go +from czitools.utils import logger + +logger = logger.get_logger() + + +def scatterplot_mpl( + planetable, + s=0, + t=0, + z=0, + c=0, + msz2d=35, + normz=True, + fig1savename="zsurface2d.png", + fig2savename="zsurface3d.png", + msz3d=20, +): + + # extract XYZ positions + try: + xpos = planetable["X[micron]"] + ypos = planetable["Y[micron]"] + zpos = planetable["Z[micron]"] + except KeyError as e: + xpos = planetable["X [micron]"] + ypos = planetable["Y [micron]"] + zpos = planetable["Z [micron]"] + + # normalize z-data by substracting the minimum value + if normz: + zpos = zpos - zpos.min() + + # create a name for the figure + figtitle = "XYZ-Positions: T=" + str(t) + " Z=" + str(z) + " CH=" + str(c) + + # try to find a "good" aspect ratio for the figures + dx = xpos.max() - xpos.min() + dy = ypos.max() - ypos.min() + fsy = 8 + fsx = int(np.ceil(fsy * dx / dy)) + + # create figure + fig1, ax1 = plt.subplots(1, 1, figsize=(fsx + 1, fsy)) + + # invert the Y-axis --> O,O = Top-Left + ax1.invert_yaxis() + + # configure the axis + ax1.set_title(figtitle) + ax1.set_xlabel("Stage X-Axis [micron]", fontsize=12, fontweight="normal") + ax1.set_ylabel("Stage Y-Axis [micron]", fontsize=12, fontweight="normal") + ax1.grid(True) + ax1.set_aspect("equal", "box") + + # plot data and label the colorbar + sc1 = ax1.scatter( + xpos, + ypos, + marker="s", + c=zpos, + s=msz2d, + facecolor=cm.coolwarm, + edgecolor="black", + ) + + # add the colorbar on the right-hand side + cb1 = plt.colorbar(sc1, fraction=0.046, shrink=0.8, pad=0.04) + + # add a label + if normz: + cb1.set_label( + "Z-Offset [micron]", labelpad=20, fontsize=12, fontweight="normal" + ) + if not normz: + cb1.set_label( + "Z-Position [micron]", labelpad=20, fontsize=12, fontweight="normal" + ) + + # save figure as PNG + fig1.savefig(fig1savename, dpi=100) + logger.info(f"Saved: {fig1savename}") + + # 3D plot of surface + fig2 = plt.figure(figsize=(fsx + 1, fsy)) + ax2 = fig2.add_subplot(111, projection="3d") + + # invert the Y-axis --> O,O = Top-Left + ax2.invert_yaxis() + + # define the labels + ax2.set_xlabel("Stage X-Axis [micron]", fontsize=12, fontweight="normal") + ax2.set_ylabel("Stage Y-Axis [micron]", fontsize=12, fontweight="normal") + ax2.set_title(figtitle) + + # plot data and label the colorbar + sc2 = ax2.scatter( + xpos, + ypos, + zpos, + marker=".", + s=msz3d, + c=zpos, + facecolor=cm.coolwarm, + depthshade=False, + ) + + # add colorbar to the 3d plot + cb2 = plt.colorbar(sc2, shrink=0.8) + # add a label + if normz: + cb2.set_label( + "Z-Offset [micron]", labelpad=20, fontsize=12, fontweight="normal" + ) + if not normz: + cb2.set_label( + "Z-Position [micron]", labelpad=20, fontsize=12, fontweight="normal" + ) + + # save figure as PNG + fig2.savefig(fig2savename, dpi=100) + logger.info(f"Saved: {fig2savename}") + + return fig1, fig2 + + +def scatterplot_plotly( + planetable, + s=0, + t=0, + z=0, + c=0, + msz2d=35, + normz=True, + fig1savename="zsurface2d.html", + fig2savename="zsurface3d.html", + msz3d=20, +): + + # extract XYZ position for the selected channel + try: + xpos = planetable["X[micron]"] + ypos = planetable["Y[micron]"] + zpos = planetable["Z[micron]"] + except KeyError as e: + xpos = planetable["X [micron]"] + ypos = planetable["Y [micron]"] + zpos = planetable["Z [micron]"] + + # normalize z-data by substracting the minimum value + if normz: + zpos = zpos - zpos.min() + scalebar_title = "Z-Offset [micron]" + if not normz: + scalebar_title = "Z-Position [micron]" + + # create a name for the figure + figtitle = "XYZ-Positions: T=" + str(t) + " Z=" + str(z) + " CH=" + str(c) + + fig1 = go.Figure( + data=go.Scatter( + x=xpos, + y=ypos, + mode="markers", + text=np.round(zpos, 1), + marker_symbol="square", + marker_size=msz2d, + marker=dict( + color=zpos, + colorscale="Viridis", + line_width=2, + showscale=True, + colorbar=dict( + thickness=10, title=dict(text=scalebar_title, side="right") + ), + ), + ) + ) + + fig1.update_xaxes(showgrid=True, zeroline=True, automargin=True) + fig1.update_yaxes(showgrid=True, zeroline=True, automargin=True) + fig1["layout"]["yaxis"]["autorange"] = "reversed" + fig1.update_layout( + title=figtitle, + xaxis_title="StageX Position [micron]", + yaxis_title="StageY Position [micron]", + font=dict(size=16, color="Black"), + ) + + # save the figure + fig1.write_html(fig1savename) + logger.info(f"Saved: {fig1savename}") + + fig2 = go.Figure( + data=[ + go.Scatter3d( + x=xpos, + y=ypos, + z=zpos, + mode="markers", + marker=dict( + size=msz3d, + color=zpos, + colorscale="Viridis", + opacity=0.8, + colorbar=dict( + thickness=10, title=dict(text=scalebar_title, side="right") + ), + ), + ) + ] + ) + + fig2.update_xaxes(showgrid=True, zeroline=True, automargin=True) + fig2.update_yaxes(showgrid=True, zeroline=True, automargin=True) + fig2["layout"]["yaxis"]["autorange"] = "reversed" + fig2.update_layout( + title=figtitle, + xaxis_title="StageX Position [micron]", + yaxis_title="StageY Position [micron]", + font=dict(size=16, color="Black"), + ) + + # save the figure + fig2.write_html(fig2savename) + logger.info(f"Saved: {fig2savename}") + + return fig1, fig2 diff --git a/src/czitools/write_tools/__init__.py b/src/czitools/write_tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/czitools/write_tools.py b/src/czitools/write_tools/write_tools.py similarity index 86% rename from src/czitools/write_tools.py rename to src/czitools/write_tools/write_tools.py index 9899623..571f23f 100644 --- a/src/czitools/write_tools.py +++ b/src/czitools/write_tools/write_tools.py @@ -16,13 +16,14 @@ import ome_zarr.reader import ome_zarr.scale import ome_zarr.writer +import ome_zarr.format from ome_zarr.io import parse_url -from typing import List, Dict, Tuple, Optional, Type, Any, Union, Mapping +from typing import Union import shutil import numpy as np -from czitools import logger as LOGGER +from czitools.utils import logger -logger = LOGGER.get_logger() +logger = logger.get_logger() def write_omezarr( @@ -38,7 +39,7 @@ def write_omezarr( array5d (Union[np.ndarary, da.Array]): Up-to 5 dimensional array to be written as OME-ZARR zarr_path (str): Path for the OME-ZARR folder to be created axes (str): Defines the dimension order (lower case string). Defaults to "STCZYX" - overwrite (False): If True, than an existing folder will be overwritten.Defaults to False + overwrite (False): If True, then an existing folder will be overwritten. Defaults to False Returns: str: Path for location of OME-ZARR folder @@ -46,7 +47,7 @@ def write_omezarr( # check number of dimension of input array if len(array5d.shape) > 5: - LOGGER.warning("Input array as more than 5 dimensions.") + logger.warning("Input array as more than 5 dimensions.") return None # make sure lower case is use for axes order @@ -71,7 +72,7 @@ def write_omezarr( # TODO: Add Channel information etc. to the root along those lines """ - # add omero metadata: the napari ome-zarr plugin uses this to pass rendering + # add omero metadata_tools: the napari ome-zarr plugin uses this to pass rendering # options to napari. root.attrs['omero'] = { 'channels': [{ diff --git a/tox.ini b/tox.ini index 0793704..f3ea8cc 100644 --- a/tox.ini +++ b/tox.ini @@ -30,23 +30,24 @@ deps = pytest-xvfb ; sys_platform == 'linux' # you can remove these if you don't use them - numpy + #numpy pylibczirw>=4.1.0 - aicsimageio[all]>=4.11.0 - aicspylibczi - fsspec>=2022.8.0 + #aicsimageio[all]>=4.11.0 + aicspylibczi>=3.1.2 + #fsspec>=2022.8.0 tqdm - xmltodict - napari + #xmltodict + napari[all] # will install PyQt5 pandas magicgui pytest-qt qtpy pyqt5 + ome-zarr zarr python-dateutil python-box[all] - scikit-image>=0.19.3 + #scikit-image>=0.19.3 czifile pyqtgraph