# Copyright CNRS/Inria/UNS # Contributor(s): Eric Debreuve (since 2019), Morgane Nadal (2020) # # eric.debreuve@cnrs.fr # # This software is governed by the CeCILL license under French law and # abiding by the rules of distribution of free software. You can use, # modify and/ or redistribute the software under the terms of the CeCILL # license as circulated by CEA, CNRS and INRIA at the following URL # "http://www.cecill.info". # # As a counterpart to the access to the source code and rights to copy, # modify and redistribute granted by the license, users are provided only # with a limited warranty and the software's author, the holder of the # economic rights, and the successive licensors have only limited # liability. # # In this respect, the user's attention is drawn to the risks associated # with loading, using, modifying and/or developing or reproducing the # software by the user in light of its specific status of free software, # that may mean that it is complicated to manipulate, and that also # therefore means that it is reserved for developers and experienced # professionals having in-depth computer knowledge. Users are therefore # encouraged to load and test the software's suitability as regards their # requirements in conditions enabling the security of their systems and/or # data to be ensured and, more generally, to use and operate it in the # same conditions as regards security. # # The fact that you are presently reading this means that you have had # knowledge of the CeCILL license and that you accept its terms. import math as mt_ import re as re_ from pathlib import Path as path_t from typing import Any, Sequence, Union import numpy as nmpy from logger_36 import LOGGER from PIL import Image from PIL.ExifTags import TAGS from brick.type.base import array_t def FindVoxelDimensionInMicron( data_path: path_t, voxel_size_in_micron: list = None ) -> array_t: """ Find Voxel dimension in micron from the image metadata. """ # if voxel_size_in_micron is not None: LOGGER.info(f"VOXEL DIM: [X Y Z] = {voxel_size_in_micron} microns") return nmpy.array(voxel_size_in_micron) else: # TODO if not found, try to make something more general LOGGER.warning( "Warning: The size of a voxel is not specified in the parameters." ) try: # Find the voxels dimensions in micron in the metadata. # /!\ Very specific to one type of microscope metadata ! with Image.open(data_path) as img: print(img.size) # Use the exif tags into the image metadata meta_dict = {TAGS.get(key, "missing"): img.tag[key] for key in img.tag} # TODO: replace meta_dict processing below with meta_dict_2 processing... one day meta_dict_2 = {} for tag_id in img.tag: value = img.tag[tag_id] if isinstance(value, bytes): value = value.decode("utf8") value = value.replace("\x00", "") if "\n" in value: sub_dictionary = {} for line in value.split("\n"): if "=" in line: sub_key, sub_value = line.split("=") sub_dictionary[sub_key.strip()] = ConvertedValue( sub_value.strip() ) else: sub_dictionary[ line.strip() ] = "Not a Key=Value entry" value = sub_dictionary else: value = ConvertedValue(value) elif isinstance(value, Sequence) and (value.__len__() == 1): value = ConvertedValue(value[0]) else: value = ConvertedValue(value) meta_dict_2[TAGS.get(tag_id, tag_id)] = value # Decode the tags text metadata = meta_dict["missing"].decode("utf8") metadata = metadata.replace("\x00", "") # LOGGER.info(metadata) # Initialize the list of voxel size in str voxel_size = [] for axe in "XYZ": pattern = ( "Voxel" + axe + ".+\= (\d.+E.\d.)" ) # Regular expression found in metadata voxel_size.append(re_.findall(pattern, metadata)[0]) voxel_size = nmpy.array(list(map(float, voxel_size))) # Conversion meters in micron voxel_size_micron = 1.0e6 * voxel_size print("VOXEL DIM: [X Y Z] =", voxel_size_micron, "micron.") return voxel_size_micron except Exception as exception: raise ValueError( f"{exception}: /!\ Unable to find the voxel dimensions in micron. Specify it in the parameters." ) def ConvertedValue(value: Any) -> Union[Any, bool, int, float]: """""" output = value if isinstance(output, str): no_successful_conversion = True if no_successful_conversion: try: output = int(output) no_successful_conversion = False except ValueError: pass if no_successful_conversion: try: output = float(output) no_successful_conversion = False except ValueError: pass if no_successful_conversion: lower_cased = output.lower() if lower_cased == "false": output = False elif lower_cased == "true": output = True return output def ToPixel( micron: float, voxel_size_micron: array_t, dimension: tuple = (0,), decimals: int = None, ) -> int: """ Dimension correspond to the axis (X,Y,Z) = (0,1,2). Can be used for distance, area and volumes. """ # Conversion of micron into pixels. return round( micron / (mt_.prod(voxel_size_micron[axis] for axis in dimension)), decimals ) def ToMicron( pixel: float, voxel_size_micron: Sequence, dimension: tuple = (0,), decimals: int = None, ) -> float: """ Dimension correspond to the axis (X,Y,Z) = (0,1,2). Can be used for distance, area and volumes. """ # Conversion of pixels into microns return round( pixel * (mt_.prod(voxel_size_micron[axis] for axis in dimension)), decimals )