Newer
Older
# 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
from PIL import Image
from PIL.ExifTags import TAGS
from brick.type.base import array_t
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:
# 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", "")
# 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."
)
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
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
)