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 typing import Any, Sequence, Union
import numpy as np_
from PIL import Image
from PIL.ExifTags import TAGS
from brick.general.type import array_t
def FindVoxelDimensionInMicron(
data_path: str, size_voxel_in_micron: list = None
) -> array_t:
"""
Find Voxel dimension in micron from the image metadata.
"""
#
if size_voxel_in_micron is not None:
print("VOXEL DIM: [X Y Z] =", size_voxel_in_micron, "micron.")
return np_.array(size_voxel_in_micron)
else: # TODO if not found, try to make something more general
print("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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# 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 = np_.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."
)
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
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: list, 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
)