Coverage for modules/ScanningDiffraction.py: 60%

868 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 

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 * 

52 

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

75 

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

88 

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

99 

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) 

109 

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

122 

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

133 

134 self.log(fullPath(self.filepath, self.filename) + " has been processed.") 

135 self.cacheInfo() 

136 

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] 

150 

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) 

177 

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

187 

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) 

200 

201 center = self.info['center'] 

202 

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 

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 

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) 

221 

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) 

224 

225 self.info['2dintegration'] = [I2D, tth, chi] 

226 self.info['tophat_2dintegration'] = [I2D2, tth2, chi2] 

227 

228 hists = list(I) 

229 

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) 

235 

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

251 

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

261 

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

274 

275 ranges, histograms = self.get_partial_integrations(ref_angle) 

276 self.info['partial_ranges'] = ranges 

277 self.info['m1_partial_hists'] = histograms 

278 

279 hulls = [] 

280 all_peaks = [] 

281 partial_peaks = [] 

282 

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) 

289 

290 self.info['m1_partial_hulls'] = hulls 

291 self.info['m1_partial_peaks'] = partial_peaks 

292 

293 histnp = np.array(self.info['hull_hist']) 

294 indexes = self.select_peaks(sorted(all_peaks), times_threshold=2, distance_threshold=15) 

295 

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

299 

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) 

306 

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) 

309 

310 all_peaks_list = sorted(peakList1) 

311 

312 self.log("Selected peaks = " + str(all_peaks_list)) 

313 self.info['m1_rings'] = all_peaks_list 

314 self.removeInfo('merged_peaks') 

315 

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 

330 

331 dict_runs = self.get_runs_from_image(central_log) 

332 

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

336 

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

341 

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

358 

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) 

378 

379 return sigmaList 

380 

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) 

394 

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

400 

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 

411 

412 best_ind = np.argmin(temp_error) 

413 result = temp_model[best_ind] 

414 self.log("Selected Fitting Method = "+str(methods[best_ind])) 

415 

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) 

420 

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

423 

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

443 

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 

447 

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

459 

460 blank, mask = getBlankImageAndMask(self.filepath) 

461 img = copy.copy(self.original_image) 

462 if blank is not None: 

463 img = img - blank 

464 

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

466 det = "pilatus1m" # This detector has the size (1043, 981) 

467 else: 

468 det = "agilent_titan" 

469 

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

475 

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) 

480 

481 return ranges, histograms 

482 

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) 

492 

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

516 

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 

531 

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 = [] 

542 

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 

549 

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

565 

566 if i + c in runs: 

567 end = i + c 

568 dist = 0 

569 else: 

570 dist += 1 

571 

572 if dist > distance_threshold: 

573 result_rings[(end + start) / 2] = (start, end) 

574 i += c 

575 break 

576 i += 1 

577 

578 return result_rings 

579 

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) 

591 

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

602 

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 

606 

607 # Group by same peak using distance_threshold 

608 l_peaks = sorted(aux_results.keys()) 

609 

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) 

618 

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] 

625 

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} 

630 

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] 

639 

640 # Number of hypothesis 

641 h = 4 

642 min_di = 1 

643 errors = [] 

644 

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 

655 

656 return 1. * peaks[0] / min_di 

657 

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 

671 

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

686 

687 # Limit rings to inside image 

688 image_lenght = len(self.info['orig_hists']) - self.info['start_point'] 

689 

690 if limit * dist > image_lenght: 

691 limit = int(image_lenght/dist) 

692 

693 if limit < 10: 

694 limit = 0 

695 for limit in range(1, 10): 

696 if limit * dist > image_lenght: 

697 break 

698 

699 limit = min(10 ,limit) 

700 

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

713 

714 return peaks, sigmas, typ 

715 

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 

734 

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 

741 

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 = [] 

750 

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 

755 

756 while i < len(orig_hist)-1: 

757 # Find start point of a valley by its slope 

758 if deep_hist[i] < 0: 

759 

760 # Move left for 2 to start before valley 

761 start_ind = max(0, i-2) 

762 

763 # Pass valley 

764 while i < len(deep_hist)-1 and deep_hist[i] < 0: 

765 i = i + 1 

766 

767 # Move right for 1 to end after valley 

768 end_ind = min(i+2, len(orig_hist)-1) 

769 i = end_ind 

770 

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

781 

782 orig_hist[start_ind: end_ind+1] = sub 

783 start_points.append(start_ind) 

