Coverage for modules/DiffractionCentroids.py: 11%

436 statements  

« prev     ^ index     » next       coverage.py v7.0.4, created at 2023-01-10 09:27 -0600

1""" 

2Copyright 1999 Illinois Institute of Technology 

3 

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: 

11 

12The above copyright notice and this permission notice shall be 

13included in all copies or substantial portions of the Software. 

14 

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. 

22 

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""" 

28 

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 * 

46 

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 

78 

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) 

93 

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 = {} 

105 

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") 

114 

115 return cachefile, info 

116 

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() 

134 

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() 

142 

143 if "no_cache" not in self.info: 

144 self.cacheInfo() 

145 

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] 

157 

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) 

170 

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') 

186 

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') 

198 

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'] 

208 

209 if img.shape == (1043, 981): 

210 det = "pilatus1m" 

211 else: 

212 det = "agilent_titan" 

213 

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') 

222 

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 

239 

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 

250 

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') 

262 

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 

276 

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 

282 

283 rotImg, self.info["center"], self.rotMat = rotateImage(img, center, angle, self.img_type, self.mask_thres) 

284 

285 return rotImg 

286 

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') 

307 

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 

317 

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) 

324 

325 top_hist, top_ignore, bottom_hist, bottom_ignore = self.splitHist(center_y, hist, ignore) 

326 

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') 

332 

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') 

338 

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') 

351 

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') 

358 

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 

374 

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') 

391 

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])) 

395 

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])) 

409 

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]) 

426 

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))) 

436 

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'])) 

455 

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'])) 

479 

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 

489 

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 

507 

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)) 

517 

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) 

539 

540 if height > baseline: 

541 baselines[peak_num] = baseline 

542 

543 self.removeInfo(side+"_centroids") 

544 

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] 

557 

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) 

567 

568 if height > baseline: 

569 baselines[ind] = baseline 

570 

571 self.removeInfo("off_mer_peak_info") 

572 

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") 

584 

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 

600 

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") 

612 

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]) 

630 

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) 

644 

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") 

660 

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") 

676 

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") 

691 

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"] 

702 

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) 

709 

710 self.info["off_mer_peak_info"] = all_info 

711 

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"])) 

717 

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') 

724 

725 if not exists(cache_path): 

726 makedirs(cache_path) 

727 

728 self.info["program_version"] = self.version 

729 pickle.dump(self.info, open(self.cachefile, "wb"))