Coverage for modules/DiffractionCentroids.py: 11%
436 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"""
29from os import makedirs
30from os.path import isfile, exists
31import pickle
32import matplotlib.pyplot as plt
33from pyFAI.azimuthalIntegrator import AzimuthalIntegrator
34from lmfit import Parameters
35from lmfit.models import VoigtModel
36import fabio
37from musclex import __version__
38try:
39 from ..utils.file_manager import fullPath, ifHdfReadConvertless
40 from ..utils.image_processor import *
41 from ..utils.histogram_processor import *
42except: # for coverage
43 from utils.file_manager import fullPath, ifHdfReadConvertless
44 from utils.image_processor import *
45 from utils.histogram_processor import *
47class DiffractionCentroids:
48 """
49 A class for Diffraction Centroids processing - go to process() to see all processing steps
50 """
51 def __init__(self, dir_path, imgList, grp_number, fixRanges, off_mer):
52 """
53 Initial value for DiffractionCentroids object
54 :param dir_path: directory path of input images (str)
55 :param imgList: all images in a group (list)
56 :param grp_number: image group number - used for writing csv (int)
57 :param fixRanges: fixed peak ranges configured my users
58 :param off_mer: configuration of off-meridian peaks configured my users
59 """
60 self.avgImg = self.mergeImages(dir_path, imgList) # average all image in a group
61 if self.avgImg.shape == (1043, 981):
62 self.img_type = "PILATUS"
63 else:
64 self.img_type = "NORMAL"
65 self.mask_thres = getMaskThreshold(self.avgImg, self.img_type)
66 self.dir_path = dir_path
67 self.version = __version__
68 self.info = {
69 "reject" : {"top":[], "bottom": [] }
70 }
71 self.cachefile, cinfo = self.loadCache(dir_path, imgList)
72 self.info.update(cinfo)
73 self.info["filelist"] = imgList
74 self.info["grp_num"] = grp_number+1
75 self.fixRanges = fixRanges
76 self.init_off_mer = off_mer
77 self.rotMat = None # store the rotation matrix used so that any point specified in current co-ordinate system can be transformed to the base (original image) co-ordinate system
79 def mergeImages(self, dir_path, imgList):
80 """
81 Merge all image in imgList
82 :param dir_path: directory of images
83 :param imgList: all merging images
84 :return:
85 """
86 imgList2 = []
87 for fname in imgList:
88 fullname = fullPath(dir_path, fname)
89 img = fabio.open(fullname).data
90 img = ifHdfReadConvertless(fname, img)
91 imgList2.append(img)
92 return np.mean(imgList2, axis=0)
94 def loadCache(self, dir_path, imgList):
95 """
96 Load info dict from cache. Cache file will be filename.info in folder "qf_cache"
97 :param dir_path: directory of images
98 :param imgList: input images
99 :return: cached info (dict)
100 """
101 cache_path = fullPath(dir_path, 'dc_cache')
102 cache_filename = imgList[0]+'_'+ imgList[-1]+ '.info'
103 cachefile = fullPath(cache_path, cache_filename)
104 info = {}
106 if isfile(cachefile):
107 cinfo = pickle.load(open(cachefile, "rb"))
108 if cinfo is not None:
109 if cinfo['program_version'] == self.version:
110 info = cinfo
111 else:
112 print("Cache version " + cinfo['program_version'] + " did not match with Program version " + self.version)
113 print("Invalidating cache and reprocessing the image")
115 return cachefile, info
117 def process(self, flags):
118 """
119 All processing steps
120 """
121 imgList = self.info['filelist']
122 print(str(imgList[0])+' ... '+str(imgList[-1])+' are being processed...')
123 self.updateInfo(flags)
124 self.findCenter()
125 self.findRotationAngle()
126 self.calculateRmin()
127 self.getIntegrateArea()
128 self.setConvexhullPoints()
129 self.getHistograms()
130 self.getPeaks()
131 self.correctPeaks()
132 self.calculateBaselines()
133 self.calculateCentroids()
135 if self.init_off_mer is not None:
136 self.getOffMeridianRanges()
137 self.getOffMerRminmax()
138 self.getOffMeridianHistograms()
139 self.getOffMeridianPeaks()
140 self.getOffMeridianBaselines()
141 self.getOffMeridianInfos()
143 if "no_cache" not in self.info:
144 self.cacheInfo()
146 def removeInfo(self, k):
147 """
148 Remove k from info dict
149 :param k: key of dictionary
150 :return: -
151 """
152 if k == 'hists':
153 for ky in ['top_hist', 'top_hull', 'bottom_hist', 'bottom_hull']:
154 self.removeInfo(ky)
155 if k in self.info:
156 del self.info[k]
158 def updateInfo(self, flags):
159 """
160 Update info dict using flags
161 :param flags: flags
162 :return: -
163 """
164 if flags['orientation_model'] is None:
165 if 'orientation_model' not in self.info:
166 flags['orientation_model'] = 0
167 else:
168 del flags['orientation_model']
169 self.info.update(flags)
171 def findCenter(self):
172 """
173 Find center of diffraction, and keep it in self.info["center"]
174 This calculation will affect rotation angle, so self.info["rotationAngle"] will be removed
175 """
176 if 'center' in self.info:
177 if self.rotMat is not None:
178 center = self.info['center']
179 center = np.dot(cv2.invertAffineTransform(self.rotMat), [center[0], center[1], 1])
180 self.info['center'] = (center[0], center[1])
181 self.info['orig_center'] = (center[0], center[1])
182 return
183 self.avgImg, self.info['center'] = processImageForIntCenter(self.avgImg, getCenter(self.avgImg), self.img_type, self.mask_thres)
184 print("center = "+str(self.info['center']))
185 self.removeInfo('rotationAngle')
187 def findRotationAngle(self):
188 """
189 Find rotation angle of diffraction, and keep it in self.info["rotationAngle"]
190 This calculation will affect R-min, so self.info["rmin"] will be removed
191 """
192 if 'rotationAngle' in self.info:
193 return
194 center = self.info['center']
195 self.info['rotationAngle'] = getRotationAngle(self.avgImg, center, self.info['orientation_model'])
196 print("rotation angle = " + str(self.info['rotationAngle']))
197 self.removeInfo('rmin')
199 def calculateRmin(self):
200 """
201 Find R-min of diffraction, and keep it in self.info["rmin"]
202 This calculation will affect integrated area (meridian), so self.info["int_area"] will be removed
203 """
204 if 'rmin' in self.info:
205 return
206 img = copy.copy(self.avgImg)
207 center = self.info['center']
209 if img.shape == (1043, 981):
210 det = "pilatus1m"
211 else:
212 det = "agilent_titan"
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])
218 _, I = ai.integrate1d(img, npt_rad, unit="r_mm", method="csr_ocl")
219 self.info['rmin'] = getFirstVallay(I)
220 print("R-min = "+str(self.info['rmin']))
221 self.removeInfo('int_area')
223 def getIntegrateArea(self):
224 """
225 Find intergrated area or meridian lines of diffraction, and keep it in self.info["int_area"]
226 This calculation will affect start and end points of convexh hull applying points, so self.info["top_se"] and self.info["bottom_se"] will be removed
227 """
228 if 'int_area' in self.info:
229 return
230 rmin = self.info['rmin']
231 img = getCenterRemovedImage(copy.copy(self.avgImg), self.info['center'], self.info['rmin']) # remove center location
232 rotate_img = self.getRotatedImage(img) # rotate image
233 center = self.info['center']
234 l = max(0,center[0]-rmin) # initial guess for left line
235 r = min(center[0]+rmin, rotate_img.shape[1]) # initial guess for right line
236 area = rotate_img[:,l:r] # Get the area by initial guess
237 hist = np.sum(area, axis=0) # find verital histogram
238 hull = convexHull(hist) # appl convex hull to the histogram
240 if len(hist) > 0:
241 max_loc = np.argmax(hull)
242 r = 1
243 l = 1
244 # Find start and end points by finding first zero value from on both side
245 while max_loc-l >= 0 and max_loc+r < len(hull) and (hull[max_loc-l] != 0 or hull[max_loc+r] != 0):
246 if hull[max_loc-l] != 0:
247 l += 1
248 if hull[max_loc+r] != 0:
249 r += 1
251 # Assume that meridian size should not smaller than 50% or R-min
252 if max_loc+r < center[0] or max_loc-l > center[0] or abs(r+l) < rmin*.5:
253 self.info['int_area'] = (int(round(center[0] - rmin*.5)), int(round(center[0] + rmin*.5)) + 1)
254 else:
255 center = center[0]-rmin+max_loc
256 self.info['int_area'] = (int(round(center-l*1.2)), int(round(center+r*1.2))+1)
257 else:
258 self.info['int_area'] = (int(round(center[0] - rmin * .5)), int(round(center[0] + rmin * .5)) + 1)
259 print("integrated area = "+str(self.info['int_area']))
260 self.removeInfo('top_se')
261 self.removeInfo('bottom_se')
263 def getRotatedImage(self, img = None, angle = None):
264 """
265 Get rotated image by angle. If the input params are not specified. image = original input image, angle = self.info["rotationAngle"]
266 :param img: input image
267 :param angle: rotation angle
268 :return: rotated image
269 """
270 if img is None:
271 img = copy.copy(self.avgImg)
272 if angle is None:
273 angle = self.info['rotationAngle']
274 if '90rotation' in self.info and self.info['90rotation'] is True:
275 angle = angle - 90 if angle > 90 else angle + 90
277 center = self.info["center"]
278 if "orig_center" in self.info:
279 center = self.info["orig_center"]
280 else:
281 self.info["orig_center"] = center
283 rotImg, self.info["center"], self.rotMat = rotateImage(img, center, angle, self.img_type, self.mask_thres)
285 return rotImg
287 def setConvexhullPoints(self):
288 """
289 Set start and and points for convex hull in both top and bottom side. Init by start = R-min and end = 0.7*half of image size.
290 This values will be kept in self.info["top_se"] and self.info["bottom_se"]
291 This calculation will affect histograms, so all histograms will be removed from self.info
292 """
293 if 'top_se' not in self.info:
294 if 'top_fixed_se' in self.info:
295 self.info['top_se'] = self.info['top_fixed_se']
296 else:
297 self.info['top_se'] = (self.info['rmin'], int(round(min(self.avgImg.shape[0] / 2, self.avgImg.shape[1] / 2) * 0.7)))
298 self.removeInfo('top_hist')
299 self.removeInfo('top_hull')
300 if 'bottom_se' not in self.info:
301 if 'bottom_fixed_se' in self.info:
302 self.info['bottom_se'] = self.info['bottom_fixed_se']
303 else:
304 self.info['bottom_se'] = (self.info['rmin'], int(round(min(self.avgImg.shape[0]/2, self.avgImg.shape[1]/2) * 0.7)))
305 self.removeInfo('bottom_hist')
306 self.removeInfo('bottom_hull')
308 def getHistograms(self):
309 """
310 Get original histograms and background subtracted histogram ( subtracted by convex hull )
311 The original histogram will be kept in self.info["top_hist"] and self.info["bottom_hist"]
312 The background subtracted histogram will be kept in self.info["top_hull"] and self.info["bottom_hull"]
313 These changing will affect peak locations, so peaks will be removed from self.info
314 """
315 if 'top_hist' in self.info and 'top_hull' in self.info and 'bottom_hist' in self.info and 'bottom_hull' in self.info:
316 return
318 int_area = self.info['int_area']
319 img = self.getRotatedImage(copy.copy(self.avgImg), self.info['rotationAngle'])
320 center_y = self.info['center'][1]
321 img_area = img[:,int_area[0]: int_area[1]]
322 ignore = np.array([any(line <= self.mask_thres) for line in img_area])
323 hist = np.sum(img_area, axis=1)
325 top_hist, top_ignore, bottom_hist, bottom_ignore = self.splitHist(center_y, hist, ignore)
327 if not ('top_hist' in self.info or 'top_hull' in self.info):
328 top_hull = convexHull(top_hist, start_p=self.info['top_se'][0], end_p=self.info['top_se'][1], ignore=top_ignore)
329 self.info['top_hist'] = np.array(top_hist)
330 self.info['top_hull'] = np.array(top_hull)
331 self.removeInfo('pre_top_peaks')
333 if not ('bottom_hist' in self.info or 'bottom_hull' in self.info):
334 bottom_hull = convexHull(bottom_hist, start_p=self.info['bottom_se'][0], end_p=self.info['bottom_se'][1], ignore=bottom_ignore)
335 self.info['bottom_hist'] = np.array(bottom_hist)
336 self.info['bottom_hull'] = np.array(bottom_hull)
337 self.removeInfo('pre_bottom_peaks')
339 def getPeaks(self):
340 """
341 Get pre peaks from histograms with specified fixed ranges or without. These peaks won't be used until it's correct.
342 These pre peaks will be kept in self.info["pre_[side]_peaks"]
343 This calculation will affect reak peaks, so peaks will be removed from self.info
344 """
345 if 'pre_top_peaks' not in self.info:
346 if len(self.fixRanges) > 0:
347 self.info['pre_top_peaks'] = self.getPeaksFromRanges(self.info['top_hull'], self.fixRanges)
348 else:
349 self.info['pre_top_peaks'] = getPeaksFromHist(self.info['top_hull'])
350 self.removeInfo('top_peaks')
352 if 'pre_bottom_peaks' not in self.info:
353 if len(self.fixRanges) > 0:
354 self.info['pre_bottom_peaks'] = self.getPeaksFromRanges(self.info['bottom_hull'], self.fixRanges)
355 else:
356 self.info['pre_bottom_peaks'] = getPeaksFromHist(self.info['bottom_hull'])
357 self.removeInfo('bottom_peaks')
359 def getPeaksFromRanges(self, hist, fix_ranges):
360 """
361 Get Peaks from specified peak ranges
362 :param hist: background subtracted histogram
363 :param fix_ranges: fixed ranges specified by users
364 :return: -
365 """
366 results = []
367 for fr in fix_ranges:
368 r = fr[1]
369 start = min(r[0], len(hist)-2)
370 end = min(r[1], len(hist)-1)
371 peak = start + np.argmax(hist[start:end + 1])
372 results.append(peak)
373 return results
375 def correctPeaks(self):
376 """
377 Correct pre-peak locatons by moving them to the local maximum point.
378 These result peaks are considered as real peaks. They will be kept in self.info["[side]_peaks"]
379 Peaks location will affect baselines, so baselines will be removed from self.info
380 :return:
381 """
382 if 'top_peaks' not in self.info:
383 if len(self.fixRanges) == 0:
384 moved_peaks = self.movePeaks(self.info['top_hull'], self.info['pre_top_peaks'])
385 self.info['top_peaks'] = moved_peaks
386 self.info['top_names'] = [str(p) for p in moved_peaks]
387 else:
388 self.info['top_peaks'] = self.info['pre_top_peaks']
389 self.info['top_names'] = [self.fixRanges[i][0] for i in range(len(self.fixRanges))]
390 self.removeInfo('top_baselines')
392 print("Top peaks : ")
393 for i in range(len(self.info['top_peaks'])):
394 print(str(self.info['top_names'][i])+" : "+str(self.info['top_peaks'][i]))
396 if 'bottom_peaks' not in self.info:
397 if len(self.fixRanges) == 0:
398 moved_peaks = self.movePeaks(self.info['bottom_hull'], self.info['pre_bottom_peaks'])
399 self.info['bottom_peaks'] = moved_peaks
400 self.info['bottom_names'] = [str(p) for p in moved_peaks]
401 else:
402 self.info['bottom_peaks'] = self.info['pre_bottom_peaks']
403 names = [self.fixRanges[i][0] for i in range(len(self.fixRanges))]
404 self.info['bottom_names'] = names
405 self.removeInfo('bottom_baselines')
406 print("Bottom peaks : ")
407 for i in range(len(self.info['bottom_peaks'])):
408 print(str(self.info['bottom_names'][i]) + " : " + str(self.info['bottom_peaks'][i]))
410 def movePeaks(self, hist, peaks, dist = 10):
411 """
412 Move peaks to their local maximum. Duplicated peak locations will be removed
413 :param hist: input histogram
414 :param peaks: approximate peak locations
415 :param dist: maximum distance of local maximum
416 :return: sorted moved peaks
417 """
418 peakList = []
419 smooth_hist = smooth(hist)
420 for p in peaks:
421 new_peak = p
422 while True:
423 start = max(0, p-dist)
424 end = min(len(hist), p+dist)
425 new_peak = start + np.argmax(hist[start:end])
427 if abs(p-new_peak) < 4:
428 break
429 left = min(p, new_peak)
430 right = max(p, new_peak)
431 if all(smooth_hist[left+1:right] > p):
432 break
433 dist = dist/2
434 peakList.append(new_peak)
435 return sorted(list(set(peakList)))
437 def calculateBaselines(self):
438 """
439 Find baselines of peaks. Initial with half-height of peaks
440 Baselines will be kept in self..info["[side]_baselines"].
441 This calulation might affact other infos : centroids width and intensity
442 """
443 if 'top_baselines' not in self.info:
444 hist = self.info['top_hull']
445 peaks = self.info['top_peaks']
446 self.info['top_baselines'] = [hist[p] / 2. for p in peaks]
447 self.removeInfo('top_centroids')
448 print("Top baselines = "+str(self.info['top_baselines']))
449 if 'bottom_baselines' not in self.info:
450 hist = self.info['bottom_hull']
451 peaks = self.info['bottom_peaks']
452 self.info['bottom_baselines'] = [hist[p]/2. for p in peaks]
453 self.removeInfo('bottom_centroids')
454 print("Bottom baselines = "+str(self.info['bottom_baselines']))
456 def calculateCentroids(self):
457 """
458 Calculate all other peaks infomation including centroid, width, and intensity(area)
459 This results will be kept in self.info
460 """
461 if 'top_centroids' not in self.info:
462 hist = self.info['top_hull']
463 peaks = self.info['top_peaks']
464 baselines = self.info["top_baselines"]
465 results = getPeakInformations(hist, peaks, baselines)
466 self.info['top_centroids'] = results['centroids']
467 self.info['top_widths'] = results['widths']
468 self.info['top_areas'] = results['areas']
469 print("Top centroids = "+ str(self.info['top_centroids']))
470 if 'bottom_centroids' not in self.info:
471 hist = self.info['bottom_hull']
472 peaks = self.info['bottom_peaks']
473 baselines = self.info["bottom_baselines"]
474 results = getPeakInformations(hist, peaks, baselines)
475 self.info['bottom_centroids'] = results['centroids']
476 self.info['bottom_widths'] = results['widths']
477 self.info['bottom_areas'] = results['areas']
478 print("Bottom centroids = " + str(self.info['bottom_centroids']))
480 def fitModel(self, hist, peaks, baselines):
481 """
482 JUST FOR TEST
483 Fit Voigt model to histogram using peaks and baselines
484 Currently, this function is not used by any process
485 """
486 new_hist = np.zeros(len(hist))
487 pars = Parameters()
488 mean_margin = 3
490 for (i, p) in enumerate(peaks):
491 baseline = baselines[i]
492 width, _ = getWidth(hist, p, baseline)
493 new_hist[p - width:p + width] = hist[p - width:p + width]
494 prefix = "v"+str(i + 1) + '_'
495 init_amp = hist[p] * width * np.sqrt(2 * np.pi)
496 voigt = VoigtModel(prefix=prefix)
497 pars.add(prefix + 'center', p, min=p - mean_margin, max=p + mean_margin)
498 pars.add(prefix + 'sigma',width, min = 0, max = width*3.)
499 pars.add(prefix + 'amplitude', init_amp, min = 0, max = init_amp*3.)
500 pars.add(prefix + 'gamma', width, min = 0, max = width*3.)
501 pars.add(prefix + 'fwhm', baseline, min=0, max=baseline * 3.)
502 pars.add(prefix + 'height', hist[p], min=0, max= hist[p] * 3.)
503 if i == 0:
504 model = voigt
505 else:
506 model += voigt
508 xs = np.arange(0, len(new_hist))
509 result = model.fit(np.array(new_hist), params = pars, x = xs).values
510 fig = plt.figure()
511 ax = fig.add_subplot(111)
512 ax.plot(hist)
513 ax.plot(new_hist)
514 ax.plot(model.eval(x=xs, **result))
515 fig.show()
516 print(str(result))
518 def setBaseline(self, side, peak_num, new_baseline):
519 """
520 Set new baselines of meridina peaks by users. Remove centroids from self.info as it needs to be re-calculated.
521 :param side: "top" or "bottom"
522 :param peak_num: peak number from left to right (int)
523 :param new_baseline: new baseline value or percent of peak height (str or float)
524 """
525 new_baseline = str(new_baseline)
526 hist = self.info[side + "_hull"]
527 peaks = self.info[side+ "_peaks"]
528 baselines = self.info[side + "_baselines"]
529 height = hist[peaks[peak_num]]
530 if "%" in new_baseline:
531 # if new_baseline contain "%", baseline value will use this as percent of peak height
532 percent = float(new_baseline.rstrip("%"))
533 baseline = height*percent/100.
534 elif len(new_baseline) == 0:
535 # if new_baseline is empty, baseline will by half-height
536 baseline = float(height*.5)
537 else:
538 baseline = float(new_baseline)
540 if height > baseline:
541 baselines[peak_num] = baseline
543 self.removeInfo(side+"_centroids")
545 def setOffMerBaseline(self, quadrant, ind, new_baseline):
546 """
547 Set new baselines of off-meridian peaks by users. Remove other infos from self.info as it needs to be re-calculated.
548 :param quadrant: "top_left", "top_right, "bottom_left", or "bottom_right"
549 :param ind: peak number from left to right (int)
550 :param new_baseline: new baseline value or percent of peak height (str or float)
551 """
552 new_baseline = str(new_baseline)
553 hist = self.info["off_mer_hists"]["hulls"][quadrant]
554 peak = self.info["off_mer_peaks"][quadrant][ind]
555 baselines = self.info["off_mer_baselines"][quadrant]
556 height = hist[peak]
558 if "%" in new_baseline:
559 # if new_baseline contain "%", baseline value will use this as percent of peak height
560 percent = float(new_baseline.rstrip("%"))
561 baseline = height * percent / 100.
562 elif len(new_baseline) == 0:
563 # if new_baseline is empty, baseline will by half-height
564 baseline = float(height * .5)
565 else:
566 baseline = float(new_baseline)
568 if height > baseline:
569 baselines[ind] = baseline
571 self.removeInfo("off_mer_peak_info")
573 def getOffMeridianRanges(self):
574 """
575 Get off-meridian ranges as position in the image from x1,x2,x3,x4 which are specified by users
576 """
577 if "x1" not in self.info:
578 centerX = self.info["center"][0]
579 self.info["x1"] = centerX - self.init_off_mer["x1"]
580 self.info["x2"] = centerX - self.init_off_mer["x2"]
581 self.info["x3"] = centerX + self.init_off_mer["x3"]
582 self.info["x4"] = centerX + self.init_off_mer["x4"]
583 self.removeInfo("off_mer_rmin_rmax")
585 def splitHist(self, center_y, hist, ignore):
586 """
587 Split histogram to top and bottom by center_y
588 :param center_y: center location of histogram (int)
589 :param hist: input histogram (list)
590 :param ignore: ignored locations in histogram (list of boolean)
591 :return: top_hist, top_ignore, bottom_hist, bottom_ignore (list)
592 """
593 top_hist = hist[center_y:]
594 top_ignore = ignore[center_y:]
595 bottom_hist = hist[:center_y]
596 bottom_hist = bottom_hist[::-1]
597 bottom_ignore = ignore[:center_y]
598 bottom_ignore = bottom_ignore[::-1]
599 return top_hist, top_ignore, bottom_hist, bottom_ignore
601 def getOffMerRminmax(self):
602 """
603 Produce Rmin and Rmax of off-meridian ranges.
604 """
605 if 'off_mer_rmin_rmax' not in self.info:
606 if 'fixed_offmer_hull_range' in self.info:
607 self.info['off_mer_rmin_rmax'] = self.info['fixed_offmer_hull_range']
608 else:
609 rmin, rmax = self.initOffMeridianPeakRange()
610 self.info['off_mer_rmin_rmax'] = (rmin, rmax)
611 self.removeInfo("off_mer_hists")
613 def getOffMeridianHistograms(self):
614 """
615 Produce histograms of off-meridian ranges.
616 All histograms will be kept in self.info["off_mer_hists"]["hists"]
617 All backgound subtracted histograms will be kept in self.info["off_mer_hists"]["hull"]
618 """
619 if "off_mer_hists" not in self.info:
620 x1 = self.info["x1"]
621 x2 = self.info["x2"]
622 x3 = self.info["x3"]
623 x4 = self.info["x4"]
624 img = self.getRotatedImage()
625 center_y = self.info["center"][1]
626 left_area = img[:, x2:x1]
627 right_area = img[:, x3:x4]
628 # left_ignore = np.array([any(line < 0) for line in left_area])
629 # right_ignore = np.array([any(line < 0) for line in right_area])
631 left_ignore = np.array([sum(np.array(line) < 0) > 0.1*len(line) for line in left_area])
632 right_ignore = np.array([sum(np.array(line) < 0) > 0.1*len(line) for line in right_area])
633 left_hist = np.sum(left_area, axis=1)
634 right_hist = np.sum(right_area, axis=1)
635 top_left_hist, top_left_ignore, bottom_left_hist, bottom_left_ignore = self.splitHist(center_y, left_hist,
636 left_ignore)
637 top_right_hist, top_right_ignore, bottom_right_hist, bottom_right_ignore = self.splitHist(center_y, right_hist,
638 right_ignore)
639 start, end = self.info['off_mer_rmin_rmax']
640 top_left_hull = convexHull(top_left_hist, start_p=start, end_p=end, ignore=top_left_ignore)
641 bottom_left_hull = convexHull(bottom_left_hist, start_p=start, end_p=end, ignore=bottom_left_ignore)
642 top_right_hull = convexHull(top_right_hist, start_p=start, end_p=end, ignore=top_right_ignore)
643 bottom_right_hull = convexHull(bottom_right_hist, start_p=start, end_p=end, ignore=bottom_right_ignore)
645 self.info["off_mer_hists"] = {
646 "hists" : {
647 "top_left" : np.array(top_left_hist),
648 "top_right": np.array(top_right_hist),
649 "bottom_left" : np.array(bottom_left_hist),
650 "bottom_right": np.array(bottom_right_hist)
651 },
652 "hulls": {
653 "top_left": np.array(top_left_hull),
654 "top_right": np.array(top_right_hull),
655 "bottom_left": np.array(bottom_left_hull),
656 "bottom_right": np.array(bottom_right_hull)
657 }
658 }
659 self.removeInfo("off_mer_peaks")
661 def getOffMeridianPeaks(self):
662 """
663 Get peak 51 and 59 from 4 quadrants by using peak ranges speicifed by users
664 These peaks will be kept in self.info["off_mer_peaks"]
665 This might affect baselines, so off_mer_baselines is removed from self.info
666 """
667 if "off_mer_peaks" not in self.info:
668 peaks = {}
669 hulls = self.info["off_mer_hists"]["hulls"]
670 peak_ranges = [("59",(self.init_off_mer["s59"], self.init_off_mer["e59"])),
671 ("51",(self.init_off_mer["s51"], self.init_off_mer["e51"]))]
672 for k in hulls.keys():
673 peaks[k] = self.getPeaksFromRanges(hulls[k], peak_ranges)
674 self.info["off_mer_peaks"] = peaks
675 self.removeInfo("off_mer_baselines")
677 def getOffMeridianBaselines(self):
678 """
679 Get baselines of peak 51 and 59 from 4 quadrants. Init with half-height of peaks
680 These baselines will be kept in self.info["off_mer_baselines"]
681 This might affect peak infos, so off_mer_peak_info is removed from self.info
682 """
683 if "off_mer_baselines" not in self.info:
684 baselines = {}
685 peaks = self.info["off_mer_peaks"]
686 hulls = self.info["off_mer_hists"]["hulls"]
687 for k in peaks.keys():
688 baselines[k] = [hulls[k][p]*.5 for p in peaks[k]]
689 self.info["off_mer_baselines"] = baselines
690 self.removeInfo("off_mer_peak_info")
692 def getOffMeridianInfos(self):
693 """
694 Get information of peak 51 and 59 from 4 quadrants including centroid, width, intersection with baseline, area (intensity)
695 These info will be kept in self.info["off_mer_peak_info"]
696 """
697 if "off_mer_peak_info" not in self.info:
698 all_info = {}
699 peaks = self.info["off_mer_peaks"]
700 hulls = self.info["off_mer_hists"]["hulls"]
701 baselines = self.info["off_mer_baselines"]
703 for k in peaks.keys():
704 peak_list = peaks[k]
705 hull = hulls[k]
706 baseline_list = baselines[k]
707 results = getPeakInformations(hull, peak_list, baseline_list)
708 all_info[k] = copy.copy(results)
710 self.info["off_mer_peak_info"] = all_info
712 def initOffMeridianPeakRange(self):
713 """
714 Find start and end points of peak 51 and 59 for applying convex hull
715 """
716 return int(round(0.9*self.init_off_mer["s59"])), int(round(1.1*self.init_off_mer["e51"]))
718 def cacheInfo(self):
719 """
720 Save info dict to cache. Cache file will be save as filename.info in folder "qf_cache"
721 :return: -
722 """
723 cache_path = fullPath(self.dir_path, 'dc_cache')
725 if not exists(cache_path):
726 makedirs(cache_path)
728 self.info["program_version"] = self.version
729 pickle.dump(self.info, open(self.cachefile, "wb"))