784 end_points.append(end_ind) 

785 

786 i = i + 1 

787 

788 return orig_hist 

789 

790 

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 

801 

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 

807 

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 

810 

811 start_ind = max(0, start_ind - 3) 

812 end_ind = min(i, len(hist2) - 1) 

813 

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 

827 

828 return hist2 

829 

830 def process_rings(self): 

831 """ 

832 Process all rings 

833 :return: 

834 """ 

835 peaks = self.info['model_peaks'] 

836 result = [] 

837 

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] 

841 

842 histogram1D = self.info['hull_hist'] 

843 rings = [] 

844 ring_peaks = [] 

845 ring_hists = [] 

846 

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) 

853 

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

858 

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) 

863 

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) 

871 

872 # Add type of ring and angle range if not complete 

873 if type_rings[i] != -1: 

874 type_rings[i] = degrees 

875 

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) 

880 

881 # Filled pilatus valleys 

882 if self.original_image.shape == (1043, 981): 

883 hists2 = self.removeValleys(hists2) 

884 

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

889 

890 # Compute peak of each ring 

891 ring_peaks.append(max(hists2)) 

892 ring_hists.append(hists) 

893 

894 

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 

902 

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 

909 

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) 

920 

921 index = np.argmax(hist[1]) 

922 u1 = hist[0][index] 

923 u2 = (u1 + np.pi)%(2*np.pi) 

924 d = 180 

925 

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

938 

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] 

943 

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

948 

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) 

955 

956 result = model.fit(hist[1], x=x, params = params, nan_policy='propagate') 

957 

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) 

961 

962 hist_shifted = np.roll(hist[1], -v_point) 

963 

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. 

970 

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) 

976 

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 

980 

981 # Correction over shifted peaks 

982 result['u'] = result['u'] + v_value - np.pi 

983 

984 return result 

985 

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) 

994 

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 

1002 

1003 # Fit model using same gaussian 

1004 x = hist[0] 

1005 

1006 # Call orientation_GMM3 

1007 model = Model(orientation_GMM3, independent_vars='x') 

1008 max_height = np.max(hist[1]) 

1009 

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) 

1014 

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

1023 

1024 return result.values 

1025 

1026 def getRingHistograms(self): 

1027 """ 

1028 Give the histogram of the different rings on the image. 

1029 """ 

1030 histograms = [] 

1031 deep_valleys_hists = [] 

1032 

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 

1044 

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

1050 

1051 if len(histograms) < 1: 

1052 return [], [], [] 

1053 

1054 ring_hists, revised_hists = [], [] 

1055 for (i, _) in enumerate(histograms): 

1056 

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 

1064 

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) 

1071 

1072 return ring_hists, revised_hists, idxs 

1073 

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

1083 

1084 x = np.arange(0, 2 * np.pi, 2 * np.pi / 360) 

1085 

1086 for i, hist in zip(idx_dict, revised_hists): 

1087 

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) 

1095 

1096 self.info['ring_hists'] = ring_hists 

1097 self.info['ring_models'] = model_dict 

1098 self.info['ring_errors'] = errors_dict 

1099 

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) 

1104 

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

1127 

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 

1143 

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 

1152 

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 

1183 

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

1194 

1195 model_dict, errors_dict = {}, {} 

1196 for i, hist in zip(idx_dict, revised_hists): 

1197 

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) 

1203 

1204 self.info['ring_hists'] = ring_hists 

1205 self.info['ring_models'] = model_dict 

1206 self.info['ring_errors'] = errors_dict 

1207 

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

1214 

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

1223 

1224 model_dict, errors_dict = {}, {} 

1225 for i, hist in zip(idx_dict, revised_hists): 

1226 

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 

1231 

1232 self.info['ring_hists'] = ring_hists 

1233 self.info['ring_models'] = model_dict 

1234 self.info['ring_errors'] = errors_dict 

1235 

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

1241 

1242############################# Batch mode Results ############################# 

1243 

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 

1255 

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) 

1264 

1265############################# Gaussian Models ############################# 

1266 

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

1272 

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 

1282 

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 

1289 

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) 

1296 

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 

1305 

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

1316 

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) 

1329 

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

1340 

1341 error = r2_score(GMM_any(x = x, params = result), hists_np) 

1342 return result, error 

1343 

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

1354 

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] 

1369 

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] 

1384 

1385 error = r2_score(GMM_any(x = x, params = result), hists_np) 

1386 return result, error