NeuNorm 1.6.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- NeuNorm/__init__.py +12 -0
- NeuNorm/_utilities.py +37 -0
- NeuNorm/_version.py +1 -0
- NeuNorm/exporter.py +13 -0
- NeuNorm/loader.py +53 -0
- NeuNorm/normalization.py +928 -0
- NeuNorm/roi.py +35 -0
- neunorm-1.6.12.dist-info/LICENSE +29 -0
- neunorm-1.6.12.dist-info/METADATA +16 -0
- neunorm-1.6.12.dist-info/RECORD +12 -0
- neunorm-1.6.12.dist-info/WHEEL +5 -0
- neunorm-1.6.12.dist-info/top_level.txt +1 -0
NeuNorm/__init__.py
ADDED
NeuNorm/_utilities.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
im_ext = [
|
|
5
|
+
".fits",
|
|
6
|
+
".tiff",
|
|
7
|
+
".tif",
|
|
8
|
+
".hdf",
|
|
9
|
+
".h4",
|
|
10
|
+
".hdf4",
|
|
11
|
+
".he2",
|
|
12
|
+
"h5",
|
|
13
|
+
".hdf5",
|
|
14
|
+
".he5",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_sorted_list_images(folder=""):
|
|
19
|
+
"""return the list of images sorted that have the correct format
|
|
20
|
+
|
|
21
|
+
Parameters:
|
|
22
|
+
folder: string of the path containing the images
|
|
23
|
+
|
|
24
|
+
Return:
|
|
25
|
+
sorted list of only images that can be read by program
|
|
26
|
+
"""
|
|
27
|
+
filenames = [
|
|
28
|
+
name for name in os.listdir(folder) if name.lower().endswith(tuple(im_ext))
|
|
29
|
+
]
|
|
30
|
+
filenames.sort()
|
|
31
|
+
return filenames
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def average_df(df=[]):
|
|
35
|
+
"""if more than 1 DF have been provided, we need to average them"""
|
|
36
|
+
mean_average = np.mean(df, axis=0)
|
|
37
|
+
return mean_average
|
NeuNorm/_version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.6.12"
|
NeuNorm/exporter.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from PIL import Image
|
|
2
|
+
from astropy.io import fits
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def make_tif(data=[], metadata=[], file_name=""):
|
|
6
|
+
"""create tif file"""
|
|
7
|
+
new_image = Image.fromarray(data)
|
|
8
|
+
new_image.save(file_name, tiffinfo=metadata)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def make_fits(data=[], file_name=""):
|
|
12
|
+
"""create fits file"""
|
|
13
|
+
fits.writeto(file_name, data, overwrite=True)
|
NeuNorm/loader.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from astropy.io import fits
|
|
2
|
+
import numpy as np
|
|
3
|
+
import h5py
|
|
4
|
+
from PIL import Image
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def load_hdf(file_name):
|
|
8
|
+
"""load HDF image
|
|
9
|
+
|
|
10
|
+
Parameters
|
|
11
|
+
----------
|
|
12
|
+
full file name of HDF5 file
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
hdf = h5py.File(file_name, "r")["entry"]["data"]["data"].value
|
|
16
|
+
tmp = []
|
|
17
|
+
for iScan in hdf:
|
|
18
|
+
tmp.append(iScan)
|
|
19
|
+
return tmp
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def load_fits(file_name):
|
|
23
|
+
"""load fits image
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
full file name of fits image
|
|
28
|
+
"""
|
|
29
|
+
tmp = []
|
|
30
|
+
try:
|
|
31
|
+
tmp = fits.open(file_name, ignore_missing_end=True)[0].data
|
|
32
|
+
if len(tmp.shape) == 3:
|
|
33
|
+
tmp = tmp.reshape(tmp.shape[1:])
|
|
34
|
+
return tmp
|
|
35
|
+
except OSError:
|
|
36
|
+
raise OSError("Unable to read the FITS file provided!")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def load_tiff(file_name):
|
|
40
|
+
"""load tiff image
|
|
41
|
+
|
|
42
|
+
Parameters:
|
|
43
|
+
-----------
|
|
44
|
+
full file name of tiff image
|
|
45
|
+
"""
|
|
46
|
+
try:
|
|
47
|
+
_image = Image.open(file_name)
|
|
48
|
+
metadata = dict(_image.tag_v2)
|
|
49
|
+
data = np.asarray(_image)
|
|
50
|
+
_image.close()
|
|
51
|
+
return [data, metadata]
|
|
52
|
+
except OSError as e:
|
|
53
|
+
raise OSError(f"Unable to read the TIFF file provided!: {e}")
|
NeuNorm/normalization.py
ADDED
|
@@ -0,0 +1,928 @@
|
|
|
1
|
+
"""Normalization module for NeuNorm"""
|
|
2
|
+
#!/usr/bin/env python
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import numpy as np
|
|
5
|
+
import os
|
|
6
|
+
import logging
|
|
7
|
+
import copy
|
|
8
|
+
from scipy.ndimage import convolve
|
|
9
|
+
from tqdm.auto import tqdm
|
|
10
|
+
|
|
11
|
+
from NeuNorm.loader import load_tiff, load_fits
|
|
12
|
+
from NeuNorm.exporter import make_fits, make_tif
|
|
13
|
+
from NeuNorm.roi import ROI
|
|
14
|
+
from NeuNorm._utilities import get_sorted_list_images, average_df
|
|
15
|
+
from NeuNorm import DataType
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Normalization:
|
|
19
|
+
working_data_type = np.float32
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
self.shape = {"width": np.nan, "height": np.nan}
|
|
23
|
+
self.dict_image = {
|
|
24
|
+
"data": None,
|
|
25
|
+
"oscilation": None,
|
|
26
|
+
"file_name": None,
|
|
27
|
+
"metadata": None,
|
|
28
|
+
"shape": copy.deepcopy(self.shape),
|
|
29
|
+
}
|
|
30
|
+
self.dict_ob = {
|
|
31
|
+
"data": None,
|
|
32
|
+
"oscilation": None,
|
|
33
|
+
"metadata": None,
|
|
34
|
+
"file_name": None,
|
|
35
|
+
"data_mean": None,
|
|
36
|
+
"shape": copy.deepcopy(self.shape),
|
|
37
|
+
}
|
|
38
|
+
self.dict_df = {
|
|
39
|
+
"data": None,
|
|
40
|
+
"metadata": None,
|
|
41
|
+
"data_average": None,
|
|
42
|
+
"file_name": None,
|
|
43
|
+
"shape": copy.deepcopy(self.shape),
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
__roi_dict = {"x0": np.nan, "x1": np.nan, "y0": np.nan, "y1": np.nan}
|
|
47
|
+
self.roi = {
|
|
48
|
+
"normalization": copy.deepcopy(__roi_dict),
|
|
49
|
+
"crop": copy.deepcopy(__roi_dict),
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
self.__exec_process_status = {
|
|
53
|
+
"df_correction": False,
|
|
54
|
+
"normalization": False,
|
|
55
|
+
"crop": False,
|
|
56
|
+
"oscillation": False,
|
|
57
|
+
"bin": False,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
self.data = {}
|
|
61
|
+
self.data["sample"] = self.dict_image
|
|
62
|
+
self.data["ob"] = self.dict_ob
|
|
63
|
+
self.data["df"] = self.dict_df
|
|
64
|
+
self.data["normalized"] = None
|
|
65
|
+
self.export_file_name = None
|
|
66
|
+
|
|
67
|
+
def load(
|
|
68
|
+
self,
|
|
69
|
+
file="",
|
|
70
|
+
folder="",
|
|
71
|
+
data=None,
|
|
72
|
+
data_type="sample",
|
|
73
|
+
auto_gamma_filter=True,
|
|
74
|
+
manual_gamma_filter=False,
|
|
75
|
+
notebook=False,
|
|
76
|
+
manual_gamma_threshold=0.1,
|
|
77
|
+
check_shape=True,
|
|
78
|
+
):
|
|
79
|
+
"""
|
|
80
|
+
Function to read individual files, entire files from folder, list of files or event data arrays.
|
|
81
|
+
Data are also gamma filtered if requested.
|
|
82
|
+
|
|
83
|
+
Parameters:
|
|
84
|
+
file: list - full path to a single file, or list of files
|
|
85
|
+
folder: string - full path to folder containing files to load
|
|
86
|
+
data: numpy array - 2D array of data to load
|
|
87
|
+
data_type: string - 'sample', 'ob' or 'df (default 'sample')
|
|
88
|
+
auto_gamma_filter: boolean - will correct the gamma filter automatically (highest count possible
|
|
89
|
+
for the data type will be replaced by the average of the 9 neighboring pixels) (default True)
|
|
90
|
+
manual_gamma_filter: boolean - apply or not gamma filtering to the data loaded (default False)
|
|
91
|
+
notebooks: boolean - turn on this option if you run the library from a
|
|
92
|
+
notebook to have a progress bar displayed showing you the progress of the loading (default False)
|
|
93
|
+
manual_gamma_threshold: float between 0 and 1 - manual gamma coefficient to use (default 0.1)
|
|
94
|
+
|
|
95
|
+
Warning:
|
|
96
|
+
Algorithm won't be allowed to run if any of the main algorithm have been run already, such as
|
|
97
|
+
oscillation, crop, binning, df_correction.
|
|
98
|
+
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
list_exec_flag = [_flag for _flag in self.__exec_process_status.values()]
|
|
102
|
+
if True in list_exec_flag:
|
|
103
|
+
raise IOError(
|
|
104
|
+
"Operation not allowed as you already worked on this data set!"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
if not file == "":
|
|
108
|
+
if isinstance(file, str):
|
|
109
|
+
self.load_file(
|
|
110
|
+
file=file,
|
|
111
|
+
data_type=data_type,
|
|
112
|
+
auto_gamma_filter=auto_gamma_filter,
|
|
113
|
+
manual_gamma_filter=manual_gamma_filter,
|
|
114
|
+
manual_gamma_threshold=manual_gamma_threshold,
|
|
115
|
+
check_shape=check_shape,
|
|
116
|
+
)
|
|
117
|
+
elif isinstance(file, list):
|
|
118
|
+
# use tqdm to handle the progress bar
|
|
119
|
+
if notebook:
|
|
120
|
+
for _file in tqdm(file, desc=f"Loading {data_type}", leave=False):
|
|
121
|
+
self.load_file(
|
|
122
|
+
file=_file,
|
|
123
|
+
data_type=data_type,
|
|
124
|
+
auto_gamma_filter=auto_gamma_filter,
|
|
125
|
+
manual_gamma_filter=manual_gamma_filter,
|
|
126
|
+
manual_gamma_threshold=manual_gamma_threshold,
|
|
127
|
+
check_shape=check_shape,
|
|
128
|
+
)
|
|
129
|
+
else:
|
|
130
|
+
for _file in file:
|
|
131
|
+
self.load_file(
|
|
132
|
+
file=_file,
|
|
133
|
+
data_type=data_type,
|
|
134
|
+
auto_gamma_filter=auto_gamma_filter,
|
|
135
|
+
manual_gamma_filter=manual_gamma_filter,
|
|
136
|
+
manual_gamma_threshold=manual_gamma_threshold,
|
|
137
|
+
check_shape=check_shape,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
elif not folder == "":
|
|
141
|
+
# load all files from folder
|
|
142
|
+
list_images = get_sorted_list_images(folder=folder)
|
|
143
|
+
# use tqdm to handle the progress bar
|
|
144
|
+
if notebook:
|
|
145
|
+
for _image in tqdm(
|
|
146
|
+
list_images, desc=f"Loading {data_type}", leave=False
|
|
147
|
+
):
|
|
148
|
+
full_path_image = os.path.join(folder, _image)
|
|
149
|
+
self.load_file(
|
|
150
|
+
file=full_path_image,
|
|
151
|
+
data_type=data_type,
|
|
152
|
+
auto_gamma_filter=auto_gamma_filter,
|
|
153
|
+
manual_gamma_filter=manual_gamma_filter,
|
|
154
|
+
manual_gamma_threshold=manual_gamma_threshold,
|
|
155
|
+
check_shape=check_shape,
|
|
156
|
+
)
|
|
157
|
+
else:
|
|
158
|
+
for _image in list_images:
|
|
159
|
+
full_path_image = os.path.join(folder, _image)
|
|
160
|
+
self.load_file(
|
|
161
|
+
file=full_path_image,
|
|
162
|
+
data_type=data_type,
|
|
163
|
+
auto_gamma_filter=auto_gamma_filter,
|
|
164
|
+
manual_gamma_filter=manual_gamma_filter,
|
|
165
|
+
manual_gamma_threshold=manual_gamma_threshold,
|
|
166
|
+
check_shape=check_shape,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
elif data is not None:
|
|
170
|
+
self.load_data(data=data, data_type=data_type, notebook=notebook)
|
|
171
|
+
|
|
172
|
+
def calculate_how_long_its_going_to_take(
|
|
173
|
+
self, index_we_are=-1, time_it_took_so_far=0, total_number_of_loop=1
|
|
174
|
+
):
|
|
175
|
+
"""Estimate how long the loading is going to take according to the time it already took to load the
|
|
176
|
+
first images.
|
|
177
|
+
|
|
178
|
+
Parameters:
|
|
179
|
+
index_we_are: int - index where we are in the list of files to load (default -1)
|
|
180
|
+
time_it_took_so_far: float - time it took so far to load the data (default 0)
|
|
181
|
+
total_number_of_loop: int - total number of files to load (default 1)
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
string
|
|
185
|
+
"""
|
|
186
|
+
time_per_loop = time_it_took_so_far / index_we_are
|
|
187
|
+
total_time_it_will_take = time_per_loop * total_number_of_loop
|
|
188
|
+
time_left = total_time_it_will_take - time_per_loop * index_we_are
|
|
189
|
+
|
|
190
|
+
# convert to nice format h mn and seconds
|
|
191
|
+
m, s = divmod(time_left, 60)
|
|
192
|
+
h, m = divmod(m, 60)
|
|
193
|
+
|
|
194
|
+
if h == 0:
|
|
195
|
+
if m == 0:
|
|
196
|
+
return "%02ds" % (s)
|
|
197
|
+
else:
|
|
198
|
+
return "%02dmn %02ds" % (m, s)
|
|
199
|
+
else:
|
|
200
|
+
return "%dh %02dmn %02ds" % (h, m, s)
|
|
201
|
+
|
|
202
|
+
def load_data(self, data=None, data_type="sample", notebook=False):
|
|
203
|
+
"""Function to save the data already loaded as arrays
|
|
204
|
+
|
|
205
|
+
Paramters:
|
|
206
|
+
data: np array 2D or 3D
|
|
207
|
+
data_type: string - 'sample', 'ob' or 'df' (default 'sample')
|
|
208
|
+
notebook: boolean - turn on this option if you run the library from a
|
|
209
|
+
notebook to have a progress bar displayed showing you the progress of the loading (default False)
|
|
210
|
+
"""
|
|
211
|
+
if len(np.shape(data)) > 2:
|
|
212
|
+
# use tqdm to handle the progress bar
|
|
213
|
+
if notebook:
|
|
214
|
+
for _data in tqdm(data, desc=f"Loading {data_type}", leave=False):
|
|
215
|
+
_data = _data.astype(self.working_data_type)
|
|
216
|
+
self.__load_individual_data(data=_data, data_type=data_type)
|
|
217
|
+
else:
|
|
218
|
+
for _data in data:
|
|
219
|
+
_data = _data.astype(self.working_data_type)
|
|
220
|
+
self.__load_individual_data(data=_data, data_type=data_type)
|
|
221
|
+
|
|
222
|
+
else:
|
|
223
|
+
data = data.astype(self.working_data_type)
|
|
224
|
+
self.__load_individual_data(data=data, data_type=data_type)
|
|
225
|
+
|
|
226
|
+
def __load_individual_data(self, data=None, data_type="sample"):
|
|
227
|
+
"""method that loads the data one at a time
|
|
228
|
+
|
|
229
|
+
Parameters:
|
|
230
|
+
data: np array
|
|
231
|
+
data_type: string - 'data', 'ob' or 'df' (default 'sample')
|
|
232
|
+
"""
|
|
233
|
+
if self.data[data_type]["data"] is None:
|
|
234
|
+
self.data[data_type]["data"] = [data]
|
|
235
|
+
else:
|
|
236
|
+
self.data[data_type]["data"].append(data)
|
|
237
|
+
index = len(self.data[data_type]["data"])
|
|
238
|
+
if self.data[data_type]["file_name"] is None:
|
|
239
|
+
self.data[data_type]["file_name"] = ["image_{:04}".format(index)]
|
|
240
|
+
else:
|
|
241
|
+
self.data[data_type]["file_name"].append("image_{:04}".format(index))
|
|
242
|
+
if self.data[data_type]["metadata"] is None:
|
|
243
|
+
self.data[data_type]["metadata"] = [""]
|
|
244
|
+
else:
|
|
245
|
+
self.data[data_type]["metadata"].append("")
|
|
246
|
+
self.save_or_check_shape(data=data, data_type=data_type)
|
|
247
|
+
|
|
248
|
+
def load_file(
|
|
249
|
+
self,
|
|
250
|
+
file="",
|
|
251
|
+
data_type="sample",
|
|
252
|
+
auto_gamma_filter=True,
|
|
253
|
+
manual_gamma_filter=False,
|
|
254
|
+
manual_gamma_threshold=0.1,
|
|
255
|
+
check_shape=True,
|
|
256
|
+
):
|
|
257
|
+
"""
|
|
258
|
+
Function to read data from the specified path, it can read FITS, TIFF and HDF.
|
|
259
|
+
|
|
260
|
+
Parameters
|
|
261
|
+
file : string - full path of the input file with his extension.
|
|
262
|
+
data_type: string - 'sample', 'df' or 'ob' (default 'sample')
|
|
263
|
+
manual_gamma_filter: boolean - apply or not gamma filtering (default False)
|
|
264
|
+
manual_gamma_threshold: float (between 0 and 1) - manual gamma threshold
|
|
265
|
+
auto_gamma_filter: boolean - flag to turn on or off the auto gamma filering (default True)
|
|
266
|
+
|
|
267
|
+
Raises:
|
|
268
|
+
OSError: if file does not exist
|
|
269
|
+
NotImplementedError: if file is HDF5
|
|
270
|
+
OSError: if any other any file format requested
|
|
271
|
+
|
|
272
|
+
"""
|
|
273
|
+
my_file = Path(file)
|
|
274
|
+
if my_file.is_file():
|
|
275
|
+
metadata = {}
|
|
276
|
+
if file.lower().endswith(".fits"):
|
|
277
|
+
data = np.array(load_fits(my_file))
|
|
278
|
+
elif file.lower().endswith((".tiff", ".tif")):
|
|
279
|
+
[data, metadata] = load_tiff(my_file)
|
|
280
|
+
data = np.array(data)
|
|
281
|
+
elif file.lower().endswith(
|
|
282
|
+
(".hdf", ".h4", ".hdf4", ".he2", "h5", ".hdf5", ".he5")
|
|
283
|
+
):
|
|
284
|
+
raise NotImplementedError
|
|
285
|
+
# data = np.array(load_hdf(my_file))
|
|
286
|
+
else:
|
|
287
|
+
raise OSError(
|
|
288
|
+
"file extension not yet implemented....Do it your own way!"
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
if auto_gamma_filter:
|
|
292
|
+
data = self._auto_gamma_filtering(data=data)
|
|
293
|
+
elif manual_gamma_filter:
|
|
294
|
+
data = self._manual_gamma_filtering(
|
|
295
|
+
data=data, manual_gamma_threshold=manual_gamma_threshold
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
data = np.squeeze(data)
|
|
299
|
+
|
|
300
|
+
if self.data[data_type]["data"] is None:
|
|
301
|
+
self.data[data_type]["data"] = [data]
|
|
302
|
+
else:
|
|
303
|
+
self.data[data_type]["data"].append(data)
|
|
304
|
+
|
|
305
|
+
if self.data[data_type]["metadata"] is None:
|
|
306
|
+
self.data[data_type]["metadata"] = [metadata]
|
|
307
|
+
else:
|
|
308
|
+
self.data[data_type]["metadata"].append(metadata)
|
|
309
|
+
|
|
310
|
+
if self.data[data_type]["file_name"] is None:
|
|
311
|
+
self.data[data_type]["file_name"] = [file]
|
|
312
|
+
else:
|
|
313
|
+
self.data[data_type]["file_name"].append(file)
|
|
314
|
+
|
|
315
|
+
if check_shape:
|
|
316
|
+
self.save_or_check_shape(data=data, data_type=data_type)
|
|
317
|
+
|
|
318
|
+
else:
|
|
319
|
+
raise OSError("The file name does not exist")
|
|
320
|
+
|
|
321
|
+
def _auto_gamma_filtering(self, data=None):
|
|
322
|
+
"""perform the automatic gamma filtering
|
|
323
|
+
|
|
324
|
+
This algorithm check the data format of the input data file (ex: int16, int32...)
|
|
325
|
+
and will determine the maxixum value for this data type. Any pixel that have a value
|
|
326
|
+
above the max value - 5 (just to give it a little bit of range) will be considered as
|
|
327
|
+
being gamma pixels. Those pixels will be replaced by the average value of the 8 pixels
|
|
328
|
+
surrounding this pixel
|
|
329
|
+
|
|
330
|
+
Parameters:
|
|
331
|
+
data: np array
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
np array of the data cleaned
|
|
335
|
+
|
|
336
|
+
Raises:
|
|
337
|
+
ValueError if array is empty
|
|
338
|
+
"""
|
|
339
|
+
if data is None:
|
|
340
|
+
raise ValueError("Data array is empty!")
|
|
341
|
+
|
|
342
|
+
# we may be dealing with a float time, that means it does not need any gamma filtering
|
|
343
|
+
|
|
344
|
+
try:
|
|
345
|
+
data_type = data.dtype
|
|
346
|
+
if data_type in [float, "float32"]:
|
|
347
|
+
max = np.finfo(data_type).max
|
|
348
|
+
else:
|
|
349
|
+
max = np.iinfo(data.dtype).max
|
|
350
|
+
except Exception as error:
|
|
351
|
+
logging.warning(f"Use default max value for data type: {error}")
|
|
352
|
+
return data
|
|
353
|
+
|
|
354
|
+
manual_gamma_threshold = max - 5
|
|
355
|
+
new_data = np.array(data, self.working_data_type)
|
|
356
|
+
|
|
357
|
+
data_gamma_filtered = np.copy(new_data)
|
|
358
|
+
gamma_indexes = np.where(new_data > manual_gamma_threshold)
|
|
359
|
+
|
|
360
|
+
mean_kernel = np.array([[1, 1, 1], [1, 0, 1], [1, 1, 1]]) / 8.0
|
|
361
|
+
convolved_data = convolve(data_gamma_filtered, mean_kernel, mode="constant")
|
|
362
|
+
|
|
363
|
+
data_gamma_filtered[gamma_indexes] = convolved_data[gamma_indexes]
|
|
364
|
+
|
|
365
|
+
return data_gamma_filtered
|
|
366
|
+
|
|
367
|
+
def _manual_gamma_filtering(self, data=None, manual_gamma_threshold=0.1):
|
|
368
|
+
"""perform manual gamma filtering on the data
|
|
369
|
+
|
|
370
|
+
This algoritm uses the manual_gamma_threshold value to estimate if a pixel is a gamma or not.
|
|
371
|
+
1. mean value of data array is calculated
|
|
372
|
+
2. pixel is considered gamma if its value times the manual gamma threshold is bigger than the mean value
|
|
373
|
+
3. if pixel is gamma, its value is replaced by the mean value of the 8 pixels surrounding it.
|
|
374
|
+
|
|
375
|
+
Parameters:
|
|
376
|
+
data: numpy 2D array
|
|
377
|
+
manual_gamma_threshold: float - coefficient between 0 and 1 used to estimate the threshold of the
|
|
378
|
+
gamma pixels (default 0.1)
|
|
379
|
+
|
|
380
|
+
Returns:
|
|
381
|
+
numpy 2D array
|
|
382
|
+
|
|
383
|
+
Raises:
|
|
384
|
+
ValueError if data is empty
|
|
385
|
+
"""
|
|
386
|
+
if data is None:
|
|
387
|
+
raise ValueError("Data array is empty!")
|
|
388
|
+
|
|
389
|
+
data_gamma_filtered = np.copy(data)
|
|
390
|
+
mean_counts = np.mean(data_gamma_filtered)
|
|
391
|
+
gamma_indexes = np.where(
|
|
392
|
+
manual_gamma_threshold * data_gamma_filtered > mean_counts
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
mean_kernel = np.array([[1, 1, 1], [1, 0, 1], [1, 1, 1]]) / 8.0
|
|
396
|
+
convolved_data = convolve(data_gamma_filtered, mean_kernel, mode="constant")
|
|
397
|
+
|
|
398
|
+
data_gamma_filtered[gamma_indexes] = convolved_data[gamma_indexes]
|
|
399
|
+
|
|
400
|
+
return data_gamma_filtered
|
|
401
|
+
|
|
402
|
+
def save_or_check_shape(self, data=None, data_type="sample"):
|
|
403
|
+
"""save the shape for the first data loaded (of each type) otherwise
|
|
404
|
+
check if the size match
|
|
405
|
+
|
|
406
|
+
Parameters:
|
|
407
|
+
data: np array of the data to check or save shape (default [])
|
|
408
|
+
data_type: string - 'ob', 'df' or 'sample' (default 'sample')
|
|
409
|
+
|
|
410
|
+
Raises:
|
|
411
|
+
IOError if size do not match
|
|
412
|
+
"""
|
|
413
|
+
[height, width] = np.shape(data)
|
|
414
|
+
if np.isnan(self.data[data_type]["shape"]["height"]):
|
|
415
|
+
_shape = copy.deepcopy(self.shape)
|
|
416
|
+
_shape["height"] = height
|
|
417
|
+
_shape["width"] = width
|
|
418
|
+
self.data[data_type]["shape"] = _shape
|
|
419
|
+
else:
|
|
420
|
+
_prev_width = self.data[data_type]["shape"]["width"]
|
|
421
|
+
_prev_height = self.data[data_type]["shape"]["height"]
|
|
422
|
+
|
|
423
|
+
if (not (_prev_width == width)) or (not (_prev_height == height)):
|
|
424
|
+
raise IOError(
|
|
425
|
+
"Shape of {} do not match previous loaded data set!".format(
|
|
426
|
+
data_type
|
|
427
|
+
)
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
def normalization(
|
|
431
|
+
self,
|
|
432
|
+
roi=None,
|
|
433
|
+
force=False,
|
|
434
|
+
force_mean_ob=False,
|
|
435
|
+
force_median_ob=False,
|
|
436
|
+
notebook=False,
|
|
437
|
+
use_only_sample=False,
|
|
438
|
+
):
|
|
439
|
+
"""normalization of the data
|
|
440
|
+
|
|
441
|
+
Parameters:
|
|
442
|
+
roi: ROI object or list of ROI objects - object defines the region of the sample and OB that have to match
|
|
443
|
+
in intensity
|
|
444
|
+
force: boolean - True will force the normalization to occur, even if it had been
|
|
445
|
+
run before with the same data set (default False)
|
|
446
|
+
notebook: boolean - turn on this option if you run the library from a
|
|
447
|
+
notebook to have a progress bar displayed showing you the progress of the loading (default False)
|
|
448
|
+
use_only_sample - turn on this option to normalize the sample data using the ROI on the sample. each pixel
|
|
449
|
+
counts will be divided by the average counts of all the ROI of the same image
|
|
450
|
+
|
|
451
|
+
Return:
|
|
452
|
+
True - status of the normalization (True if every went ok, this is mostly used for the unit test)
|
|
453
|
+
|
|
454
|
+
Raises:
|
|
455
|
+
IOError: if no sample loaded
|
|
456
|
+
IOError: if no OB loaded and use_only_sample if False
|
|
457
|
+
IOError: if use_only_sample is True and no ROI provided
|
|
458
|
+
IOError: if size of sample and OB do not match
|
|
459
|
+
|
|
460
|
+
"""
|
|
461
|
+
if not force:
|
|
462
|
+
# does nothing if normalization has already been run
|
|
463
|
+
if self.__exec_process_status["normalization"]:
|
|
464
|
+
return
|
|
465
|
+
self.__exec_process_status["normalization"] = True
|
|
466
|
+
|
|
467
|
+
# make sure we loaded some sample data
|
|
468
|
+
if self.data["sample"]["data"] is None:
|
|
469
|
+
raise IOError("No normalization available as no data have been loaded")
|
|
470
|
+
|
|
471
|
+
# make sure we loaded some ob data
|
|
472
|
+
if not use_only_sample:
|
|
473
|
+
if self.data["ob"]["data"] is None:
|
|
474
|
+
raise IOError("No normalization available as no OB have been loaded")
|
|
475
|
+
|
|
476
|
+
# make sure the data loaded have the same size
|
|
477
|
+
if not self.data_loaded_have_matching_shape():
|
|
478
|
+
raise ValueError("Data loaded do not have the same shape!")
|
|
479
|
+
|
|
480
|
+
if notebook:
|
|
481
|
+
from ipywidgets import widgets
|
|
482
|
+
from IPython.display import display
|
|
483
|
+
|
|
484
|
+
# make sure, if provided, roi has the right type and fits into the images
|
|
485
|
+
b_list_roi = False
|
|
486
|
+
|
|
487
|
+
if not use_only_sample:
|
|
488
|
+
if roi:
|
|
489
|
+
b_list_roi = self.check_roi_format(roi)
|
|
490
|
+
|
|
491
|
+
if b_list_roi:
|
|
492
|
+
_sample_corrected_normalized = self.calculate_corrected_normalized(
|
|
493
|
+
data_type=DataType.sample, roi=roi
|
|
494
|
+
)
|
|
495
|
+
_ob_corrected_normalized = self.calculate_corrected_normalized(
|
|
496
|
+
data_type=DataType.ob, roi=roi
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
else:
|
|
500
|
+
_x0 = roi.x0
|
|
501
|
+
_y0 = roi.y0
|
|
502
|
+
_x1 = roi.x1
|
|
503
|
+
_y1 = roi.y1
|
|
504
|
+
|
|
505
|
+
_sample_corrected_normalized = [
|
|
506
|
+
_sample / np.mean(_sample[_y0 : _y1 + 1, _x0 : _x1 + 1])
|
|
507
|
+
for _sample in self.data["sample"]["data"]
|
|
508
|
+
]
|
|
509
|
+
_ob_corrected_normalized = [
|
|
510
|
+
_ob / np.mean(_ob[_y0 : _y1 + 1, _x0 : _x1 + 1])
|
|
511
|
+
for _ob in self.data["ob"]["data"]
|
|
512
|
+
]
|
|
513
|
+
|
|
514
|
+
else:
|
|
515
|
+
_sample_corrected_normalized = copy.deepcopy(
|
|
516
|
+
self.data["sample"]["data"]
|
|
517
|
+
)
|
|
518
|
+
_ob_corrected_normalized = copy.deepcopy(self.data["ob"]["data"])
|
|
519
|
+
|
|
520
|
+
self.data[DataType.sample]["data"] = _sample_corrected_normalized
|
|
521
|
+
self.data[DataType.ob]["data"] = _ob_corrected_normalized
|
|
522
|
+
|
|
523
|
+
# if the number of sample and ob do not match, use mean or median of obs
|
|
524
|
+
nbr_sample = len(self.data["sample"]["file_name"])
|
|
525
|
+
nbr_ob = len(self.data["ob"]["file_name"])
|
|
526
|
+
if (
|
|
527
|
+
(nbr_sample != nbr_ob) or force_mean_ob or force_median_ob
|
|
528
|
+
): # work with mean ob
|
|
529
|
+
if force_median_ob:
|
|
530
|
+
_ob_corrected_normalized = np.nanmedian(
|
|
531
|
+
_ob_corrected_normalized, axis=0
|
|
532
|
+
)
|
|
533
|
+
elif force_mean_ob:
|
|
534
|
+
_ob_corrected_normalized = np.nanmean(
|
|
535
|
+
_ob_corrected_normalized, axis=0
|
|
536
|
+
)
|
|
537
|
+
else:
|
|
538
|
+
_ob_corrected_normalized = np.nanmedian(
|
|
539
|
+
_ob_corrected_normalized, axis=0
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
self.data["ob"]["data_mean"] = _ob_corrected_normalized
|
|
543
|
+
_working_ob = copy.deepcopy(_ob_corrected_normalized)
|
|
544
|
+
_working_ob[_working_ob == 0] = np.nan
|
|
545
|
+
|
|
546
|
+
if notebook:
|
|
547
|
+
# turn on progress bar
|
|
548
|
+
_message = "Normalization"
|
|
549
|
+
box1 = widgets.HBox(
|
|
550
|
+
[
|
|
551
|
+
widgets.Label(_message, layout=widgets.Layout(width="20%")),
|
|
552
|
+
widgets.IntProgress(max=len(self.data["sample"]["data"])),
|
|
553
|
+
]
|
|
554
|
+
)
|
|
555
|
+
display(box1)
|
|
556
|
+
w1 = box1.children[1]
|
|
557
|
+
|
|
558
|
+
normalized_data = []
|
|
559
|
+
for _index, _sample in enumerate(self.data["sample"]["data"]):
|
|
560
|
+
_norm = np.divide(_sample, _working_ob)
|
|
561
|
+
_norm[np.isnan(_norm)] = 0
|
|
562
|
+
_norm[np.isinf(_norm)] = 0
|
|
563
|
+
normalized_data.append(_norm)
|
|
564
|
+
|
|
565
|
+
if notebook:
|
|
566
|
+
w1.value = _index + 1
|
|
567
|
+
|
|
568
|
+
else: # 1 ob for each sample
|
|
569
|
+
# produce normalized data
|
|
570
|
+
sample_ob = zip(
|
|
571
|
+
self.data[DataType.sample]["data"], self.data[DataType.ob]["data"]
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
if notebook:
|
|
575
|
+
# turn on progress bar
|
|
576
|
+
_message = "Normalization"
|
|
577
|
+
box1 = widgets.HBox(
|
|
578
|
+
[
|
|
579
|
+
widgets.Label(_message, layout=widgets.Layout(width="20%")),
|
|
580
|
+
widgets.IntProgress(max=len(self.data["sample"]["data"])),
|
|
581
|
+
]
|
|
582
|
+
)
|
|
583
|
+
display(box1)
|
|
584
|
+
w1 = box1.children[1]
|
|
585
|
+
|
|
586
|
+
normalized_data = []
|
|
587
|
+
for _index, [_sample, _ob] in enumerate(sample_ob):
|
|
588
|
+
_working_ob = copy.deepcopy(_ob)
|
|
589
|
+
_working_ob[_working_ob == 0] = np.nan
|
|
590
|
+
_norm = np.divide(_sample, _working_ob)
|
|
591
|
+
_norm[np.isnan(_norm)] = 0
|
|
592
|
+
_norm[np.isinf(_norm)] = 0
|
|
593
|
+
normalized_data.append(_norm)
|
|
594
|
+
|
|
595
|
+
if notebook:
|
|
596
|
+
w1.value = _index + 1
|
|
597
|
+
|
|
598
|
+
self.data["normalized"] = normalized_data
|
|
599
|
+
|
|
600
|
+
else: # use_sample_only with ROI
|
|
601
|
+
normalized_data = self.calculate_corrected_normalized_without_ob(roi=roi)
|
|
602
|
+
self.data["normalized"] = normalized_data
|
|
603
|
+
|
|
604
|
+
return True
|
|
605
|
+
|
|
606
|
+
def calculate_corrected_normalized_without_ob(self, roi=None):
|
|
607
|
+
if not roi:
|
|
608
|
+
raise ValueError(
|
|
609
|
+
"You need to provide at least 1 ROI using this use_only_sample mode!"
|
|
610
|
+
)
|
|
611
|
+
|
|
612
|
+
b_list_roi = self.check_roi_format(roi)
|
|
613
|
+
|
|
614
|
+
if b_list_roi:
|
|
615
|
+
normalized_data = []
|
|
616
|
+
for _sample in self.data["sample"]["data"]:
|
|
617
|
+
total_counts_of_rois = 0
|
|
618
|
+
total_number_of_pixels = 0
|
|
619
|
+
for _roi in roi:
|
|
620
|
+
_x0 = _roi.x0
|
|
621
|
+
_y0 = _roi.y0
|
|
622
|
+
_x1 = _roi.x1
|
|
623
|
+
_y1 = _roi.y1
|
|
624
|
+
total_number_of_pixels += (_y1 - _y0 + 1) * (_x1 - _x0 + 1)
|
|
625
|
+
total_counts_of_rois += np.sum(
|
|
626
|
+
_sample[_y0 : _y1 + 1, _x0 : _x1 + 1]
|
|
627
|
+
)
|
|
628
|
+
|
|
629
|
+
full_sample_mean = total_counts_of_rois / total_number_of_pixels
|
|
630
|
+
normalized_data.append(_sample / full_sample_mean)
|
|
631
|
+
|
|
632
|
+
else:
|
|
633
|
+
_x0 = roi.x0
|
|
634
|
+
_y0 = roi.y0
|
|
635
|
+
_x1 = roi.x1
|
|
636
|
+
_y1 = roi.y1
|
|
637
|
+
|
|
638
|
+
normalized_data = [
|
|
639
|
+
_sample / np.mean(_sample[_y0 : _y1 + 1, _x0 : _x1 + 1])
|
|
640
|
+
for _sample in self.data["sample"]["data"]
|
|
641
|
+
]
|
|
642
|
+
|
|
643
|
+
return normalized_data
|
|
644
|
+
|
|
645
|
+
def calculate_corrected_normalized(self, data_type=DataType.sample, roi=None):
|
|
646
|
+
corrected_normalized = []
|
|
647
|
+
for _sample in self.data[data_type]["data"]:
|
|
648
|
+
total_counts_of_rois = 0
|
|
649
|
+
total_number_of_pixels = 0
|
|
650
|
+
for _roi in roi:
|
|
651
|
+
_x0 = _roi.x0
|
|
652
|
+
_y0 = _roi.y0
|
|
653
|
+
_x1 = _roi.x1
|
|
654
|
+
_y1 = _roi.y1
|
|
655
|
+
total_number_of_pixels += (_y1 - _y0 + 1) * (_x1 - _x0 + 1)
|
|
656
|
+
total_counts_of_rois += np.sum(_sample[_y0 : _y1 + 1, _x0 : _x1 + 1])
|
|
657
|
+
|
|
658
|
+
full_sample_mean = total_counts_of_rois / total_number_of_pixels
|
|
659
|
+
corrected_normalized.append(_sample / full_sample_mean)
|
|
660
|
+
return corrected_normalized
|
|
661
|
+
|
|
662
|
+
def check_roi_format(self, roi):
|
|
663
|
+
b_list_roi = False
|
|
664
|
+
if isinstance(roi, list):
|
|
665
|
+
for _roi in roi:
|
|
666
|
+
if not type(_roi) == ROI:
|
|
667
|
+
raise ValueError("roi must be a ROI object!")
|
|
668
|
+
if not self.__roi_fit_into_sample(roi=_roi):
|
|
669
|
+
raise ValueError("roi does not fit into sample image!")
|
|
670
|
+
b_list_roi = True
|
|
671
|
+
|
|
672
|
+
elif not type(roi) == ROI:
|
|
673
|
+
raise ValueError("roi must be a ROI object!")
|
|
674
|
+
else:
|
|
675
|
+
if not self.__roi_fit_into_sample(roi=roi):
|
|
676
|
+
raise ValueError("roi does not fit into sample image!")
|
|
677
|
+
|
|
678
|
+
return b_list_roi
|
|
679
|
+
|
|
680
|
+
def data_loaded_have_matching_shape(self):
|
|
681
|
+
"""check that data loaded have the same shape
|
|
682
|
+
|
|
683
|
+
Returns:
|
|
684
|
+
bool: result of the check
|
|
685
|
+
"""
|
|
686
|
+
_shape_sample = self.data["sample"]["shape"]
|
|
687
|
+
_shape_ob = self.data["ob"]["shape"]
|
|
688
|
+
|
|
689
|
+
if not (_shape_sample == _shape_ob):
|
|
690
|
+
return False
|
|
691
|
+
|
|
692
|
+
_shape_df = self.data["df"]["shape"]
|
|
693
|
+
if not np.isnan(_shape_df["height"]):
|
|
694
|
+
if not (_shape_sample == _shape_df):
|
|
695
|
+
return False
|
|
696
|
+
|
|
697
|
+
return True
|
|
698
|
+
|
|
699
|
+
def __roi_fit_into_sample(self, roi=None):
|
|
700
|
+
"""check if roi is within the dimension of the image
|
|
701
|
+
|
|
702
|
+
Returns:
|
|
703
|
+
bool: True if roi is within the image dimension
|
|
704
|
+
|
|
705
|
+
"""
|
|
706
|
+
[sample_height, sample_width] = np.shape(self.data["sample"]["data"][0])
|
|
707
|
+
|
|
708
|
+
[_x0, _y0, _x1, _y1] = [roi.x0, roi.y0, roi.x1, roi.y1]
|
|
709
|
+
if (_x0 < 0) or (_x1 >= sample_width):
|
|
710
|
+
return False
|
|
711
|
+
|
|
712
|
+
if (_y0 < 0) or (_y1 >= sample_height):
|
|
713
|
+
return False
|
|
714
|
+
|
|
715
|
+
return True
|
|
716
|
+
|
|
717
|
+
def df_correction(self, force=False):
|
|
718
|
+
"""dark field correction of sample and ob
|
|
719
|
+
|
|
720
|
+
Parameters
|
|
721
|
+
force: boolean - that if True will force the df correction to occur, even if it had been
|
|
722
|
+
run before with the same data set (default False)
|
|
723
|
+
|
|
724
|
+
sample_df_corrected = sample - DF
|
|
725
|
+
ob_df_corrected = OB - DF
|
|
726
|
+
|
|
727
|
+
"""
|
|
728
|
+
if not force:
|
|
729
|
+
if self.__exec_process_status["df_correction"]:
|
|
730
|
+
return
|
|
731
|
+
self.__exec_process_status["df_correction"] = True
|
|
732
|
+
|
|
733
|
+
if self.data["sample"]["data"] is not None:
|
|
734
|
+
self.__df_correction(data_type="sample")
|
|
735
|
+
|
|
736
|
+
if self.data["ob"]["data"] is not None:
|
|
737
|
+
self.__df_correction(data_type="ob")
|
|
738
|
+
|
|
739
|
+
def __df_correction(self, data_type="sample"):
|
|
740
|
+
"""dark field correction
|
|
741
|
+
|
|
742
|
+
Parameters:
|
|
743
|
+
data_type: string ['sample','ob]
|
|
744
|
+
|
|
745
|
+
Raises:
|
|
746
|
+
KeyError: if data type is not 'sample' or 'ob'
|
|
747
|
+
IOError: if sample and df or ob and df do not have the same shape
|
|
748
|
+
"""
|
|
749
|
+
if data_type not in ["sample", "ob"]:
|
|
750
|
+
raise KeyError("Wrong data type passed. Must be either 'sample' or 'ob'!")
|
|
751
|
+
|
|
752
|
+
if self.data["df"]["data"] is None:
|
|
753
|
+
return
|
|
754
|
+
|
|
755
|
+
if self.data["df"]["data_average"] is None:
|
|
756
|
+
_df = self.data["df"]["data"]
|
|
757
|
+
if len(_df) > 1:
|
|
758
|
+
_df = average_df(df=_df)
|
|
759
|
+
self.data["df"]["data_average"] = np.squeeze(_df)
|
|
760
|
+
|
|
761
|
+
else:
|
|
762
|
+
_df = np.squeeze(self.data["df"]["data_average"])
|
|
763
|
+
|
|
764
|
+
if np.shape(self.data[data_type]["data"][0]) != np.shape(
|
|
765
|
+
self.data["df"]["data"][0]
|
|
766
|
+
):
|
|
767
|
+
raise IOError("{} and df data must have the same shape!".format(data_type))
|
|
768
|
+
|
|
769
|
+
_data_df_corrected = [_data - _df for _data in self.data[data_type]["data"]]
|
|
770
|
+
_data_df_corrected = [np.squeeze(_data) for _data in _data_df_corrected]
|
|
771
|
+
self.data[data_type]["data"] = _data_df_corrected
|
|
772
|
+
|
|
773
|
+
def crop(self, roi=None, force=False):
|
|
774
|
+
"""Cropping the sample and ob normalized data
|
|
775
|
+
|
|
776
|
+
Parameters:
|
|
777
|
+
roi: ROI object that defines the region to crop
|
|
778
|
+
force: Boolean - that force or not the algorithm to be run more than once
|
|
779
|
+
with the same data set (default False)
|
|
780
|
+
|
|
781
|
+
Returns:
|
|
782
|
+
True (for unit test purpose)
|
|
783
|
+
|
|
784
|
+
Raises:
|
|
785
|
+
ValueError if sample and ob data have not been normalized yet
|
|
786
|
+
"""
|
|
787
|
+
if (self.data["sample"]["data"] is None) or (self.data["ob"]["data"] is None):
|
|
788
|
+
raise IOError("We need sample and ob Data !")
|
|
789
|
+
|
|
790
|
+
if not type(roi) == ROI:
|
|
791
|
+
raise ValueError("roi must be of type ROI")
|
|
792
|
+
|
|
793
|
+
if not force:
|
|
794
|
+
if self.__exec_process_status["crop"]:
|
|
795
|
+
return
|
|
796
|
+
self.__exec_process_status["crop"] = True
|
|
797
|
+
|
|
798
|
+
_x0 = roi.x0
|
|
799
|
+
_y0 = roi.y0
|
|
800
|
+
_x1 = roi.x1
|
|
801
|
+
_y1 = roi.y1
|
|
802
|
+
|
|
803
|
+
new_sample = [
|
|
804
|
+
_data[_y0 : _y1 + 1, _x0 : _x1 + 1] for _data in self.data["sample"]["data"]
|
|
805
|
+
]
|
|
806
|
+
self.data["sample"]["data"] = new_sample
|
|
807
|
+
|
|
808
|
+
new_ob = [
|
|
809
|
+
_data[_y0 : _y1 + 1, _x0 : _x1 + 1] for _data in self.data["ob"]["data"]
|
|
810
|
+
]
|
|
811
|
+
self.data["ob"]["data"] = new_ob
|
|
812
|
+
|
|
813
|
+
if self.data["df"]["data"] is not None:
|
|
814
|
+
new_df = [
|
|
815
|
+
_data[_y0 : _y1 + 1, _x0 : _x1 + 1] for _data in self.data["df"]["data"]
|
|
816
|
+
]
|
|
817
|
+
self.data["df"]["data"] = new_df
|
|
818
|
+
|
|
819
|
+
if self.data["normalized"] is not None:
|
|
820
|
+
new_normalized = [
|
|
821
|
+
_data[_y0 : _y1 + 1, _x0 : _x1 + 1] for _data in self.data["normalized"]
|
|
822
|
+
]
|
|
823
|
+
self.data["normalized"] = new_normalized
|
|
824
|
+
|
|
825
|
+
return True
|
|
826
|
+
|
|
827
|
+
def export(self, folder="./", data_type="normalized", file_type="tif"):
|
|
828
|
+
"""export all the data from the type specified into a folder
|
|
829
|
+
|
|
830
|
+
Parameters:
|
|
831
|
+
folder: String - where to create all the images. Folder must exist otherwise an error is
|
|
832
|
+
raised (default is './')
|
|
833
|
+
data_type: String - Must be one of the following 'sample','ob','df','normalized' (default is 'normalized').
|
|
834
|
+
file_type: String - format in which to export the data. Must be either 'tif' or 'fits' (default is 'tif')
|
|
835
|
+
|
|
836
|
+
Raises:
|
|
837
|
+
IOError if the folder does not exist
|
|
838
|
+
KeyError if data_type can not be found in the list ['normalized','sample','ob','df']
|
|
839
|
+
|
|
840
|
+
"""
|
|
841
|
+
if not os.path.exists(folder):
|
|
842
|
+
raise IOError("Folder '{}' does not exist!".format(folder))
|
|
843
|
+
|
|
844
|
+
if data_type not in ["normalized", "sample", "ob", "df"]:
|
|
845
|
+
raise KeyError("data_type '{}' is wrong".format(data_type))
|
|
846
|
+
|
|
847
|
+
prefix = ""
|
|
848
|
+
if data_type == "normalized":
|
|
849
|
+
data = self.get_normalized_data()
|
|
850
|
+
prefix = "normalized"
|
|
851
|
+
data_type = "sample"
|
|
852
|
+
else:
|
|
853
|
+
data = self.data[data_type]["data"]
|
|
854
|
+
|
|
855
|
+
if data is None:
|
|
856
|
+
return False
|
|
857
|
+
|
|
858
|
+
metadata = self.data[data_type]["metadata"]
|
|
859
|
+
|
|
860
|
+
list_file_name_raw = self.data[data_type]["file_name"]
|
|
861
|
+
self.__create_list_file_names(
|
|
862
|
+
initial_list=list_file_name_raw,
|
|
863
|
+
output_folder=folder,
|
|
864
|
+
prefix=prefix,
|
|
865
|
+
suffix=file_type,
|
|
866
|
+
)
|
|
867
|
+
|
|
868
|
+
self.__export_data(
|
|
869
|
+
data=data,
|
|
870
|
+
metadata=metadata,
|
|
871
|
+
output_file_names=self._export_file_name,
|
|
872
|
+
suffix=file_type,
|
|
873
|
+
)
|
|
874
|
+
|
|
875
|
+
def __export_data(self, data=[], metadata=[], output_file_names=[], suffix="tif"):
|
|
876
|
+
"""save the list of files with the data specified
|
|
877
|
+
|
|
878
|
+
Parameters:
|
|
879
|
+
data: numpy array that contains the array of data to save (default [])
|
|
880
|
+
output_file_names: numpy array of string of full file names (default [])
|
|
881
|
+
suffix: String - format in which the file will be created (default 'tif')
|
|
882
|
+
"""
|
|
883
|
+
name_data_metadata_array = zip(output_file_names, data, metadata)
|
|
884
|
+
for _file_name, _data, _metadata in name_data_metadata_array:
|
|
885
|
+
if suffix in ["tif", "tiff"]:
|
|
886
|
+
make_tif(data=_data, metadata=_metadata, file_name=_file_name)
|
|
887
|
+
elif suffix == "fits":
|
|
888
|
+
make_fits(data=_data, file_name=_file_name)
|
|
889
|
+
|
|
890
|
+
def __create_list_file_names(
|
|
891
|
+
self, initial_list=[], output_folder="", prefix="", suffix=""
|
|
892
|
+
):
|
|
893
|
+
"""create a list of the new file name used to export the images
|
|
894
|
+
|
|
895
|
+
Parameters:
|
|
896
|
+
initial_list: array of full file name
|
|
897
|
+
ex: ['/users/me/image001.tif',/users/me/image002.tif',/users/me/image003.tif']
|
|
898
|
+
output_folder: String (default is ./ as specified by calling function) where we want to create the data
|
|
899
|
+
prefix: String. what to add to the output file name in front of base name
|
|
900
|
+
ex: 'normalized' will produce 'normalized_image001.tif'
|
|
901
|
+
suffix: String. extension to file. 'tif' for TIFF and 'fits' for FITS
|
|
902
|
+
"""
|
|
903
|
+
_base_name = [os.path.basename(_file) for _file in initial_list]
|
|
904
|
+
_raw_name = [os.path.splitext(_file)[0] for _file in _base_name]
|
|
905
|
+
_prefix = ""
|
|
906
|
+
if prefix:
|
|
907
|
+
_prefix = prefix + "_"
|
|
908
|
+
full_file_names = [
|
|
909
|
+
os.path.join(output_folder, _prefix + _file + "." + suffix)
|
|
910
|
+
for _file in _raw_name
|
|
911
|
+
]
|
|
912
|
+
self._export_file_name = full_file_names
|
|
913
|
+
|
|
914
|
+
def get_normalized_data(self):
|
|
915
|
+
"""return the normalized data"""
|
|
916
|
+
return self.data["normalized"]
|
|
917
|
+
|
|
918
|
+
def get_sample_data(self):
|
|
919
|
+
"""return the sample data"""
|
|
920
|
+
return self.data["sample"]["data"]
|
|
921
|
+
|
|
922
|
+
def get_ob_data(self):
|
|
923
|
+
"""return the ob data"""
|
|
924
|
+
return self.data["ob"]["data"]
|
|
925
|
+
|
|
926
|
+
def get_df_data(self):
|
|
927
|
+
"""return the df data"""
|
|
928
|
+
return self.data["df"]["data"]
|
NeuNorm/roi.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ROI(object):
|
|
5
|
+
"""class that list the type of ROI available"""
|
|
6
|
+
|
|
7
|
+
x0 = np.nan
|
|
8
|
+
y0 = np.nan
|
|
9
|
+
x1 = np.nan
|
|
10
|
+
y1 = np.nan
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self, x0=np.nan, y0=np.nan, x1=np.nan, y1=np.nan, width=np.nan, height=np.nan
|
|
14
|
+
):
|
|
15
|
+
if np.isnan(x0) or np.isnan(y0):
|
|
16
|
+
raise ValueError("x0 and y0 must be provided!")
|
|
17
|
+
|
|
18
|
+
self.x0 = x0
|
|
19
|
+
self.y0 = y0
|
|
20
|
+
|
|
21
|
+
if not np.isnan(y1):
|
|
22
|
+
self.y1 = np.max([y0, y1])
|
|
23
|
+
self.y0 = np.min([y0, y1])
|
|
24
|
+
elif not np.isnan(height):
|
|
25
|
+
self.y1 = y0 + height
|
|
26
|
+
else:
|
|
27
|
+
raise ValueError("You must defined either y1 or height!")
|
|
28
|
+
|
|
29
|
+
if not np.isnan(x1):
|
|
30
|
+
self.x1 = np.max([x0, x1])
|
|
31
|
+
self.x0 = np.min([x0, x1])
|
|
32
|
+
elif not np.isnan(width):
|
|
33
|
+
self.x1 = x0 + width
|
|
34
|
+
else:
|
|
35
|
+
raise ValueError("you must defined either x1 or width!")
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2017, Oak Ridge National Laboratory
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
* Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: NeuNorm
|
|
3
|
+
Version: 1.6.12
|
|
4
|
+
Summary: neutron normalization data
|
|
5
|
+
Author-email: Jean Bilheux <bilheuxjm@ornl.gov>
|
|
6
|
+
Maintainer-email: Jean Bilheux <bilheuxjm@ornl.gov>, Chen Zhang <zhangc@ornl.gov>
|
|
7
|
+
License: BSD-3-Clause license
|
|
8
|
+
Project-URL: homepage, https://github.com/neutrons/python_project_template/
|
|
9
|
+
Keywords: neutron,normalization,imaging
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: numpy
|
|
13
|
+
Requires-Dist: pillow
|
|
14
|
+
Requires-Dist: pathlib
|
|
15
|
+
Requires-Dist: astropy
|
|
16
|
+
Requires-Dist: scipy
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
NeuNorm/__init__.py,sha256=4g6jYEZo5zwXqV609fsLeEAtOWWP3I4synH03O2pApA,247
|
|
2
|
+
NeuNorm/_utilities.py,sha256=DE0X6FhIFqfIoA6EQwx_KKNV0gqjAA-OlkpASCG74y4,740
|
|
3
|
+
NeuNorm/_version.py,sha256=1ThuVeMS5KlxvVTyorbbzJi1pftu6GNdFZ7WRJWlGVc,23
|
|
4
|
+
NeuNorm/exporter.py,sha256=AYcGRmeM6wEdxRGhUD0kEZFTsdSzazZR8x4sVP4R2q4,332
|
|
5
|
+
NeuNorm/loader.py,sha256=kyApFIk-D4Hf4B9D7_hRjuO8tptgwKT7THhh4ewqCYc,1143
|
|
6
|
+
NeuNorm/normalization.py,sha256=ib_D5jY04qZb64gUNcsmheAwUU2g3vbiNWLnlOzbfqA,35528
|
|
7
|
+
NeuNorm/roi.py,sha256=lcaTnH8kq5UmoXm7zGJOba7qzsanPj0zPI4rh12xL2M,932
|
|
8
|
+
neunorm-1.6.12.dist-info/LICENSE,sha256=nT1_bF7wnQfD8Obx20W8Umce2Fc03aPyZRG1rL6rgR0,1529
|
|
9
|
+
neunorm-1.6.12.dist-info/METADATA,sha256=Yx5uKuzT6HJb2FsZP3z9hT8feS4T692dVPnPMyMw0Sw,520
|
|
10
|
+
neunorm-1.6.12.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
|
|
11
|
+
neunorm-1.6.12.dist-info/top_level.txt,sha256=zwenKNgm2fVtus0Segjs7dVG4zz9ENAbhRvn8o9bvko,8
|
|
12
|
+
neunorm-1.6.12.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
NeuNorm
|