Coverage for modules/ScanningDiffraction.py: 60%
868 statements
« prev ^ index » next coverage.py v7.0.4, created at 2023-01-10 09:27 -0600
« prev ^ index » next coverage.py v7.0.4, created at 2023-01-10 09:27 -0600
1"""
2Copyright 1999 Illinois Institute of Technology
4Permission is hereby granted, free of charge, to any person obtaining
5a copy of this software and associated documentation files (the
6"Software"), to deal in the Software without restriction, including
7without limitation the rights to use, copy, modify, merge, publish,
8distribute, sublicense, and/or sell copies of the Software, and to
9permit persons to whom the Software is furnished to do so, subject to
10the following conditions:
12The above copyright notice and this permission notice shall be
13included in all copies or substantial portions of the Software.
15THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18IN NO EVENT SHALL ILLINOIS INSTITUTE OF TECHNOLOGY BE LIABLE FOR ANY
19CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23Except as contained in this notice, the name of Illinois Institute
24of Technology shall not be used in advertising or otherwise to promote
25the sale, use or other dealings in this Software without prior written
26authorization from Illinois Institute of Technology.
27"""
29import copy
30import math
31import time
32from os.path import exists
33import collections
34import pickle
35import numpy as np
36import fabio
37import pygpufit.gpufit as gf
38from lmfit import Model, Parameters
39from lmfit.models import GaussianModel
40from scipy.integrate import simps
41from sklearn.metrics import r2_score
42from pyFAI.azimuthalIntegrator import AzimuthalIntegrator
43from musclex import __version__
44try:
45 from ..utils.file_manager import fullPath, createFolder, getBlankImageAndMask, ifHdfReadConvertless
46 from ..utils.histogram_processor import *
47 from ..utils.image_processor import *
48except: # for coverage
49 from utils.file_manager import fullPath, createFolder, getBlankImageAndMask, ifHdfReadConvertless
50 from utils.histogram_processor import *
51 from utils.image_processor import *
53class ScanningDiffraction:
54 """
55 A class for Scanning Diffraction processing - go to process() to see all processing steps
56 """
57 def __init__(self, filepath, filename, file_list=None, extension='', logger = None):
58 if extension in ('.hdf5', '.h5'):
59 index = next((i for i, item in enumerate(file_list[0]) if item == filename), 0)
60 original_image = file_list[1][index]
61 else:
62 original_image = fabio.open(fullPath(filepath, filename)).data
63 original_image = ifHdfReadConvertless(filename, original_image)
64 self.original_image = original_image.astype("float32")
65 if self.original_image.shape == (1043, 981):
66 self.img_type = "PILATUS"
67 else:
68 self.img_type = "NORMAL"
69 self.filepath = filepath
70 self.filename = filename
71 self.logger = logger
72 self.version = __version__
73 self.noBGImg = getImgAfterWhiteTopHat(self.original_image)
74 self.info = self.loadCache()
76 def loadCache(self):
77 """
78 Load the cache and return it if it exists, else return an empty dict.
79 :return: cache info
80 """
81 cache_path = fullPath(self.filepath, "di_cache")
82 cache_file = fullPath(cache_path, self.filename+'.info')
83 if exists(cache_file):
84 info = pickle.load(open(cache_file, "rb"))
85 if info is not None:
86 return info
87 return {}
89 def cacheInfo(self):
90 """
91 Save the cache info in a file named di_cache to help execute the prrgram
92 faster the next time a same image is processed.
93 """
94 cache_path = fullPath(self.filepath, 'di_cache')
95 createFolder(cache_path)
96 cache_file = fullPath(cache_path, self.filename + '.info')
97 self.info['program_version'] = self.version
98 pickle.dump(self.info, open(cache_file, "wb"))
100 def log(self, msg):
101 """
102 print and log msg to file
103 :param msg: logged msg
104 :return:
105 """
106 print(str(msg))
107 if self.logger is not None:
108 self.logger.debug(msg)
110 def process(self, flags={}):
111 """
112 All processing steps
113 """
114 self.updateInfo(flags)
115 self.log("----------------------------------------------------------------------")
116 self.log(fullPath(self.filepath, self.filename)+" is being processed ...")
117 self.findCenter()
118 self.get2DIntegrations()
119 self.processPartialIntegrationMethod()
120 self.processCentralDiffMethod()
121 self.mergeRings()
123 if len(self.info['merged_peaks']) > 0:
124 self.fitModel()
125 self.processRings()
126 else:
127 self.log("WARNING : no effective rings detected.")
128 self.removeInfo('fitResult')
129 self.removeInfo('ring_hists')
130 self.removeInfo('average_ring_model')
131 self.removeInfo('ring_models')
132 self.removeInfo('ring_errors')
134 self.log(fullPath(self.filepath, self.filename) + " has been processed.")
135 self.cacheInfo()
137 def removeInfo(self, k = None):
138 """
139 Remove information from info dictionary by k as a key. If k is None, remove all information in the dictionary
140 :param k: key of dictionary
141 :return: -
142 """
143 if k is None:
144 keys = list(self.info.keys())
145 for key in keys:
146 del self.info[key]
147 else:
148 if k in self.info: # remove from dictionary if the key exists
149 del self.info[k]
151 def updateInfo(self, flags):
152 """
153 Update info dict using flags
154 :param flags: flags
155 :return: -
156 """
157 depn_lists = {
158 '2dintegration': ['fixed_hull', 'merged_peaks'],
159 'ring_hists': ['ROI', 'orientation_model', '90rotation', 'merged_peaks'],
160 'fitResult' : ['m1_rings', 'm2_rings', 'merged_peaks'],
161 'model_peaks' : ['m1_rings', 'm2_rings','merged_peaks'],
162 'm2_runs_dict' : ['m2_rings'],
163 'm2_central_difference' : ['m2_rings'],
164 'merged_rings' : ['m2_rings'],
165 'central_log' : ['m2_rings'],
166 'partial_ranges' : ['m1_rings'],
167 'm1_partial_hist' : ['m1_rings'],
168 'm1_partial_hulls' : ['m1_rings'],
169 'm1_partial_peaks' : ['m1_rings']
170 }
171 for key, params in depn_lists.items():
172 for flag in params:
173 if flag in flags and (flag not in self.info or flags[flag] != self.info[flag]):
174 self.removeInfo(key)
175 break
176 self.info.update(flags)
178 def findCenter(self):
179 """
180 Get center of the diffraction
181 :return:
182 """
183 if 'center' not in self.info.keys():
184 img = get8bitImage(copy.copy(self.original_image))
185 self.original_image, self.info['center'] = processImageForIntCenter(img, getCenter(img), self.img_type)
186 self.log("Center has been calculated. center is "+str(self.info['center']))
188 def get2DIntegrations(self):
189 """
190 Get 2D integrations and azimuthal histrogram
191 :return:
192 """
193 if '2dintegration' not in self.info.keys():
194 blank, mask = getBlankImageAndMask(self.filepath)
195 img = copy.copy(self.original_image)
196 noBGImg = copy.copy(self.noBGImg)
197 if blank is not None:
198 img = img - blank
199 noBGImg = getImgAfterWhiteTopHat(img)
201 center = self.info['center']
203 if img.shape == (1043, 981):
204 det = "pilatus1m" # Sensor used for diffraction_mapping_bone is pilatus1m
205 # rmax = max(img.shape[0] - center[1], img.shape[1] - center[0], center[0], center[1])
206 rmax = max(img.shape[0] / 2, img.shape[1] / 2)
207 min_endpoint = int(round(min(img.shape[0] - center[1], img.shape[1] - center[0], center[0], center[1]) *.75))
208 # img = self.getFilledImage(img)
209 else:
210 det = "agilent_titan" # This detector has the same size (2048,2048)
211 rmax = int(round(min(img.shape[0] - center[1], img.shape[1] - center[0], center[0], center[1]) *.75))
212 min_endpoint = rmax
214 corners = [(0, 0), (img.shape[1], 0), (0, img.shape[0]), (img.shape[1], img.shape[0])]
215 npt_rad = int(round(max([distance(center, c) for c in corners])))
216 ai = AzimuthalIntegrator(detector=det)
217 ai.setFit2D(100, center[0], center[1])
219 I2D, tth, chi = ai.integrate2d(copy.copy(self.original_image), npt_rad, 360, unit="r_mm", method="csr_ocl", mask=mask)
220 I2D2, tth2, chi2 = ai.integrate2d(noBGImg, npt_rad, 360, unit="r_mm", method="csr_ocl", mask=mask)
222 _, I = ai.integrate1d(copy.copy(self.original_image), npt_rad, unit="r_mm", method="csr_ocl", mask=mask)
223 _, I2 = ai.integrate1d(img, npt_rad, unit="r_mm", method="csr_ocl", mask=mask)
225 self.info['2dintegration'] = [I2D, tth, chi]
226 self.info['tophat_2dintegration'] = [I2D2, tth2, chi2]
228 hists = list(I)
230 if 'fixed_hull' in self.info:
231 rmin = self.info['fixed_hull'][0]
232 rmax = self.info['fixed_hull'][1]
233 else:
234 rmin = getFirstVallay(hists)
236 hists = list(I2)
237 self.info['rmax'] = rmax
238 self.info['min_endpoint'] = min_endpoint
239 hull = self.getConvexhull(np.array(hists))
240 self.info['orig_hists'] = hists
241 self.info['start_point'] = rmin
242 self.info['hull_hist'] = hull
243 self.info['area'] = simps(hull)
244 self.info['simple_total_intensity'] = self.roiIntensity(center, rmin, rmax)
245 if 'ROI' not in self.info:
246 self.info['ROI'] = [rmin, rmax]
247 if 'persist_rings' not in self.info:
248 self.removeInfo('m1_rings')
249 self.removeInfo('m2_rings')
250 self.log('2D integration has been calculated.')
252 def roiIntensity(self, center, rmin, rmax):
253 """
254 Simply add up all intensity together within ROI.
255 """
256 img = copy.copy(self.original_image)
257 h, w = img.shape
258 xc, yc = np.meshgrid(range(w), range(h))
259 eucld = ((xc - center[0]) ** 2 + (yc - center[1]) ** 2) ** 0.5
260 return sum(img[(rmin <= eucld) & (eucld < rmax)])
262 def processPartialIntegrationMethod(self): #### Method 1 ####
263 """
264 Process image by 1st mether : Partial Integration method
265 Get radial histograms from each degree range, find peaks from each range, then merge them
266 :return:
267 """
268 if 'm1_rings' not in self.info.keys():
269 self.log("=== Partial Integration Method is being processed...")
270 # Method 1: multiple conical integrations
271 ref_angle = 90
272 if 'partial_angle' in self.info.keys():
273 ref_angle = self.info['partial_angle']
275 ranges, histograms = self.get_partial_integrations(ref_angle)
276 self.info['partial_ranges'] = ranges
277 self.info['m1_partial_hists'] = histograms
279 hulls = []
280 all_peaks = []
281 partial_peaks = []
283 for h in histograms:
284 hull = self.getConvexhull(np.array(h))
285 peaks = self.findPeaksFromHist(hull)
286 all_peaks.extend(peaks)
287 partial_peaks.append(peaks)
288 hulls.append(hull)
290 self.info['m1_partial_hulls'] = hulls
291 self.info['m1_partial_peaks'] = partial_peaks
293 histnp = np.array(self.info['hull_hist'])
294 indexes = self.select_peaks(sorted(all_peaks), times_threshold=2, distance_threshold=15)
296 all_peaks_list = sorted(indexes.keys())
297 peakList1 = sorted([p for p in indexes.keys() if indexes[p] >= len(ranges)/4])
298 peakList2 = sorted([p for p in indexes.keys() if indexes[p] < len(ranges)/4])
300 if len(peakList2) > 0:
301 sigmaList = self.getInitSigma(histnp, all_peaks_list)
302 normSigma = sigmaList / np.max(sigmaList)
303 normHeight = [histnp[p]/ np.max(histnp) for p in all_peaks_list]
304 areas = [normHeight[i] * normSigma[i] for i in range(len(normHeight))]
305 normAreas = areas/np.max(areas)
307 peakList2 = sorted([peakList2[i] for i in range(len(peakList2)) if normAreas[all_peaks_list.index(peakList2[i])] > 0.2])
308 peakList1.extend(peakList2)
310 all_peaks_list = sorted(peakList1)
312 self.log("Selected peaks = " + str(all_peaks_list))
313 self.info['m1_rings'] = all_peaks_list
314 self.removeInfo('merged_peaks')
316 def processCentralDiffMethod(self): #### Method 2 ####
317 """
318 Process image by 2nd method : Central difference Method
319 Get 2D histogram, find the log of central difference, then find the straight line for rings
320 :return:
321 """
322 if 'm2_rings' not in self.info.keys():
323 self.log("=== Central Difference Method is being processed...")
324 I2D = self.info['tophat_2dintegration'][0]
325 h = 10 # Displacement of columns for difference
326 central_difference = self.get_central_difference(I2D, h)
327 central_difference = central_difference.clip(0, central_difference.max()) + 0.1
328 central_log = np.log(central_difference)
329 self.info['central_log'] = central_log
331 dict_runs = self.get_runs_from_image(central_log)
333 selected_rings = self.group_runs_by_ring(dict_runs)
334 selected_rings = {k: v for k, v in selected_rings.items() if v[1] - v[0] >= 5}
335 self.log("Selected peaks = "+ str(sorted(selected_rings.keys())))
337 self.info['m2_runs_dict'] = dict_runs
338 self.info['m2_central_difference'] = central_difference
339 self.info['m2_rings'] = selected_rings
340 self.removeInfo('merged_rings')
342 def mergeRings(self):
343 """
344 Merge all rings together. If multiple rings have similar location, count them as one and use the average location
345 :return:
346 """
347 if 'merged_peaks' not in self.info.keys():
348 self.log("=== Merging rings ...")
349 m1_rings = copy.copy(self.info['m1_rings'])
350 m2_rings = copy.copy(self.info['m2_rings'])
351 all_peaks_list = m1_rings
352 all_peaks_list.extend(m2_rings.keys())
353 all_peaks_list = self.select_peaks(all_peaks_list, distance_threshold = 15)
354 merged_peaks = sorted([p for p in all_peaks_list.keys() if self.info['start_point'] < p and p <= self.info['rmax']])
355 self.log("Merged rings = "+str(merged_peaks))
356 self.info['merged_peaks'] = merged_peaks
357 self.removeInfo('fitResult')
359 def getInitSigma(self, hist, peaks):
360 """
361 Get Initial Sigmas of all peaks by using half-maximum height width
362 :param hist: input histogram
363 :param peaks: list of peaks
364 :return:
365 """
366 sigmaList = []
367 for p in peaks:
368 peak_height = hist[p]
369 r = 1
370 l = 1
371 while p-l > 0 and p+r < len(hist) and (hist[p-l] > peak_height/2. or hist[p+r] > peak_height/2.) :
372 if hist[p-l] > peak_height/2.:
373 l += 1
374 if hist[p+r] > peak_height/2.:
375 r += 1
376 w = r+l
377 sigmaList.append(w*2./2.35482)
379 return sigmaList
381 def fitModel(self):
382 """
383 Fit model to azimuthal integration
384 """
385 if 'fitResult' not in self.info.keys():
386 self.log("=== Fitting model ...")
387 self.removeInfo('ring_hists')
388 self.removeInfo('average_ring_model')
389 self.removeInfo('ring_models')
390 self.removeInfo('ring_errors')
391 merged_peaks = self.info['merged_peaks']
392 hists_np = np.array(self.info['hull_hist'])
393 sigmaList = self.getInitSigma(hists_np, merged_peaks)
395 # Fit several times and take the minimum error result
396 temp_model = []
397 temp_error = []
398 # methods = ['leastsq', 'lbfgsb', 'cg', 'tnc', 'slsqp']
399 methods = ['leastsq']
401 for (i, _) in enumerate(methods):
402 start = time.time()
403 t_m, error = fitGMMv2(hists_np, merged_peaks, sigmaList, methods[i])
404 temp_model.append(t_m)
405 self.log("Fitting Results = " + str(t_m))
406 self.log("Error for method "+ str(methods[i])+ " = "+ str(error))
407 self.log("Time for method "+ str(methods[i])+ " = "+str(time.time() - start))
408 temp_error.append(error)
409 if error < 0.05:
410 break
412 best_ind = np.argmin(temp_error)
413 result = temp_model[best_ind]
414 self.log("Selected Fitting Method = "+str(methods[best_ind]))
416 # Check if peaks are guessed right
417 model_peaks = [result['u' + str(i)] for i in range(1, len(merged_peaks) + 1)]
418 more_info = {'model_peaks': model_peaks, 'fitResult': result, 'minimize_method': methods[best_ind]}
419 self.info.update(more_info)
421 if 'lambda_sdd' in self.info.keys() and 'model_peaks' in self.info.keys() and len(self.info['model_peaks']) > 0:
422 self.info['peak_ds'] = [(1.0 / p) * self.info['lambda_sdd'] for p in self.info['model_peaks']]
424 def processRings(self):
425 """
426 Process rings to get informations of rings i.e. rotation, intensity
427 :return:
428 """
429 if 'ring_hists' not in self.info.keys():
430 self.log("=== Rings information is being processed ...")
431 if 'orientation_model' not in self.info:
432 self.info['orientation_model'] = "GMM3"
433 if self.info['orientation_model'] == "Max Intensity":
434 self.maxIntensityOrientation()
435 elif self.info['orientation_model'] == "GMM2":
436 self.processOrientation2()
437 elif self.info['orientation_model'] == "GMM3":
438 self.processOrientation2(model='GMM3')
439 elif self.info['orientation_model'] == "Herman Factor (Half Pi)":
440 self.HermanOrientation(ir='h')
441 elif self.info['orientation_model'] == "Herman Factor (Pi)":
442 self.HermanOrientation()
444 if 'average_ring_model' in self.info:
445 if '90rotation' in self.info and self.info['90rotation']:
446 self.info['average_ring_model']['u'] += np.pi/2
448 def get_partial_integrations(self, ref_angle):
449 """
450 Get azimuthal integration by angle range
451 :param ref_angle: angle range i.e. (30,60)
452 :return:
453 """
454 histograms = []
455 # Generate the angle ranges in tuples
456 ranges = [(x, x + ref_angle) for x in range(0, 360, ref_angle)]
457 ranges.extend([(x, x + ref_angle) for x in range(int(ref_angle / 2), 360 + int(ref_angle / 2), ref_angle)])
458 ranges = sorted(ranges, key=lambda se: se[0])
460 blank, mask = getBlankImageAndMask(self.filepath)
461 img = copy.copy(self.original_image)
462 if blank is not None:
463 img = img - blank
465 if img.shape == (1043, 981):
466 det = "pilatus1m" # This detector has the size (1043, 981)
467 else:
468 det = "agilent_titan"
470 center = self.info['center']
471 corners = [(0, 0), (img.shape[1], 0), (0, img.shape[0]), (img.shape[1], img.shape[0])]
472 npt_rad = int(round(max([distance(center, c) for c in corners])))
473 ai = AzimuthalIntegrator(detector=det)
474 ai.setFit2D(100, center[0], center[1])
476 # Compute histograms for each range
477 for a_range in ranges:
478 _, I = ai.integrate1d(img, npt_rad, unit="r_mm", method="csr_ocl", azimuth_range=a_range, mask=mask)
479 histograms.append(I)
481 return ranges, histograms
483 def get_central_difference(self, I2D, h):
484 """
485 Compute central difference
486 """
487 central = I2D * 2
488 right_shift = -1 * np.roll(I2D, -h, axis=1)
489 left_shift = -1 * np.roll(I2D, h, axis=1)
490 result_r = np.add(central, right_shift)
491 return np.add(result_r, left_shift)
493 def getConvexhull(self, hist):
494 """
495 Get backgrouns subtracted histogram by applying convex hull
496 :param hist:
497 :param rmax:
498 :return:
499 """
500 if 'fixed_hull' in self.info:
501 start = self.info['fixed_hull'][0]
502 end = self.info['fixed_hull'][1]
503 return convexHull(hist, start_p=start, end_p=end)
504 else:
505 shist = smooth(hist, 30)
506 rmax = self.info['rmax']
507 start = getFirstVallay(list(hist))
508 end = len(hist)
509 for i in range(len(shist)-2, 0, -1):
510 if shist[i+1]-shist[i] > 0:
511 end = int(round(i*0.8))
512 break
513 if end-start < 100:
514 end = self.info['min_endpoint']
515 return convexHull(hist, start_p=start, end_p=min(end, rmax))
517 def get_runs_from_image(self, central_difference):
518 """
519 Get runs for 2nd method
520 :param central_difference:
521 :return:
522 """
523 runs = collections.defaultdict(list)
524 lenght_threshold = 60
525 value_threshold = np.median(central_difference, axis=0)
526 step = 10
527 for i in range(step, len(value_threshold) - step):
528 value_threshold[i] = np.mean(value_threshold[i - step:i + step])
529 value_threshold = [1 if v < 1 else v for v in value_threshold]
530 distance_threshold = 8
532 for c in range(central_difference.shape[1]):
533 run = []
534 last_pos = 0
535 for r in range(central_difference.shape[0]):
536 if central_difference[r][c] > value_threshold[c]:
537 if r - last_pos > distance_threshold:
538 if len(run) > lenght_threshold:
539 line = [run[0], run[len(run) - 1]]
540 runs[c].append(line)
541 run = []
543 last_pos = r
544 run.append((r, c))
545 if len(run) > lenght_threshold:
546 line = [run[0], run[len(run) - 1]]
547 runs[c].append(line)
548 return runs
550 def group_runs_by_ring(self, runs):
551 """
552 Find rings from input runs
553 :param runs:
554 :return:
555 """
556 result_rings = {}
557 distance_threshold = 1
558 i = 0
559 while i < self.original_image.shape[1]:
560 if i in runs:
561 dist = 0
562 start = i
563 end = i
564 for c in range(1, self.original_image.shape[1] - i):
566 if i + c in runs:
567 end = i + c
568 dist = 0
569 else:
570 dist += 1
572 if dist > distance_threshold:
573 result_rings[(end + start) / 2] = (start, end)
574 i += c
575 break
576 i += 1
578 return result_rings
580 def findPeaksFromHist(self, orig_hist, min_dist = 30):
581 """
582 Get all peaks from histogram
583 :param orig_hist: input histogram
584 :param min_dist: if distance between two peaks is less than min_dist
585 :return:
586 """
587 peak_list = getPeaksFromHist(orig_hist, width_thres=10)
588 peak_list = movePeaks(orig_hist, peak_list)
589 peak_list2 = self.select_peaks(peak_list, times_threshold=1, distance_threshold=min_dist)
590 return sorted(peak_list2)
592 def select_peaks(self, peaks, times_threshold = 1, distance_threshold = 10, round_val = True):
593 """
594 Select peaks from list of peaks . Peaks with similar location will be merge into one as average location
595 :param peaks: list of peaks
596 :param times_threshold: peak which its number of appears less than times_threshold will be ignored
597 :param distance_threshold: minimum distance between peaks
598 :return:
599 """
600 aux_results = {}
601 results = {}
603 # Create dictionary for peaks with value equal to number of matches
604 for peak in peaks:
605 aux_results[peak] = aux_results.setdefault(peak, 0) + 1
607 # Group by same peak using distance_threshold
608 l_peaks = sorted(aux_results.keys())
610 prev = 0
611 for (i, _) in enumerate(l_peaks):
612 aux_freq = aux_results[l_peaks[i]]
613 if l_peaks[i] - prev < distance_threshold:
614 freq = 0
615 if prev in results:
616 freq = results[prev]
617 results.pop(prev, None)
619 value = 1.*(prev*freq+l_peaks[i]*aux_freq)/(freq+aux_freq)
620 results[value] = aux_freq + freq
621 prev = value
622 else:
623 results[l_peaks[i]] = aux_freq
624 prev = l_peaks[i]
626 if round_val:
627 return {int(round(k)): v for k, v in results.items() if v >= times_threshold}
628 else:
629 return {k: v for k, v in results.items() if v >= times_threshold}
631 def getDspacing(self, peaks):
632 """
633 Find D-spacing from peaks
634 :param peaks: list
635 :return:
636 """
637 if len(peaks) == 1:
638 return peaks[0]
640 # Number of hypothesis
641 h = 4
642 min_di = 1
643 errors = []
645 for i in range(1, h + 1):
646 d = peaks[0] / i
647 error = 0.
648 for p in peaks:
649 mult = 1. * p / d
650 error += abs((p-d*round(mult)))/d
651 print("distance: " + str(d) + " error: " +str(error))
652 errors.append(error)
653 if error < errors[min_di-1]:
654 min_di = i
656 return 1. * peaks[0] / min_di
658 def get_closer_peak(self, model, num_peaks, p, dist):
659 """
660 Get the peak that close to p by distance
661 :param model: model containing all peaks
662 :param num_peaks: number of peaks
663 :param p: peak location
664 :param dist: minimum distance
665 :return:
666 """
667 for i in range(1, num_peaks + 1):
668 if abs(model['u' + str(i)] - p) < dist:
669 return model['u' + str(i)], model['sigmad' + str(i)]
670 return -1, -1
672 def get_missed_rings_by_distance(self, num_peaks, dist, limit):
673 """
674 Get missed ring by using d-spacing
675 :param num_peaks: number of peaks
676 :param dist: d-spacing
677 :param limit: limit number of rings
678 :return:
679 """
680 distance_threshold = dist / 3
681 peaks = []
682 sigmas = []
683 typ = []
684 sigma_default = 3
685 model_result = self.info['fitResult']
687 # Limit rings to inside image
688 image_lenght = len(self.info['orig_hists']) - self.info['start_point']
690 if limit * dist > image_lenght:
691 limit = int(image_lenght/dist)
693 if limit < 10:
694 limit = 0
695 for limit in range(1, 10):
696 if limit * dist > image_lenght:
697 break
699 limit = min(10 ,limit)
701 for i in range(1, limit):
702 location = dist * i
703 p_found, sigma_found = self.get_closer_peak(model_result, num_peaks, location, distance_threshold)
704 if p_found == -1:
705 # Include non-existing peak
706 peaks.append(location)
707 sigmas.append(sigma_default)
708 typ.append({'non-existing': -1})
709 else:
710 peaks.append(p_found)
711 sigmas.append(sigma_found)
712 typ.append({'existing': 0})
714 return peaks, sigmas, typ
716 def get_ring_angle_range(self, ring_radius, img_center, shape):
717 """
718 Get ring angle range
719 :param ring_radius: radius of ring
720 :param img_center: image center
721 :param shape: image shape
722 :return:
723 """
724 angle_1 = 0
725 angle_2 = 0
726 x = 1
727 y = 1
728 ref_x = min(img_center[0], shape[0] - img_center[0])
729 ref_y = min(img_center[1], shape[1] - img_center[1])
730 if ref_x < img_center[0]:
731 x = -1
732 if ref_y < img_center[1]:
733 y = -1
735 if ring_radius > ref_x:
736 angle_1 = np.arccos(x * ref_x / ring_radius)
737 if ring_radius > ref_y:
738 angle_2 = y * np.arcsin(ref_y / ring_radius)
739 ring_range = abs(convertRadtoDegrees(angle_1) - convertRadtoDegrees(angle_2))
740 return ring_range
742 def removeValleys2(self, deep_hist, orig_hist):
743 """
744 Remove valley from ring hist (pilatus line)
745 :param hists: radial histogram
746 :return:
747 """
748 end_points = []
749 start_points = []
751 deep_hist = copy.copy(deep_hist)
752 orig_hist = copy.copy(orig_hist)
753 # deep_smooth_hist = smooth(deep_hist, 5) + 0.0000000001 # Prevent divide by zero
754 i = 1
756 while i < len(orig_hist)-1:
757 # Find start point of a valley by its slope
758 if deep_hist[i] < 0:
760 # Move left for 2 to start before valley
761 start_ind = max(0, i-2)
763 # Pass valley
764 while i < len(deep_hist)-1 and deep_hist[i] < 0:
765 i = i + 1
767 # Move right for 1 to end after valley
768 end_ind = min(i+2, len(orig_hist)-1)
769 i = end_ind
771 if start_ind == 0 or end_ind - start_ind < 2:
772 sub = orig_hist[end_ind]
773 elif end_ind == len(orig_hist)-1:
774 sub = orig_hist[start_ind]
775 else:
776 xs = np.linspace(0, end_ind - start_ind + 1, end_ind - start_ind + 1)
777 val_s = orig_hist[start_ind]
778 val_e = orig_hist[end_ind]
779 m = (val_e-val_s)/len(xs)
780 sub = np.array([m*x+val_s for x in xs])
782 orig_hist[start_ind: end_ind+1] = sub
783 start_points.append(start_ind)
784 end_points.append(end_ind)
786 i = i + 1
788 return orig_hist
791 def removeValleys(self, hists):
792 """
793 Remove valley from ring hist (pilatus line)
794 :param hists: radial histogram
795 :return:
796 """
797 hist2 = copy.copy(hists)
798 smooth_hist = smooth(hist2)
799 thres = np.mean(hists) * 0.8
800 i = 1
802 while i < len(hist2):
803 if hist2[i] < thres:
804 start_ind = i - 1
805 while i < len(hist2) and hist2[i] < thres:
806 i = i + 1
808 while i < len(hist2) and ((1.*(hist2[start_ind] - hist2[i])/hist2[start_ind] > 0.1) or smooth_hist[i] - smooth_hist[i - 1] > 20. ):
809 i = i + 1
811 start_ind = max(0, start_ind - 3)
812 end_ind = min(i, len(hist2) - 1)
814 if start_ind == 0:
815 sub = hist2[end_ind]
816 elif end_ind >= len(hist2) - 1:
817 sub = hist2[start_ind]
818 i = end_ind
819 else:
820 xs = np.linspace(0, end_ind - start_ind + 1, end_ind - start_ind + 1)
821 val_s = smooth_hist[start_ind]
822 val_e = smooth_hist[end_ind]
823 m = (val_e-val_s)/len(xs)
824 sub = np.array([m*x+val_s for x in xs])
825 hist2[start_ind: end_ind+1] = sub
826 i = i + 1
828 return hist2
830 def process_rings(self):
831 """
832 Process all rings
833 :return:
834 """
835 peaks = self.info['model_peaks']
836 result = []
838 center = self.info['center']
839 l = int(min(self.original_image.shape[0] - center[1], self.original_image.shape[1] - center[0], center[0], center[1]))
840 I2D = self.info['2dintegration'][0]
842 histogram1D = self.info['hull_hist']
843 rings = []
844 ring_peaks = []
845 ring_hists = []
847 # Check for non-existing peaks and include them
848 d = self.getDspacing(peaks)
849 self.log("D-spacing = " + str(d))
850 self.info['distance'] = d
851 limit = int(np.round(max(peaks)/d * 1.5))
852 rads, sigmas, type_rings = self.get_missed_rings_by_distance(len(peaks), d, limit)
854 for (i, _) in enumerate(rads):
855 h = 1
856 rad_min = int(np.around(rads[i] - h * sigmas[i]))
857 rad_max = int(np.around(rads[i] + h * sigmas[i]))
859 if rad_max < rad_min:
860 rad_max, rad_min = rad_min, rad_max
861 rad_max = min(rad_max, len(histogram1D))
862 rad_min = max(rad_min, 0)
864 # Added concern of partial rings -> divide by number of degrees
865 if rads[i] > l:
866 degrees = self.get_ring_angle_range(rads[i], center, self.original_image.shape)
867 else:
868 degrees = 360
869 # Compute the area under each peak
870 rings.append(simps(histogram1D[rad_min:rad_max]) / degrees)
872 # Add type of ring and angle range if not complete
873 if type_rings[i] != -1:
874 type_rings[i] = degrees
876 # Compute the azimuthal integration for each ring from 2D integration
877 hists = [np.sum(I2D[j, rad_min:rad_max], axis=0) for j in range(360)]
878 hists = np.array(hists)
879 hists2 = copy.copy(hists)
881 # Filled pilatus valleys
882 if self.original_image.shape == (1043, 981):
883 hists2 = self.removeValleys(hists2)
885 hists = hists - min(hists2)
886 hists[hists < 0] = 0
887 hists2 = hists2-min(hists2)
888 result.append((np.arange(0, 2 * np.pi, 2 * np.pi / 360), np.array(hists2)))
890 # Compute peak of each ring
891 ring_peaks.append(max(hists2))
892 ring_hists.append(hists)
895 self.info['rings_area'] = rings
896 self.info['rings_peaks'] = ring_peaks
897 self.info['radius'] = rads
898 self.info['sigmas'] = sigmas
899 self.info['type_rings'] = type_rings
900 self.info['ring_hists'] = ring_hists
901 return result
903 def find_nearest(self, array, value):
904 """
905 Find index of array which has the nearest value
906 """
907 idx = (np.abs(array - value)).argmin()
908 return idx
910 def get_ring_model(self, hist):
911 """
912 Fit gaussian model to rings
913 :param hist:
914 :return:
915 """
916 # Smooth histogram to find parameters easier
917 # hist = (hist[0], np.convolve(hist[1], [0.5, 1, 0.5])[1:361] / 2)
918 ring_hist = smooth(hist[1], 20)
919 hist = (hist[0], ring_hist)
921 index = np.argmax(hist[1])
922 u1 = hist[0][index]
923 u2 = (u1 + np.pi)%(2*np.pi)
924 d = 180
926 # If peak is not in first half make a round sum
927 min_x = index - d
928 max_x = index + d
929 if min_x > 0 and max_x < 360:
930 alpha1 = hist[1][range(min_x, max_x)].sum() * 2 * np.pi / len(hist[1])
931 else :
932 if min_x < 0:
933 min_x += 360
934 if max_x >= 360:
935 max_x -= 360
936 alpha1 = hist[1][range(0, max_x)].sum() * 2 * np.pi / len(hist[1])
937 alpha1 += hist[1][range(min_x, 360)].sum() * 2 * np.pi / len(hist[1])
939 sigma1 = alpha1 / hist[1][index] / np.sqrt(2 * np.pi)
940 sigma1 = sigma1 if not math.isnan(sigma1) else 0.5
941 # Fit model using same gaussian
942 x = hist[0]
944 # TODO: Use the model with constrains as for fitGMMv2
945 # Call orientation_GMM2
946 model = Model(orientation_GMM2, independent_vars='x')
947 max_height = np.max(hist[1])
949 init_u = min(u1, u2)
950 params = Parameters()
951 params.add("u", init_u, min=0, max=np.pi)
952 params.add("sigma", sigma1, min=0, max=np.pi*2)
953 params.add("alpha", alpha1, min=0, max=alpha1*5+0.0000001)
954 params.add("bg", 0, min = -1, max = max_height+1)
956 result = model.fit(hist[1], x=x, params = params, nan_policy='propagate')
958 # Compute valley point and circular shift
959 v_value = result.values['u'] + np.pi / 2
960 v_point = self.find_nearest(hist[0], v_value)
962 hist_shifted = np.roll(hist[1], -v_point)
964 # Fit model again with shifted histogram
965 # initial guess
966 sigma = result.values['sigma']
967 alpha = result.values['alpha']
968 bg = result.values['bg']
969 u1 = np.pi / 2.
971 params = Parameters()
972 params.add("u", u1, min=0, max=np.pi)
973 params.add("sigma", sigma, min=0, max=np.pi*2)
974 params.add("alpha", alpha, min=0, max=alpha*5+0.0000001)
975 params.add("bg", bg, min = -1, max = max_height)
977 model = Model(orientation_GMM2, independent_vars='x')
978 result = model.fit(hist_shifted, x=x, params=params, nan_policy='propagate')
979 result = result.values
981 # Correction over shifted peaks
982 result['u'] = result['u'] + v_value - np.pi
984 return result
986 def get_ring_model2(self, hist):
987 """
988 Fit gaussian model to rings
989 :param hist:
990 :return:
991 """
992 # Smooth histogram to find parameters easier
993 hist[1] = smooth(hist[1], 20)
995 # n_hist = len(hist[1])
996 index = np.argmax(hist[1])
997 u = hist[0][index]
998 if u < np.pi / 2:
999 u += np.pi
1000 elif u > 3 * np.pi / 2:
1001 u -= np.pi
1003 # Fit model using same gaussian
1004 x = hist[0]
1006 # Call orientation_GMM3
1007 model = Model(orientation_GMM3, independent_vars='x')
1008 max_height = np.max(hist[1])
1010 model.set_param_hint('u', value=u, min=np.pi/2, max=3*np.pi/2)
1011 model.set_param_hint('sigma', value=0.1, min=0, max=np.pi*2)
1012 model.set_param_hint('alpha', value=max_height*0.1/0.3989423, min=0)
1013 model.set_param_hint('bg', value=0, min=-1, max=max_height+1)
1015 result = model.fit(data=hist[1], x=x, params=model.make_params(), nan_policy='propagate')
1016 errs = abs(result.best_fit - result.data)
1017 if errs.mean() != 0:
1018 weights = errs / errs.mean() + 1
1019 else:
1020 weights=np.ones_like(errs)
1021 weights[weights > 3.] = 0
1022 result = model.fit(data=hist[1], x=x, params=result.params, weights=weights, nan_policy='propagate')
1024 return result.values
1026 def getRingHistograms(self):
1027 """
1028 Give the histogram of the different rings on the image.
1029 """
1030 histograms = []
1031 deep_valleys_hists = []
1033 # Filter of rings taken into account (complete)
1034 rings, idxs = [], []
1035 for i, p in enumerate(self.info['model_peaks']):
1036 if self.info['ROI'][0] <= p <= self.info['ROI'][1]:
1037 rings.append(int(round(p)))
1038 idxs.append(i)
1039 I2D = copy.copy(self.info['2dintegration'][0])
1040 I2D2 = copy.copy(I2D)
1041 if self.original_image.shape == (1043, 981):
1042 # if the image is from pilatus, replace gaps with large minus value to make deep valley
1043 I2D2[I2D2 <= 0] = -99999
1045 sigmas = [int(round(s)) for s in self.getInitSigma(self.info['hull_hist'], rings)]
1046 self.log("Peaks to check = "+ str(rings))
1047 for (i, _) in enumerate(rings):
1048 histograms.append(np.sum(I2D[:, rings[i]-sigmas[i]:rings[i]+sigmas[i]], axis=1))
1049 deep_valleys_hists.append(np.sum(I2D2[:, rings[i]-sigmas[i]:rings[i]+sigmas[i]], axis=1))
1051 if len(histograms) < 1:
1052 return [], [], []
1054 ring_hists, revised_hists = [], []
1055 for (i, _) in enumerate(histograms):
1057 # remove valleys and remove linear background
1058 hist = copy.copy(histograms[i])
1059 if self.original_image.shape == (1043, 981):
1060 deep_valleys_hist = deep_valleys_hists[i]
1061 hist = self.removeValleys2(deep_valleys_hist, hist)
1062 min_val = hist.min()
1063 hist = hist - min_val
1065 # Collect real histogram for displaying
1066 real_hist = np.array(histograms[i])
1067 real_hist -= min_val
1068 real_hist[real_hist < 0] = 0
1069 ring_hists.append(real_hist)
1070 revised_hists.append(hist)
1072 return ring_hists, revised_hists, idxs
1074 def processOrientation2(self, model='GMM2'):
1075 """
1076 Calculate ring orientation - get ring_hists, 'ring_models', 'ring_errors', 'average_ring_model'
1077 :return:
1078 """
1079 ring_hists, revised_hists, idx_dict = self.getRingHistograms()
1080 # Obtaining gaussian model of each ring histogram, means of gaussians and error over histogram
1081 model_dict = {}
1082 errors_dict = {}
1084 x = np.arange(0, 2 * np.pi, 2 * np.pi / 360)
1086 for i, hist in zip(idx_dict, revised_hists):
1088 # Fit orientation model
1089 if model == 'GMM2':
1090 model_dict[i] = self.get_ring_model([x, hist])
1091 errors_dict[i] = 1 - r2_score(orientation_GMM2(x=x, **model_dict[i]), hist)
1092 elif model == 'GMM3':
1093 model_dict[i] = self.get_ring_model2([x, hist])
1094 errors_dict[i] = 1 - r2_score(orientation_GMM3(x=x, **model_dict[i]), hist)
1096 self.info['ring_hists'] = ring_hists
1097 self.info['ring_models'] = model_dict
1098 self.info['ring_errors'] = errors_dict
1100 # Find avarage ranges ( ignore outliers )
1101 all_u1s = sorted([model_dict[i]['u'] for i in model_dict
1102 if errors_dict[i] < 1. and model_dict[i]['sigma'] < 1])
1103 u1_dict = self.select_peaks(all_u1s, times_threshold=1, distance_threshold=0.1, round_val=False)
1105 # Find an average ring model ( ignore models which produce high error )
1106 if len(u1_dict) > 0:
1107 best_angle = max(u1_dict.keys(), key=lambda a:u1_dict[a])
1108 average_result = {
1109 'u' : best_angle
1110 }
1111 sum_sigma = 0
1112 sum_alpha = 0
1113 nModels = 0
1114 for i in model_dict:
1115 if errors_dict[i] < 1. and abs(model_dict[i]['u']-best_angle) < 0.1:
1116 sum_sigma += model_dict[i]['sigma']
1117 sum_alpha += model_dict[i]['alpha']
1118 nModels += 1
1119 if nModels > 0:
1120 average_result['sigma'] = 1. * sum_sigma / nModels
1121 average_result['alpha'] = 1. * sum_alpha / nModels
1122 average_result['bg'] = 0
1123 self.info['average_ring_model'] = average_result
1124 self.log("Average Ring Model : "+ str(average_result))
1125 else:
1126 self.log("No average model detected")
1128 def HoF(self, hist, mode='f'):
1129 """
1130 Calculate Herman Orientation Factors
1131 """
1132 Ints = []
1133 n_pi = len(hist) // 2 # number of hist unit in pi range
1134 n_hpi = n_pi // 2 # number of hist unit in half pi range
1135 for i in range(n_pi):
1136 I = hist[i:(i+n_pi)].copy()
1137 I[:i] += np.flipud(hist[:i])
1138 I[i:] += np.flipud(hist[(i+n_pi):])
1139 Ints.append(I)
1140 rads = np.linspace(0, np.pi, n_pi + 1)[:-1]
1141 denom = np.sin(rads)
1142 numer = (np.cos(rads)**2) * denom
1144 HoFs = np.zeros(hist.shape)
1145 for i in range(len(hist)):
1146 I = Ints[i] if i < n_pi else np.flipud(Ints[i - n_pi])
1147 if mode == 'f':
1148 HoFs[i] = ((I * numer).sum() / (I * denom).sum()) if i < n_pi else HoFs[i - n_pi]
1149 else:
1150 HoFs[i] = (I[:n_hpi] * numer[:n_hpi]).sum() / (I[:n_hpi] * denom[:n_hpi]).sum()
1151 return (3 * HoFs - 1) / 2
1153 def getRadOfMaxHoF(self, HoFs, mode, ratio=0.05):
1154 """
1155 Get the radian of the maximum Herman Orientation Factor
1156 :param HoFs:
1157 :param mode:
1158 """
1159 nHoFs = len(HoFs)
1160 num = int(nHoFs * ratio)
1161 if mode == 'f':
1162 HoFs = HoFs[:(nHoFs // 2)]
1163 num //= 2
1164 # get the indices of the top num largest HoFs
1165 idxs = sorted(np.arange(len(HoFs)), key=lambda i: HoFs[i])[-num:]
1166 idxs = sorted(idxs)
1167 # group the indices
1168 grps = [[idxs[0]]]
1169 for idx in idxs[1:]:
1170 if grps[-1][-1] == idx - 1:
1171 grps[-1].append(idx)
1172 else:
1173 grps.append([idx])
1174 # handle the round case
1175 if len(grps) > 1 and grps[0][0] == 0 and grps[-1][-1] == len(HoFs) - 1:
1176 grps[0] += [idx - len(HoFs) for idx in grps[-1]]
1177 # find the groups of max number of indices
1178 maxn = max(len(grp) for grp in grps)
1179 grps = [grp for grp in grps if len(grp) == maxn]
1180 opt_grp = sorted(grps, key=lambda g:HoFs[g].sum())[-1]
1181 opt_idx = np.mean(opt_grp) % len(HoFs)
1182 return 2 * np.pi * opt_idx / nHoFs
1184 def HermanOrientation(self, ir='f', thres=0.1):
1185 """
1186 Calculate Herman factors - get ring_hists,
1187 :param ir: integration radian, half Pi or Pi
1188 :return:
1189 """
1190 ROI = self.info['ROI']
1191 roi_hist = np.sum(self.info['2dintegration'][0][:, ROI[0]:ROI[1]], axis=1)
1192 roi_hist -= roi_hist.min()
1193 ring_hists, revised_hists, idx_dict = self.getRingHistograms()
1195 model_dict, errors_dict = {}, {}
1196 for i, hist in zip(idx_dict, revised_hists):
1198 # Calculate herman factors
1199 HoFs = self.HoF(hist, ir)
1200 u = self.getRadOfMaxHoF(HoFs, ir)
1201 model_dict[i] = {'u': u, 'HoFs': HoFs, 'sigma': 0, 'alpha': 0, 'bg': 0}
1202 errors_dict[i] = 1 + (HoFs.max() - thres) / (thres - 1)
1204 self.info['ring_hists'] = ring_hists
1205 self.info['ring_models'] = model_dict
1206 self.info['ring_errors'] = errors_dict
1208 roi_result = {'sigma': 0, 'alpha': 0, 'bg': 0}
1209 roiHoFs = self.HoF(roi_hist, ir)
1210 roi_result['u'] = self.getRadOfMaxHoF(roiHoFs, ir)
1211 roi_result['HoFs'] = roiHoFs
1212 self.info['average_ring_model'] = roi_result
1213 self.log("Herman orientation reuslt : "+ str(roi_result['u']))
1215 def maxIntensityOrientation(self):
1216 """
1217 Calculate the max intensity orientation.
1218 :return: -
1219 """
1220 ROI = self.info['ROI']
1221 roi_hist = np.sum(self.info['2dintegration'][0][:, ROI[0]:ROI[1]], axis=1)
1222 ring_hists, revised_hists, idx_dict = self.getRingHistograms()
1224 model_dict, errors_dict = {}, {}
1225 for i, hist in zip(idx_dict, revised_hists):
1227 # Calculate herman factors
1228 u = max(np.arange(180), key=lambda d: np.sum(hist[d:d + 1]) + np.sum(hist[d + 180:d + 181])) * np.pi / 180
1229 model_dict[i] = {'u': u, 'sigma': 0, 'alpha': 0, 'bg': 0}
1230 errors_dict[i] = 0
1232 self.info['ring_hists'] = ring_hists
1233 self.info['ring_models'] = model_dict
1234 self.info['ring_errors'] = errors_dict
1236 roi_result = {'sigma': 0, 'alpha': 0, 'bg': 0, 'hist': roi_hist}
1237 roi_result['u'] = max(np.arange(180), key=lambda d: np.sum(roi_hist[d:d + 1]) + \
1238 np.sum(roi_hist[d + 180:d + 181])) * np.pi / 180
1239 self.info['average_ring_model'] = roi_result
1240 self.log("Max intensity orientation : "+ str(roi_result['u']))
1242############################# Batch mode Results #############################
1244def toFloat(value):
1245 """
1246 Convert to float.
1247 :param value: the value that needs to be converted
1248 :return: x
1249 """
1250 try:
1251 x = float(value)
1252 except Exception:
1253 x = 0
1254 return x
1256def convertRadtoDegrees(rad):
1257 """
1258 Convert radian to degrees.
1259 :param rad: the value that needs to be converted
1260 :return: deg value
1261 """
1262 rad = toFloat(rad)
1263 return int(rad * 180 / np.pi)
1265############################# Gaussian Models #############################
1267def GM(x, u1, sigmad1, alpha1):
1268 """
1269 Gaussian model
1270 """
1271 return alpha1 * np.exp(-1. * (x - u1) ** 2 / (2 * sigmad1 ** 2)) * (1. / (np.sqrt(2 * np.pi) * sigmad1))
1273def GMM_any(x, params):
1274 """
1275 Gaussian model with params
1276 """
1277 n = int(len(params.keys())/3)
1278 result = GM(x, params['u1'], params['sigmad1'], params['alpha1'])
1279 for i in range(2, n+1):
1280 result += GM(x, params['u'+str(i)], params['sigmad'+str(i)], params['alpha'+str(i)])
1281 return result
1283def orientation_GMM2(x, u, sigma, alpha, bg):
1284 """
1285 Orientation Gaussian model
1286 """
1287 mod = GaussianModel()
1288 return mod.eval(x=x, amplitude=alpha, center=u, sigma=sigma) + mod.eval(x=x, amplitude=alpha, center=u+np.pi, sigma=sigma) + bg
1290def orientation_GMM2_v1(x, u1, u2, sigma, alpha):
1291 """
1292 Orientation Gaussian model
1293 """
1294 mod = GaussianModel()
1295 return mod.eval(x=x, amplitude=alpha, center=u1, sigma=sigma) + mod.eval(x=x, amplitude=alpha, center=u2, sigma=sigma)
1297def orientation_GMM3(x, u, sigma, alpha, bg):
1298 """
1299 Orientation Gaussian model
1300 """
1301 mod = GaussianModel()
1302 return mod.eval(x=x, amplitude=alpha, center=u, sigma=sigma) + \
1303 mod.eval(x=x, amplitude=alpha, center=u-np.pi, sigma=sigma) + \
1304 mod.eval(x=x, amplitude=alpha, center=u+np.pi, sigma=sigma) + bg
1306def fitGMMv2(hists_np, indexes, widthList, method='leastsq'):
1307 """
1308 Fit Gaussian model using lmfit on CPU
1309 """
1310 parameters = []
1311 indexes = copy.copy(indexes)
1312 for index, width in zip(indexes, widthList):
1313 sigmad = 1. / np.sqrt(8 * np.log(2)) * width
1314 alpha = sigmad * hists_np[index] * np.sqrt(2 * np.pi)
1315 parameters.append((sigmad, alpha))
1317 x = np.array(range(len(hists_np)))
1318 pars = Parameters()
1319 gaussians = []
1320 mean_margin = 15
1321 for (i, e) in enumerate(indexes):
1322 prefix = 'g' + str(i + 1) + '_'
1323 gauss = GaussianModel(prefix=prefix)
1324 pars.update(gauss.make_params())
1325 pars[prefix + 'center'].set(e, min=e - mean_margin, max=e + mean_margin)
1326 pars[prefix + 'sigma'].set(parameters[i][0], min=1., max=1000.)
1327 pars[prefix + 'amplitude'].set(parameters[i][1], min=1, max=1000 * hists_np[e])
1328 gaussians.append(gauss)
1330 model = gaussians[0]
1331 for i in range(1, len(gaussians)):
1332 model += gaussians[i]
1333 out = model.fit(hists_np, pars, x=x, method=method, nan_policy='propagate').values
1334 result = {}
1335 for i in range(len(indexes)):
1336 prefix = 'g' + str(i + 1) + '_'
1337 result['u' + str(i + 1)] = out[prefix + 'center']
1338 result['sigmad' + str(i + 1)] = out[prefix + 'sigma']
1339 result['alpha' + str(i + 1)] = out[prefix + 'amplitude']
1341 error = r2_score(GMM_any(x = x, params = result), hists_np)
1342 return result, error
1344def fitGMMv2_gpu(hists_np, indexes, widthList, method='leastsq'):
1345 """
1346 Fit Gaussian model using gpufit on GPU
1347 """
1348 parameters = []
1349 indexes = copy.copy(indexes)
1350 for index, width in zip(indexes, widthList):
1351 sigmad = 1. / np.sqrt(8 * np.log(2)) * width
1352 alpha = sigmad * hists_np[index] * np.sqrt(2 * np.pi)
1353 parameters.append((sigmad, alpha))
1355 pars = np.tile((0,0,0), (len(indexes), 1)).astype(np.float32)
1356 constraints = np.zeros((len(indexes), 2*3), dtype=np.float32)
1357 constraint_types = np.array([gf.ConstraintType.LOWER_UPPER, gf.ConstraintType.LOWER_UPPER, gf.ConstraintType.LOWER_UPPER], dtype=np.int32)
1358 mean_margin = 15
1359 for (i, e) in enumerate(indexes):
1360 pars[i, 0] = e #center
1361 constraints[i, 0] = e - mean_margin
1362 constraints[i, 1] = e + mean_margin
1363 pars[i, 1] = parameters[i][0] #sigma
1364 constraints[i, 2] = 1.
1365 constraints[i, 3] = 1000.
1366 pars[i, 2] = parameters[i][1] #amplitude
1367 constraints[i, 4] = 1
1368 constraints[i, 5] = 1000 * hists_np[e]
1370 if method == 'leastsq':
1371 gfmethod = gf.EstimatorID.LSE
1372 else:
1373 gfmethod = gf.EstimatorID.MLE
1374 x = np.array(range(len(hists_np)))
1375 hists_np_2d = np.tile(hists_np.astype(np.float32), (len(indexes), 1))
1376 pars, states, chi_squares, number_iterations, execution_time = gf.fit_constrained(hists_np_2d, None, gf.ModelID.GAUSS_2D, pars,
1377 constraints, constraint_types, None, 10000, None, gfmethod, None)
1378 print(pars, states, chi_squares, number_iterations, execution_time)
1379 result = {}
1380 for (i, _) in enumerate(indexes):
1381 result['u' + str(i + 1)] = pars[i, 0]
1382 result['sigmad' + str(i + 1)] = pars[i, 1]
1383 result['alpha' + str(i + 1)] = pars[i, 2]
1385 error = r2_score(GMM_any(x = x, params = result), hists_np)
1386 return result, error