Coverage for modules/EquatorImage.py: 63%

734 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 os 

30from os import makedirs 

31from os.path import isfile, exists 

32import numpy as np 

33import json 

34import pickle 

35import tifffile 

36from lmfit import Model, Parameters 

37from lmfit.models import VoigtModel, GaussianModel 

38from sklearn.metrics import r2_score, mean_squared_error 

39from pyFAI.azimuthalIntegrator import AzimuthalIntegrator 

40import fabio 

41from musclex import __version__ 

42try: 

43 from ..utils.file_manager import fullPath, getBlankImageAndMask, getMaskOnly, ifHdfReadConvertless 

44 from ..utils.histogram_processor import * 

45 from ..utils.image_processor import * 

46except: # for coverage 

47 from utils.file_manager import fullPath, getBlankImageAndMask, getMaskOnly, ifHdfReadConvertless 

48 from utils.histogram_processor import * 

49 from utils.image_processor import * 

50 

51class EquatorImage: 

52 """ 

53 A class for Bio-Muscle processing - go to process() to see all processing steps 

54 """ 

55 def __init__(self, dir_path, filename, parent, file_list=None, extension=''): 

56 """ 

57 Initial value for EquatorImage object 

58 :param dir_path: directory path of input image 

59 :param filename: image file name 

60 """ 

61 self.sigmaS = 0.0001 

62 self.dir_path = dir_path 

63 self.filename = filename 

64 if extension in ('.hdf5', '.h5'): 

65 index = next((i for i, item in enumerate(file_list[0]) if item == filename), 0) 

66 self.orig_img = file_list[1][index] 

67 else: 

68 self.orig_img = fabio.open(fullPath(dir_path, filename)).data 

69 self.orig_img = ifHdfReadConvertless(self.filename, self.orig_img) 

70 self.orig_img = self.orig_img.astype("float32") 

71 self.image = None 

72 self.skeletalVarsNotSet = False 

73 if self.orig_img.shape == (1043, 981): 

74 self.img_type = "PILATUS" 

75 else: 

76 self.img_type = "NORMAL" 

77 

78 self.quadrant_folded = False 

79 if filename.endswith(".tif"): 

80 with tifffile.TiffFile(fullPath(dir_path, filename)) as tif: 

81 if "ImageDescription" in tif.pages[0].tags: 

82 metadata = tif.pages[0].tags["ImageDescription"].value 

83 try: 

84 self.quadrant_folded, self.initialImgDim = json.loads(metadata) 

85 except Exception: 

86 print(filename, " file is not quadrant folded") 

87 

88 self.rotated_img = None 

89 self.version = __version__ 

90 cache = self.loadCache() 

91 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 

92 if cache is None: 

93 # info dictionary will save all results 

94 self.info = { 

95 "mask_thres" : 0 #getMaskThreshold(self.orig_img, self.img_type) 

96 } 

97 else: 

98 self.info = cache 

99 if parent is not None: 

100 self.parent = parent 

101 else: 

102 self.parent = self 

103 

104 def process(self, settings, paramInfo=None): 

105 """ 

106 All processing steps - all settings are provided by bio-muscle app as a dictionary 

107 settings must have ... 

108 nPeaks - number of peaks (int) 

109 model - "Voigt" or "Gaussian" (str) 

110 sigmac - (float) 

111 isSkeletal - is it skeletal muscle (boolean) 

112 """ 

113 print("settings in process eqimg\n") 

114 print(settings) 

115 self.updateInfo(settings) 

116 self.applyBlankAndMask() 

117 self.findCenter() 

118 self.getRotationAngle() 

119 self.calculateRmin() 

120 self.getIntegrateArea() 

121 self.getHistogram() 

122 self.applyConvexhull() 

123 self.getPeaks() 

124 self.managePeaks() 

125 if paramInfo is not None: 

126 self.processParameters(paramInfo) 

127 else: 

128 self.fitModel() 

129 if "no_cache" not in settings: 

130 self.saveCache() 

131 self.parent.statusPrint("") 

132 

133 def removeInfo(self, k=None): 

134 """ 

135 Remove information from info dictionary by k as a key. If k is None, remove all information in the dictionary 

136 :param k: key of dictionary 

137 :return: - 

138 """ 

139 if k is None: 

140 keys = list(self.info.keys()) 

141 for key in keys: 

142 del self.info[key] 

143 else: 

144 if k in self.info: # remove from dictionary if the key exists 

145 del self.info[k] 

146 

147 def updateInfo(self, settings): 

148 """ 

149 Update info dict using settings 

150 :param settings: calibration settings 

151 :return: - 

152 """ 

153 if settings['orientation_model'] is None: 

154 if 'orientation_model' not in self.info or self.info['orientation_model'] is None: 

155 settings['orientation_model'] = 0 

156 else: 

157 del settings['orientation_model'] 

158 self.info.update(settings) 

159 

160 def applyBlankAndMask(self): 

161 """ 

162 Subtract the original image with blank image and set pixels in mask below the mask threshold 

163 """ 

164 img = np.array(self.orig_img, dtype='float32') 

165 if self.info['blank_mask']: 

166 blank, mask = getBlankImageAndMask(self.dir_path) 

167 maskOnly = getMaskOnly(self.dir_path) 

168 print(maskOnly) 

169 if blank is not None: 

170 img = img - blank 

171 if mask is not None: 

172 img[mask>0] = self.info['mask_thres']-1 

173 if maskOnly is not None: 

174 print("Applying mask only image") 

175 img[maskOnly>0] = self.info['mask_thres']-1 

176 

177 self.image = img 

178 

179 def findCenter(self): 

180 """ 

181 Find center of the diffraction. The center will be kept in self.info["center"]. 

182 Once the center is calculated, the rotation angle will be re-calculated, so self.info["rotationAngle"] is deleted 

183 """ 

184 self.parent.statusPrint("Finding Center...") 

185 print("Center is being calculated...") 

186 if 'center' not in self.info: 

187 if 'calib_center' in self.info: 

188 print("Using Calibration Center") 

189 self.info['center'] = self.info['calib_center'] 

190 return 

191 self.orig_img, self.info['center'] = processImageForIntCenter(self.orig_img, getCenter(self.orig_img), self.img_type, self.info['mask_thres']) 

192 self.removeInfo('rotationAngle') # Remove rotationAngle from info dict to make it be re-calculated 

193 else: 

194 if self.rotMat is not None: 

195 center = self.info['center'] 

196 center = np.dot(cv2.invertAffineTransform(self.rotMat), [center[0], center[1], 1]) 

197 self.info['orig_center'] = (center[0], center[1]) 

198 print("Done. Center is" + str(self.info['center'])) 

199 

200 def getRotationAngle(self): 

201 """ 

202 Find rotation angle of the diffraction. Turn the diffraction equator to be horizontal. The angle will be kept in self.info["rotationAngle"] 

203 Once the rotation angle is calculated, the rmin will be re-calculated, so self.info["rmin"] is deleted 

204 """ 

205 self.parent.statusPrint("Finding Rotation Angle...") 

206 

207 if self.quadrant_folded: 

208 print("Quadrant folded image: ignoring rotation angle computation") 

209 self.info['rotationAngle'] = 0 

210 return 

211 

212 if "fixed_angle" in self.info: 

213 self.info['rotationAngle'] = self.info["fixed_angle"] 

214 print("RotationAngle is fixed as " + str(self.info['fixed_angle'])) 

215 return 

216 

217 print("Rotation Angle is being calculated...") 

218 if 'rotationAngle' not in self.info: 

219 center = self.info['center'] 

220 img = copy.copy(self.image) 

221 self.info['rotationAngle'] = getRotationAngle(img, center, self.info['orientation_model']) 

222 self.removeInfo('rmin') # Remove R-min from info dict to make it be re-calculated 

223 

224 if "mode_angle" in self.info: 

225 print(f'Using mode orientation {self.info["mode_angle"]}') 

226 self.info['rotationAngle'] = self.info["mode_angle"] 

227 

228 print("Done. Rotation Angle is " + str(self.info['rotationAngle'])) 

229 

230 def calculateRmin(self): 

231 """ 

232 Calculate R-min of the diffraction. The R-min will be kept in self.info["rmin"] 

233 Once the R-min is calculated, the integrated area (Box Width) will be re-calculated, so self.info["int_area"] is deleted 

234 """ 

235 self.parent.statusPrint("Calculating Rmin...") 

236 if 'fixed_rmin' in self.info: 

237 self.info['rmin'] = self.info['fixed_rmin'] 

238 print("R-min is fixed as " + str(self.info['rmin'])) 

239 return 

240 

241 print("R-min is being calculated...") 

242 if 'rmin' not in self.info: 

243 img = copy.copy(self.orig_img) 

244 center = self.info['center'] 

245 

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

247 det = "pilatus1m" 

248 else: 

249 det = "agilent_titan" 

250 

251 corners = [(0, 0), (img.shape[1], 0), (0, img.shape[0]), (img.shape[1], img.shape[0])] 

252 npt_rad = int(round(max([distance(center, c) for c in corners]))) 

253 ai = AzimuthalIntegrator(detector=det) 

254 ai.setFit2D(100, center[0], center[1]) 

255 _, I = ai.integrate1d(img, npt_rad, unit="r_mm", method="csr_ocl") # Get 1D Azimuthal integrated histogram 

256 self.info['rmin'] = getFirstVallay(I) # R-min is value before the first valley 

257 self.removeInfo('int_area') # Remove integrated area from info dict to make it be re-calculated 

258 

259 print("Done. R-min is " + str(self.info['rmin'])) 

260 

261 def getRotatedImage(self, img=None, angle=None): 

262 """ 

263 Get rotated image by angle. If the input params are not specified. image = original input image, angle = self.info["rotationAngle"] 

264 :param img: input image 

265 :param angle: rotation angle 

266 :return: rotated image 

267 """ 

268 if img is None: 

269 img = copy.copy(self.image) 

270 if angle is None: 

271 angle = self.info['rotationAngle'] 

272 if '90rotation' in self.info and self.info['90rotation'] is True: 

273 angle = angle - 90 if angle > 90 else angle + 90 

274 

275 if self.rotated_img is not None: 

276 centersNotEq = self.rotated_img[0] != self.info["center"] if type(self.rotated_img[0] != self.info["center"]) == bool else (self.rotated_img[0] != self.info["center"]).any() 

277 if self.rotated_img is None or centersNotEq or self.rotated_img[1] != self.info["rotationAngle"] or (self.rotated_img[2] != img).any(): 

278 # encapsulate rotated image for using later as a list of [center, angle, original image, rotated image[ 

279 

280 center = self.info["center"] 

281 if "orig_center" in self.info: 

282 center = self.info["orig_center"] 

283 # print("orig_center",self.info['orig_center']) 

284 else: 

285 self.info["orig_center"] = center 

286 

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

288 self.rotated_img = [self.info["center"], angle, img, rotImg] 

289 

290 return self.rotated_img[3] 

291 

292 def getIntegrateArea(self): 

293 """ 

294 Calculate Integrated Area (Box width) of the diffraction. The integrated area will be start and end points in y direction for getting histogram. 

295 The Integrated Area will be kept in self.info["int_area"] 

296 Once the Integrated Area is calculated, the histograms will be re-calculated, so self.info["hist"] is deleted 

297 """ 

298 self.parent.statusPrint("Calculating Integrated Area...") 

299 print("Integrated Area is being calculated...") 

300 if 'int_area' not in self.info: 

301 center = self.info['center'] 

302 if 'fixed_int_area' in self.info: # integrated area is fixed by users 

303 self.info['int_area'] = self.info['fixed_int_area'] 

304 else: 

305 rmin = self.info['rmin'] 

306 img = getCenterRemovedImage(copy.copy(self.image), tuple(center), rmin) # remove center location 

307 

308 rotate_img = self.getRotatedImage(img) # rotate image 

309 center = self.info["center"] #since rotation might change center 

310 init_range = int(round(rmin * 1.5)) # specify initial guess by using 150% or R-min 

311 top = max(0, int(center[1]) - init_range) 

312 bottom = min(int(center[1]) + init_range, rotate_img.shape[0]) 

313 area = rotate_img[top:bottom, :] 

314 hist = np.sum(area, axis=1) # Get the horizontal histogram from the initital area 

315 hull = convexHull(hist) # Apply convexhull 

316 

317 if len(hist) > 0: 

318 max_loc = np.argmax(hull) 

319 r = 1 

320 l = 1 

321 # Find 0 on the left and right 

322 while max_loc - l >= 0 and max_loc + r < len(hull) and ( 

323 hull[max_loc - l] != 0 or hull[max_loc + r] != 0): 

324 if hull[max_loc - l] != 0: 

325 l += 1 

326 if hull[max_loc + r] != 0: 

327 r += 1 

328 

329 if max_loc + r < center[1] or max_loc - l > center[1] or abs(r + l) < rmin * .7: 

330 self.info['int_area'] = ( 

331 int(round(center[1] - rmin * .7)), int(round(center[1] + rmin * .7)) + 1) 

332 else: 

333 center = center[1] - rmin + max_loc 

334 self.info['int_area'] = (int(round(center - l * 1.2)), int(round(center + r * 1.2)) + 1) 

335 else: 

336 # if convex hull does not work properly, integration area will be 70% of R-min 

337 self.info['int_area'] = (int(round(center[1] - rmin * .7)), int(round(center[1] + rmin * .7)) + 1) 

338 

339 self.removeInfo('hist') # Remove histograms from info dict to make it be re-calculated 

340 

341 print("Done. Integrated Area is " + str(self.info['int_area'])) 

342 

343 def getHistogram(self): 

344 """ 

345 Getting original histogram of the diffraction in the integrated area. Histogram will be kept in self.info["hist"] 

346 Once getting histogram is done, the background subtracted histogram will be re-calculated, so self.info["hulls"] is deleted 

347 """ 

348 self.parent.statusPrint("Getting Histogram...") 

349 print("Getting Histogram...") 

350 if 'hist' not in self.info: 

351 int_area = self.info['int_area'] 

352 img = self.getRotatedImage() 

353 self.info['hist'] = np.sum(img[int_area[0]:int_area[1], :], axis=0) 

354 self.removeInfo('hulls') # Remove background subtracted histogram from info dict to make it be re-calculated 

355 

356 print("Done.") 

357 

358 def applyConvexhull(self): 

359 """ 

360 Getting backgound subtracted histogram by applying Convex hull algorithm. Background Subtracted Histogram will be kept in self.info["hulls"] 

361 This will provide left, right, and both separated by centerX 

362 Once getting background subtracted histogram is done, the temp peaks will be re-calculated, so self.info["tmp_peaks"] is deleted 

363 """ 

364 self.parent.statusPrint("Applying Convex Hull...") 

365 print("Applying Convexhull...") 

366 if 'hulls' not in self.info: 

367 center = self.info['center'] 

368 shapes = self.image.shape 

369 rmax = int(min(center[0], center[1], shapes[1] - center[0], shapes[0] - center[1]) * 0.8) 

370 rmin = self.info['rmin'] 

371 hist = copy.copy(self.info['hist']) 

372 int_area = self.info['int_area'] 

373 img_area = self.getRotatedImage()[int_area[0]:int_area[1], :] 

374 min_val = self.orig_img.min() 

375 if self.img_type == "PILATUS": 

376 histo = np.histogram(self.orig_img, 3, (min_val, min_val+3)) 

377 max_ind = np.argmax(histo[0]) 

378 self.info['mask_thres'] = histo[1][max_ind] 

379 else: 

380 self.info['mask_thres'] = min_val - 1. #getMaskThreshold(self.orig_img, self.img_type) 

381 ignore = np.array([any(img_area[:, i] <= self.info['mask_thres']) for i in range(img_area.shape[1])]) 

382 if any(ignore): 

383 left_ignore = ignore[:int(center[0])] 

384 left_ignore = left_ignore[::-1] 

385 right_ignore = ignore[int(center[0]):] 

386 else: 

387 left_ignore = None 

388 right_ignore = None 

389 

390 left_hist = hist[:int(center[0])][::-1] 

391 right_hist = hist[int(center[0]):] 

392 right_hull = convexHull(right_hist, start_p=rmin, end_p=rmax, ignore=right_ignore) # Apply Convex hull for right histogram 

393 left_hull = convexHull(left_hist, start_p=rmin, end_p=rmax, ignore=left_ignore) # Apply Convex hull for left histogram 

394 

395 hull = copy.copy(list(left_hull[::-1])) 

396 hull.extend(list(right_hull[:])) 

397 

398 self.info['hulls'] = {'right': right_hull, 

399 'left': left_hull, 

400 'all': hull} 

401 

402 self.info['hists'] = {'right': right_hist, 

403 'left': left_hist, 

404 'all': hist} 

405 

406 self.removeInfo('tmp_peaks') # Remove temp peaks from info dict to make it be re-calculated 

407 

408 print("Done") 

409 

410 def getPeaks(self): 

411 """ 

412 Finding Temp Peaks from background subtracted histogram. Temp peaks are just peaks which are found by algorithm. 

413 This might miss some peaks or detect to many peaks because of noise. These peaks will be managed again in the next step. 

414 Temp peaks will be kept in self.info["tmp_peaks"]. 

415 Once getting Temp peaks is done, the real peaks will be re-calculated, so self.info["peaks"] is deleted 

416 """ 

417 self.parent.statusPrint("Finding Peaks...") 

418 print("Finding Peaks...") 

419 if 'tmp_peaks' not in self.info: 

420 left_peaks = getPeaksFromHist(self.info['hulls']['left']) 

421 right_peaks = getPeaksFromHist(self.info['hulls']['right']) 

422 self.info['tmp_peaks'] = {'left': left_peaks, 'right': right_peaks} 

423 self.removeInfo('peaks') # Remove real peaks from info dict to make it be re-calculated 

424 

425 print("Done. Peaks are found : " + str(self.info['tmp_peaks'])) 

426 

427 def managePeaks(self): 

428 """ 

429 Getting real peaks from temp peaks. Temp peaks will be considered as real peaks if it in the Hexagonal Pattern location. 

430 Real peaks will be kept in self.info["peaks"]. 

431 Once getting real peaks is done, the fitting results will be re-calculated, so self.info["fit_results"] is deleted 

432 """ 

433 self.parent.statusPrint("Selecting Peaks...") 

434 print("Model Peaks are being selected...") 

435 if 'peaks' not in self.info: 

436 left_peaks = movePeaks(self.info['hulls']['left'], sorted(self.info['tmp_peaks']['left']), 5) 

437 right_peaks = movePeaks(self.info['hulls']['right'], sorted(self.info['tmp_peaks']['right']), 5) 

438 first_left, first_right = self.findFirstSymmetricPeaks(left_peaks, right_peaks) 

439 

440 self.removeInfo('fit_results') # Remove fit results from info dict to make it be re-calculated 

441 

442 if first_left is None: 

443 print('WARNING: '+str(self.filename) + '- no effective peaks detected. Model will not be fit.') 

444 return 

445 

446 self.hexagonalPattern(first_left, first_right, left_peaks, right_peaks) 

447 print("Done. Selected Peaks are" + str(self.info['peaks'])) 

448 

449 def findFirstSymmetricPeaks(self, left_peaks, right_peaks): 

450 """ 

451 Get first symatric peaks from left and right 

452 :param left_peaks: peaks on the left histogram (list) 

453 :param right_peaks: peaks on the right histogram (list) 

454 :return: first symmetric peaks, return None if there are no symmetric peaks 

455 """ 

456 dist_thres = 20 # Threshold for difference between distance of left and right peaks 

457 for lp in left_peaks: 

458 for rp in right_peaks: 

459 if abs(lp - rp) < dist_thres: 

460 return lp, rp 

461 return None, None 

462 

463 def hexagonalPattern(self, first_left, first_right, left_peaks, right_peaks): 

464 """ 

465 Set all peaks information after apply Hexagonal Pattern including .. 

466 - S values i.e S10, S11, S20, ... 

467 - Peaks 

468 - Missed Peaks 

469 

470 :param first_left: first symmetric peak on the left (int) 

471 :param first_right: first symmetric peak on the right (int) 

472 :param left_peaks: peaks on the left histogram (list) 

473 :param right_peaks: peaks on the right histogram (list) 

474 :return: 

475 """ 

476 if "nPeaks" not in self.info: 

477 self.info["nPeaks"] = 2 

478 allpeaks = {'left': [], 'right': []} 

479 missed_peaks = {'left': [], 'right': []} 

480 allpeaks['left'].append(first_left) 

481 allpeaks['right'].append(first_right) 

482 max_loc = min(len(self.info["hulls"]['right']), len(self.info["hulls"]['left'])) 

483 

484 # self.info['peak'] = [symP, peak] 

485 S10 = int((first_left + first_right) / 2.) 

486 self.info['S'] = [S10] 

487 

488 # Find other S values based on S10 and theta function 

489 SList = [] 

490 for i in range(1, int(self.info["nPeaks"])): 

491 s_pos = int(np.round(S10 * theta(i))) 

492 if s_pos > max_loc: 

493 break 

494 SList.append(s_pos) 

495 

496 maximum_nPeaks = 2 

497 # Find peaks correponding to S values 

498 for i, S in enumerate(SList): 

499 self.info['S'].append(S) 

500 if i + 2 > self.info["nPeaks"]: 

501 break 

502 SLeft = min(left_peaks, key=lambda p: abs(p - S)) 

503 SRight = min(right_peaks, key=lambda p: abs(p - S)) 

504 if abs(SLeft - S) > 10 or abs(SRight - S) > 10 or SLeft in allpeaks['left'] or SRight in allpeaks['right']: 

505 missed_peaks['left'].append(S) 

506 missed_peaks['right'].append(S) 

507 allpeaks['left'].append(S) 

508 allpeaks['right'].append(S) 

509 else: 

510 if SLeft not in allpeaks['left'] and SRight not in allpeaks['right']: 

511 allpeaks['left'].append(SLeft) 

512 allpeaks['right'].append(SRight) 

513 maximum_nPeaks += 2 

514 

515 allpeaks['left'] = allpeaks['left'][0:int(maximum_nPeaks / 2)] 

516 allpeaks['right'] = allpeaks['right'][0:int(maximum_nPeaks / 2)] 

517 self.info['S'] = self.info['S'][0:int(maximum_nPeaks / 2)] 

518 # self.info['S20'] = center[0]+int(S20) 

519 

520 self.info['peaks'] = allpeaks 

521 # self.info['nPeaks'] = maximum_nPeaks 

522 self.info['MissedPeaks'] = missed_peaks 

523 

524 def fitModel(self): 

525 """ 

526 Fit model to background subtracted histogram by using S10, peak location as initial guess 

527 Fit results will be kept in self.info["fit_results"]. 

528 """ 

529 self.parent.statusPrint("Fitting Model...") 

530 if 'peaks' not in self.info: 

531 # model cannot be fitted if peaks are not found 

532 return 

533 print("Fitting Model ...") 

534 if 'fit_results' not in self.info: 

535 left_hull = self.info['hulls']['left'] 

536 right_hull = self.info['hulls']['right'] 

537 left_peaks = self.info['peaks']['left'] 

538 right_peaks = self.info['peaks']['right'] 

539 

540 S = self.info['S'] 

541 

542 left_widths = self.getPeakWidths('left') 

543 right_widths = self.getPeakWidths('right') 

544 

545 right_height = [right_hull[right_peaks[i]] for i in range(len(S))] 

546 left_height = [left_hull[left_peaks[i]] for i in range(len(S))] 

547 

548 # init sigma D 

549 sigmaD = max(left_widths) 

550 

551 # initial sigma S 

552 sigmaS = self.sigmaS 

553 

554 # init areas 

555 left_areas = [left_height[i] * left_widths[i] * np.sqrt(2 * np.pi) for i in range(len(left_peaks))] 

556 right_areas = [right_height[i] * right_widths[i] * np.sqrt(2 * np.pi) for i in range(len(right_peaks))] 

557 

558 hull_hist = self.info['hulls']['all'] 

559 x = np.arange(0, len(hull_hist)) 

560 histNdarray = np.array(hull_hist) 

561 # total_area = sum(histNdarray) 

562 centerX = self.info['center'][0] 

563 

564 margin = 10. 

565 S0=0 

566 

567 # Add all fitting variables to params 

568 params = Parameters() 

569 params.add("centerX", centerX, min=centerX - margin, max=centerX + margin) 

570 params.add("S10", S[0], min=S[0] - margin, max=S[0] + margin) 

571 params.add("S0", S0, min=-0.001, max=0.001) 

572 

573 for (i, e) in enumerate(left_areas): 

574 params.add("left_area" + str(i + 1), max(e, 100), min=0) 

575 for (i, e) in enumerate(right_areas): 

576 params.add("right_area" + str(i + 1), max(e, 100), min=0) 

577 

578 left_sigmac = self.info['left_fix_sigmac'] if 'left_fix_sigmac' in self.info else self.info['left_sigmac'] 

579 

580 # Add independent variables to int_vars 

581 int_vars = { 

582 'x': x, 

583 'model': self.info["model"], 

584 'isSkeletal': self.info['isSkeletal'], 

585 'isExtraPeak': self.info['isExtraPeak'], 

586 # 'gamma': 1.0 

587 'extraGaussCenter': None, 

588 'extraGaussSig': None, 

589 'extraGaussArea': None, 

590 } 

591 

592 # Set initial parameters or independent parameters on each side 

593 for side in ['left', 'right']: 

594 

595 # If params are fixed, add them to int_vars 

596 if side+'_fix_sigmac' in self.info.keys(): 

597 int_vars[side+'_sigmac'] = self.info[side+'_fix_sigmac'] 

598 else: 

599 params.add(side+'_sigmac', self.info[side+'_sigmac'], min=-10., max=10) 

600 

601 if side+'_fix_sigmas' in self.info.keys(): 

602 int_vars[side+'_sigmas'] = self.info[side+'_fix_sigmas'] 

603 else: 

604 params.add(side+'_sigmas', sigmaS, min=-10., max=10) 

605 

606 if side+'_fix_sigmad' in self.info.keys(): 

607 int_vars[side+'_sigmad'] = self.info[side+'_fix_sigmad'] 

608 else: 

609 params.add(side+'_sigmad', sigmaD, min=0, max=30.) 

610 

611 if side+'_fix_gamma' in self.info.keys(): 

612 int_vars[side+'_gamma'] = self.info[side+'_fix_gamma'] 

613 else: 

614 init_gamma = np.sqrt(left_sigmac ** 2 + (sigmaD * theta(1)) ** 2 + (sigmaS * (theta(1) ** 2)) ** 2) 

615 params.add(side+'_gamma', init_gamma, min=0, max=init_gamma * 5.0 + 1.) 

616 

617 if self.info['isSkeletal']: 

618 self.skeletalVarsNotSet = False 

619 if side+'_fix_zline' in self.info: 

620 int_vars[side+'_zline'] = self.info[side+'_fix_zline'] 

621 else: 

622 init_z = 1.5 

623 params.add(side+'_zline', S[0] * init_z, min=S[0] * init_z - 10, max=S[0] * init_z + 10) 

624 

625 if side+'_fix_sigz' in self.info: 

626 int_vars[side+'_sigmaz'] = self.info[side+'_fix_sigz'] 

627 else: 

628 params.add(side+'_sigmaz', 8., min=0., max=20.) 

629 

630 if side+'_fix_intz' in self.info: 

631 int_vars[side+'_intz'] = self.info[side+'_fix_intz'] 

632 else: 

633 init_intz = max(left_areas[0] / 5., right_areas[0] / 5.) 

634 params.add(side+'_intz', init_intz, min=0, max=init_intz*4.+1.) 

635 

636 if side+'_fix_gammaz' in self.info: 

637 int_vars[side+'_gammaz'] = self.info[side+'_fix_gammaz'] 

638 else: 

639 params.add(side+'_gammaz', 8., min=-5., max=30.) 

640 

641 if self.info['isExtraPeak']: 

642 if side+'_fix_zline_EP' in self.info: 

643 int_vars[side+'_zline_EP'] = self.info[side+'_fix_zline_EP'] 

644 else: 

645 init_z_ep = 2.3 

646 params.add(side+'_zline_EP', S[0] * init_z_ep, min=S[0] * init_z - 10, max=S[0] * init_z_ep + 10) 

647 

648 if side+'_fix_sigz_EP' in self.info: 

649 int_vars[side+'_sigmaz_EP'] = self.info[side+'_fix_sigz_EP'] 

650 else: 

651 params.add(side+'_sigmaz_EP', 8., min=0., max=20.) 

652 

653 if side+'_fix_intz_EP' in self.info: 

654 int_vars[side+'_intz_EP'] = self.info[side+'_fix_intz_EP'] 

655 else: 

656 init_intz = max(left_areas[0] / 5., right_areas[0] / 5.) 

657 params.add(side+'_intz_EP', init_intz, min=0, max=init_intz*4.+1.) 

658 

659 if side+'_fix_gammaz_EP' in self.info: 

660 int_vars[side+'_gammaz_EP'] = self.info[side+'_fix_gammaz_EP'] 

661 else: 

662 params.add(side+'_gammaz_EP', 8., min=-5., max=30.) 

663 else: 

664 int_vars[side+'_zline_EP'] = 0 

665 int_vars[side+'_sigmaz_EP'] = 0 

666 int_vars[side+'_intz_EP'] = 0 

667 int_vars[side+'_gammaz_EP'] = 0 

668 

669 else: 

670 # set all z line variables as independent 

671 self.skeletalVarsNotSet = True 

672 int_vars[side+'_zline'] = 0 

673 int_vars[side+'_sigmaz'] = 0 

674 int_vars[side+'_intz'] = 0 

675 int_vars[side+'_gammaz'] = 0 

676 int_vars[side+'_zline_EP'] = 0 

677 int_vars[side+'_sigmaz_EP'] = 0 

678 int_vars[side+'_intz_EP'] = 0 

679 int_vars[side+'_gammaz_EP'] = 0 

680 

681 # Bias K 

682 if 'fix_k' in self.info: 

683 int_vars['k'] = self.info['fix_k'] 

684 else: 

685 params.add('k', 0., min=-1, max=max(histNdarray.max(),1.)) 

686 

687 # Fit model 

688 model = Model(cardiacFit, nan_policy='propagate', independent_vars=int_vars.keys()) 

689 min_err = 999999999 

690 final_result = None 

691 

692 # for method in ['leastsq', 'lbfgsb', 'powell', 'cg', 'slsqp', 'nelder', 'cobyla', 'tnc']: 

693 for method in ['leastsq']: 

694 # WARNING this fit function might give different results depending on the operating system 

695 result = model.fit(histNdarray, verbose = False, method=method, params=params, **int_vars) 

696 if result is not None: 

697 res = result.values 

698 res.update(int_vars) 

699 err = mean_squared_error(histNdarray, cardiacFit(**res)) 

700 if err < min_err: 

701 min_err = err 

702 final_result = result 

703 

704 if final_result is not None : 

705 fit_result = final_result.values 

706 fit_result.update(int_vars) 

707 fit_result["fiterror"] = 1. - r2_score(cardiacFit(**fit_result), histNdarray) 

708 del fit_result['x'] 

709 left_areas = [fit_result['left_area' + str(i + 1)] for i in range(len(left_peaks))] 

710 right_areas = [fit_result['right_area' + str(i + 1)] for i in range(len(right_peaks))] 

711 

712 if len(left_areas) < 2 or len(right_areas) < 2: 

713 return 

714 #### Get Ratio between I10 and I11 #### 

715 fit_result['left_ratio'] = 1.*(left_areas[1] / left_areas[0]) 

716 fit_result['right_ratio'] = 1.*(right_areas[1] / right_areas[0]) 

717 avg_area_ratios = (fit_result['left_ratio'] + fit_result['right_ratio']) / 2. 

718 fit_result['avg_ratio'] = avg_area_ratios 

719 fit_result['left_areas'] = left_areas 

720 fit_result['right_areas'] = right_areas 

721 

722 centerX = fit_result["centerX"] 

723 S10 = fit_result["S10"] 

724 S0 = fit_result["S0"] 

725 model_peaks = [centerX + S0 - S10 * theta(i) for i in range(len(left_peaks))] 

726 model_peaks.extend([centerX + S0 + S10 * theta(i) for i in range(len(right_peaks))]) 

727 

728 fit_result['model_peaks'] = sorted(model_peaks) 

729 all_S = [S10 * theta(i) for i in range(len(left_peaks))] 

730 fit_result['all_S'] = all_S 

731 

732 if "lambda_sdd" in self.info.keys(): 

733 fit_result['d10'] = self.info["lambda_sdd"] / fit_result['S10'] 

734 

735 self.info['fit_results'] = fit_result 

736 self.saveParamInfo(params, int_vars, fit_result) 

737 

738 if 'fit_results' in self.info: 

739 print("Done. Fitting Results : " + str(self.info['fit_results'])) 

740 if self.info['fit_results']['fiterror'] > 0.2: 

741 print("WARNING : High Fitting Error") 

742 else: 

743 print("Model cannot be fitted.") 

744 

745 def saveParamInfo(self, params, int_vars, fit_result): 

746 ''' 

747 Save information of parameter editor to the info 

748 :param params: this is non fixed parameters used while fitting 

749 :param int_vars: this is a dictionary of independent or fixed parameters used while fitting 

750 :param fit_result: this contains the fitted values of the parameters 

751 :return: 

752 ''' 

753 paramInfo={} 

754 for p in params.keys(): 

755 param = params[p] 

756 paramInfo[p] = {} 

757 paramInfo[p]['fixed'] = False 

758 paramInfo[p]['val'] = fit_result[p] 

759 paramInfo[p]['min'] = param.min 

760 paramInfo[p]['max'] = param.max 

761 

762 for p in int_vars.keys(): 

763 if p == 'x': 

764 continue 

765 paramInfo[p] = {} 

766 paramInfo[p]['fixed'] = True 

767 paramInfo[p]['val'] = int_vars[p] 

768 if not isinstance(int_vars[p], bool) and isinstance(int_vars[p], (float, int)): 

769 paramInfo[p]['min'] = int_vars[p] - 10 

770 paramInfo[p]['max'] = int_vars[p] + 10 

771 self.info['paramInfo'] = paramInfo 

772 

773 def processParameters(self, paramInfo): 

774 ''' 

775 Fit routine to fit parameters specified in parameter editor works differently compared to the usual fitting but uses the same underlying fitting functions 

776 :param paramInfo: information from parameter editor as dictionary 

777 :return: 

778 ''' 

779 self.parent.statusPrint("Fitting Model...") 

780 left_peaks = self.info['peaks']['left'] 

781 right_peaks = self.info['peaks']['right'] 

782 

783 hull_hist = self.info['hulls']['all'] 

784 x = np.arange(0, len(hull_hist)) 

785 histNdarray = np.array(hull_hist) 

786 

787 params = Parameters() 

788 int_vars = {} 

789 

790 for p in paramInfo.keys(): 

791 pinfo = paramInfo[p] 

792 if pinfo['fixed']: 

793 int_vars[p] = pinfo['val'] 

794 else: 

795 params.add(p, pinfo['val'], min=pinfo['min'], max=pinfo['max']) 

796 

797 # When using previous fit to update 

798 S = self.info['S'] 

799 

800 left_hull = self.info['hulls']['left'] 

801 right_hull = self.info['hulls']['right'] 

802 

803 left_widths = self.getPeakWidths('left') 

804 right_widths = self.getPeakWidths('right') 

805 

806 right_height = [right_hull[right_peaks[i]] for i in range(len(S))] 

807 left_height = [left_hull[left_peaks[i]] for i in range(len(S))] 

808 

809 # init areas 

810 left_areas = [left_height[i] * left_widths[i] * np.sqrt(2 * np.pi) for i in range(len(left_peaks))] 

811 right_areas = [right_height[i] * right_widths[i] * np.sqrt(2 * np.pi) for i in range(len(right_peaks))] 

812 

813 for (i, e) in enumerate(left_areas): 

814 if "left_area" + str(i + 1) not in paramInfo: 

815 params.add("left_area" + str(i + 1), max(e, 100), min=0) 

816 for (i, e) in enumerate(right_areas): 

817 if "right_area" + str(i + 1) not in paramInfo: 

818 params.add("right_area" + str(i + 1), max(e, 100), min=0) 

819 

820 if self.info['isSkeletal'] and self.skeletalVarsNotSet: 

821 # If zline is checked and use previous fit used, initialize the zline parameters if not set previously 

822 for side in ['left', 'right']: 

823 init_z = 1.5 

824 params.add(side + '_zline', S[0] * init_z, min=S[0] * init_z - 10, max=S[0] * init_z + 10) 

825 params.add(side + '_sigmaz', 8., min=0., max=20.) 

826 init_intz = max(left_areas[0] / 5., right_areas[0] / 5.) 

827 params.add(side + '_intz', init_intz, min=0, max=init_intz * 4. + 1.) 

828 params.add(side + '_gammaz', 8., min=-5., max=30.) 

829 if self.info['isExtraPeak']: 

830 init_z_ep = 2.3 

831 params.add(side + '_zline_EP', S[0] * init_z_ep, min=S[0] * init_z_ep - 10, max=S[0] * init_z_ep + 10) 

832 params.add(side + '_sigmaz_EP', 8., min=0., max=20.) 

833 init_intz_ep = max(left_areas[0] / 5., right_areas[0] / 5.) 

834 params.add(side + '_intz_EP', init_intz_ep, min=0, max=init_intz_ep * 4. + 1.) 

835 params.add(side + '_gammaz_EP', 8., min=-5., max=30.) 

836 

837 int_vars['x'] = x 

838 

839 # Fit model 

840 model = Model(cardiacFit, nan_policy='propagate', independent_vars=int_vars.keys()) 

841 min_err = 999999999 

842 final_result = None 

843 

844 # for method in ['leastsq', 'lbfgsb', 'powell', 'cg', 'slsqp', 'nelder', 'cobyla', 'tnc']: 

845 for method in ['leastsq']: 

846 result = model.fit(histNdarray, verbose=False, method=method, params=params, **int_vars) 

847 if result is not None: 

848 res = result.values 

849 res.update(int_vars) 

850 err = mean_squared_error(histNdarray, cardiacFit(**res)) 

851 if err < min_err: 

852 min_err = err 

853 final_result = result 

854 

855 if final_result is not None: 

856 fit_result = final_result.values 

857 fit_result.update(int_vars) 

858 fit_result["fiterror"] = 1. - r2_score(cardiacFit(**fit_result), histNdarray) 

859 del fit_result['x'] 

860 left_areas = [fit_result['left_area' + str(i + 1)] for i in range(len(left_peaks))] 

861 right_areas = [fit_result['right_area' + str(i + 1)] for i in range(len(right_peaks))] 

862 Speaks = [fit_result['Speak' + str(i+1)] if 'Speak' + str(i+1) in fit_result else 0 for i in range(max(len(left_peaks), len(right_peaks)))] 

863 

864 if len(left_areas) < 2 or len(right_areas) < 2: 

865 return 

866 #### Get Ratio between I10 and I11 #### 

867 fit_result['left_ratio'] = 1. * (left_areas[1] / left_areas[0]) 

868 fit_result['right_ratio'] = 1. * (right_areas[1] / right_areas[0]) 

869 avg_area_ratios = (fit_result['left_ratio'] + fit_result['right_ratio']) / 2. 

870 fit_result['avg_ratio'] = avg_area_ratios 

871 fit_result['left_areas'] = left_areas 

872 fit_result['right_areas'] = right_areas 

873 

874 centerX = fit_result["centerX"] 

875 S10 = fit_result["S10"] 

876 S0 = fit_result["S0"] 

877 model_peaks = [centerX + S0 - S10 * theta(i) + Speaks[i] for i in range(len(left_peaks))] 

878 model_peaks.extend([centerX + S0 + S10 * theta(i) + Speaks[i] for i in range(len(right_peaks))]) 

879 

880 fit_result['model_peaks'] = sorted(model_peaks) 

881 all_S = [S10 * theta(i) for i in range(len(left_peaks))] 

882 fit_result['all_S'] = all_S 

883 

884 if "lambda_sdd" in self.info.keys(): 

885 fit_result['d10'] = self.info["lambda_sdd"] / fit_result['S10'] 

886 

887 self.info['fit_results'] = fit_result 

888 self.saveParamInfo(params, int_vars, fit_result) 

889 self.saveCache() 

890 

891 if 'fit_results' in self.info: 

892 print("Done. Fitting Results : " + str(self.info['fit_results'])) 

893 if self.info['fit_results']['fiterror'] > 0.2: 

894 print("WARNING : High Fitting Error") 

895 else: 

896 print("Model cannot be fitted.") 

897 

898 def getPeakWidths(self, side): 

899 """ 

900 Get initial peaks' widths from histogram and peaks on speicific side 

901 :param side: "left" or "right" 

902 :return: peaks' widths list 

903 """ 

904 widthList = [] 

905 peaks = self.info['peaks'][side] 

906 missed = self.info['MissedPeaks'][side] 

907 hist = self.info['hulls'][side] 

908 for p in peaks: 

909 if p in missed: 

910 # if peak is missing, init width as 7 

911 widthList.append(7) 

912 else: 

913 i, j = self.findPeakRange(p, hist) 

914 r = max((j - i), 5) 

915 w = 1. * r / 2.3548 

916 widthList.append(w) 

917 return widthList 

918 

919 def findPeakRange(self, peak, hist): 

920 """ 

921 Find location of Full width at half maximum of peak 

922 :param peak: (int) 

923 :param hist: (list or numpy.array) 

924 :return: left and right point of Full width at half maximum 

925 """ 

926 i = j = peak 

927 midPeakHeight = hist[peak] / 2. 

928 thresh = np.mean(hist) 

929 while midPeakHeight <= hist[i] and hist[i] > thresh: 

930 i -= 1 

931 while midPeakHeight <= hist[j] and hist[j] > thresh: 

932 j += 1 

933 return i, j 

934 

935 def loadCache(self): 

936 """ 

937 Load info dict from cache. Cache file will be filename.info in folder "eq_cache" 

938 :return: cached info (dict) 

939 """ 

940 cache_path = fullPath(self.dir_path, "eq_cache") 

941 cache_file = fullPath(cache_path, self.filename + '.info') 

942 

943 if exists(cache_path) and isfile(cache_file): 

944 cinfo = pickle.load(open(cache_file, "rb")) 

945 if cinfo is not None: 

946 if cinfo['program_version'] == self.version: 

947 return cinfo 

948 print("Cache version " + cinfo['program_version'] + " did not match with Program version " + self.version) 

949 print("Invalidating cache and reprocessing the image") 

950 return None 

951 

952 def saveCache(self): 

953 """ 

954 Save info dict to cache. Cache file will be save as filename.info in folder "eq_cache" 

955 :return: - 

956 """ 

957 cache_path = fullPath(self.dir_path, "eq_cache") 

958 cache_file = fullPath(cache_path, self.filename + '.info') 

959 

960 # Create cache path if it does not exist 

961 if not exists(cache_path): 

962 makedirs(cache_path) 

963 

964 self.info['program_version'] = self.version 

965 pickle.dump(self.info, open(cache_file, "wb")) 

966 

967 def delCache(self): 

968 """ 

969 Delete cache 

970 :return: - 

971 """ 

972 cache_path = fullPath(self.dir_path, "eq_cache") 

973 cache_file = fullPath(cache_path, self.filename + '.info') 

974 if exists(cache_path) and isfile(cache_file): 

975 os.remove(cache_file) 

976 

977 def statusPrint(self, text): 

978 """ 

979 Print the text in the window or in the terminal depending on if we are using GUI or headless. 

980 :param text: text to print 

981 :return: - 

982 """ 

983 print(text) 

984 

985def cardiacFit(x, centerX, S0, S10, model, isSkeletal, isExtraPeak, k, 

986 left_sigmad, left_sigmas, left_sigmac, left_gamma, left_intz, left_sigmaz, left_zline, left_gammaz, 

987 left_zline_EP, left_sigmaz_EP, left_intz_EP, left_gammaz_EP, 

988 right_sigmad, right_sigmas, right_sigmac, right_gamma, right_intz, right_sigmaz, right_zline, right_gammaz, 

989 right_zline_EP, right_sigmaz_EP, right_intz_EP, right_gammaz_EP, 

990 extraGaussCenter, extraGaussSig, extraGaussArea, **kwargs): 

991 """ 

992 Using for fitting model by lmfit 

993 :param x: x range (list) 

994 :param centerX: x value of center (int) 

995 :param S10: (float) 

996 :param sigmad: (float) for each side 

997 :param sigmas: (float) for each side 

998 :param sigmac: (float) for each side 

999 :param model: "Voigt" or "Gaussian" 

1000 :param gamma: use for Voigt model (float) for each side 

1001 :param isSkeletal: (boolean) 

1002 :param k: linear background 

1003 :param intz: intensity of z line (float) for each side 

1004 :param sigmaz: sigma of z line (float) for each side 

1005 :param zline: center of z line (float) for each side 

1006 :param kwargs: area1, area2, ..., areaN as parameters or areas as a list 

1007 :return: 

1008 """ 

1009 if kwargs is not None: 

1010 if 'left_areas' in kwargs and 'right_areas' in kwargs: 

1011 left_areas = kwargs['left_areas'] 

1012 right_areas = kwargs['right_areas'] 

1013 else: 

1014 left_areas_dict = {} 

1015 right_areas_dict = {} 

1016 for kv in kwargs.items(): 

1017 key = kv[0] 

1018 value = kv[1] 

1019 if 'left_area' in key: 

1020 left_areas_dict[int(key[9:])] = value 

1021 if 'right_area' in key: 

1022 right_areas_dict[int(key[10:])] = value 

1023 

1024 left_areas = sorted(left_areas_dict.items(), key=lambda kv: kv[0]) 

1025 left_areas = [v for (_, v) in left_areas] 

1026 right_areas = sorted(right_areas_dict.items(), key=lambda kv: kv[0]) 

1027 right_areas = [v for (_, v) in right_areas] 

1028 

1029 speaks_dict = {} 

1030 for k1 in range(1, max(len(left_areas), len(right_areas)) + 1): 

1031 speaks_dict[k1] = 0 

1032 for kv in kwargs.items(): 

1033 key = kv[0] 

1034 value = kv[1] 

1035 if 'Speak' in key: 

1036 speaks_dict[int(key[5:])] = value 

1037 

1038 Speaks = sorted(speaks_dict.items(), key=lambda kv: kv[0]) 

1039 Speaks = [v for (_, v) in Speaks] 

1040 result = cardiacSide(model, 'left', x, centerX, S0, S10, left_sigmac, left_sigmad, left_sigmas, left_gamma, left_areas, Speaks, extraGaussCenter, extraGaussSig, extraGaussArea) 

1041 result += cardiacSide(model, 'right', x, centerX, S0, S10, right_sigmac, right_sigmad, right_sigmas, right_gamma, 

1042 right_areas, Speaks, extraGaussCenter, extraGaussSig, extraGaussArea) 

1043 if isSkeletal: 

1044 if model == "Gaussian": 

1045 mod = GaussianModel() 

1046 result += mod.eval(x=x, amplitude=left_intz, center=centerX + S0 - left_zline, 

1047 sigma=left_sigmaz) 

1048 result += mod.eval(x=x, amplitude=right_intz, center=centerX + S0 + right_zline, 

1049 sigma=right_sigmaz) 

1050 if isExtraPeak: 

1051 result += mod.eval(x=x, amplitude=left_intz_EP, center=centerX + S0 - left_zline_EP, 

1052 sigma=left_sigmaz_EP) 

1053 result += mod.eval(x=x, amplitude=right_intz_EP, center=centerX + S0 + right_zline_EP, 

1054 sigma=right_sigmaz_EP) 

1055 elif model == "Voigt": 

1056 mod = VoigtModel() 

1057 result += mod.eval(x=x, amplitude=left_intz, center=centerX + S0 + left_zline, 

1058 sigma=left_sigmaz, gamma=left_gammaz) 

1059 result += mod.eval(x=x, amplitude=right_intz, center=centerX + S0 - right_zline, 

1060 sigma=right_sigmaz, gamma=right_gammaz) 

1061 if isExtraPeak: 

1062 result += mod.eval(x=x, amplitude=left_intz_EP, center=centerX + S0 + left_zline_EP, 

1063 sigma=left_sigmaz_EP, gamma=left_gammaz_EP) 

1064 result += mod.eval(x=x, amplitude=right_intz_EP, center=centerX + S0 - right_zline_EP, 

1065 sigma=right_sigmaz_EP, gamma=right_gammaz_EP) 

1066 return result + k 

1067 return 0 

1068 

1069def cardiacSide(model, side, x, centerX, S0, S10, sigmac, sigmad, sigmas, 

1070 gamma, areas, Speak, extraGaussCenter, extraGaussSig, extraGaussArea): 

1071 """ 

1072 Using for fitting model by lmfit 

1073 """ 

1074 for (i, _) in enumerate(areas): 

1075 if side == 'left': 

1076 hk = i 

1077 p = centerX + S0 - S10 * theta(hk) + Speak[i] 

1078 else: 

1079 hk = i 

1080 p = centerX + S0 + S10 * theta(hk) + Speak[i] 

1081 

1082 sigmahk = np.sqrt(sigmac ** 2 + (sigmad * theta(hk)) ** 2 + (sigmas * (theta(hk) ** 2)) ** 2) 

1083 

1084 if model == "Gaussian": 

1085 mod = GaussianModel() 

1086 if i == 0: 

1087 result = mod.eval(x=x, amplitude=areas[i], center=p, sigma=sigmahk) 

1088 else: 

1089 result += mod.eval(x=x, amplitude=areas[i], center=p, sigma=sigmahk) 

1090 elif model == "Voigt": 

1091 mod = VoigtModel() 

1092 if i == 0: 

1093 result = mod.eval(x=x, amplitude=areas[i], center=p, sigma=sigmahk, gamma=gamma) 

1094 else: 

1095 result += mod.eval(x=x, amplitude=areas[i], center=p, sigma=sigmahk, gamma=gamma) 

1096 

1097 if extraGaussSig is not None and extraGaussCenter is not None and extraGaussCenter != 'None' and extraGaussCenter != 'None': 

1098 mod = GaussianModel() 

1099 result += mod.eval(x=x, amplitude=extraGaussArea, center=extraGaussCenter, sigma=extraGaussSig) 

1100 

1101 return result 

1102 

1103def cardiacFit_old(x, centerX, S10, sigmad, sigmas, sigmac, model, gamma, isSkeletal, intz, sigmaz, zline, gammaz, **kwargs): 

1104 """ 

1105 Using for fitting model by lmfit 

1106 :param x: x range (list) 

1107 :param centerX: x value of center (int) 

1108 :param S10: (float) 

1109 :param sigmad: (float) 

1110 :param sigmas: (float) 

1111 :param sigmac: (float) 

1112 :param model: "Voigt" or "Gaussian" 

1113 :param gamma: use for Voigt model (float) 

1114 :param isSkeletal: (boolean) 

1115 :param intz: intensity of z line 

1116 :param sigmaz: sigma of z line 

1117 :param zline: center of z line 

1118 :param kwargs: area1, area2, ..., areaN as parameters or areas as a list 

1119 :return: 

1120 """ 

1121 if kwargs is not None: 

1122 result = None 

1123 if 'areas' in kwargs: 

1124 areas = kwargs['areas'] 

1125 else: 

1126 areas_dict = {} 

1127 for kv in kwargs.items(): 

1128 if 'area' in kv[0]: 

1129 areas_dict[int(kv[0][4:])] = kv[1] 

1130 

1131 areas = sorted(areas_dict.items(), key=lambda kv: kv[0]) 

1132 areas = [v for (k, v) in areas] 

1133 

1134 nPeaks = int(len(areas)/2) 

1135 

1136 for (i, _) in enumerate(areas): 

1137 if i < nPeaks: 

1138 hk = (nPeaks) - i - 1 

1139 p = centerX - S10 * theta(hk) 

1140 else: 

1141 hk = i - (nPeaks) 

1142 p = centerX + S10 * theta(hk) 

1143 

1144 sigmahk = np.sqrt(sigmac ** 2 + (sigmad * theta(hk)) ** 2 + (sigmas * (theta(hk) ** 2)) ** 2) 

1145 

1146 if model == "Gaussian": 

1147 mod = GaussianModel() 

1148 if i == 0: 

1149 result = mod.eval(x=x, amplitude=areas[i], center=p, sigma=sigmahk) 

1150 else: 

1151 result += mod.eval(x=x, amplitude=areas[i], center=p, sigma=sigmahk) 

1152 

1153 elif model == "Voigt": 

1154 mod = VoigtModel() 

1155 if i == 0: 

1156 result = mod.eval(x=x, amplitude=areas[i], center=p, sigma=sigmahk, gamma=gamma) 

1157 else: 

1158 result += mod.eval(x=x, amplitude=areas[i], center=p, sigma=sigmahk, gamma=gamma) 

1159 

1160 if isSkeletal: 

1161 if model == "Gaussian": 

1162 mod = GaussianModel() 

1163 result += mod.eval(x=x, amplitude=intz, center=centerX + zline, 

1164 sigma=sigmaz) 

1165 result += mod.eval(x=x, amplitude=intz, center=centerX - zline, 

1166 sigma=sigmaz) 

1167 elif model == "Voigt": 

1168 mod = VoigtModel() 

1169 result += mod.eval(x=x, amplitude=intz, center=centerX + zline, 

1170 sigma=sigmaz, gamma=gammaz) 

1171 result += mod.eval(x=x, amplitude=intz, center=centerX - zline, 

1172 sigma=sigmaz, gamma=gammaz) 

1173 return result 

1174 return 0 

1175 

1176def getCardiacGraph(x, fit_results): 

1177 """ 

1178 Give the cardiac graph based on the fit results. 

1179 :param fit_results: fit results 

1180 """ 

1181 plot_params = { 

1182 'centerX': fit_results['centerX'], 

1183 'S0': fit_results["S0"], 

1184 'S10': fit_results['S10'], 

1185 'model': fit_results['model'], 

1186 'isSkeletal': fit_results['isSkeletal'], 

1187 'isExtraPeak': fit_results['isExtraPeak'], 

1188 'left_areas': fit_results['left_areas'], 

1189 'right_areas': fit_results['right_areas'], 

1190 

1191 'left_sigmac': fit_results['left_sigmac'], 

1192 'left_sigmad': fit_results['left_sigmad'], 

1193 'left_sigmas': fit_results['left_sigmas'], 

1194 'left_gamma': fit_results['left_gamma'], 

1195 'left_zline': fit_results['left_zline'], 

1196 'left_sigmaz': fit_results['left_sigmaz'], 

1197 'left_intz': fit_results['left_intz'], 

1198 'left_gammaz': fit_results['left_gammaz'], 

1199 'left_zline_EP': fit_results['left_zline_EP'], 

1200 'left_sigmaz_EP': fit_results['left_sigmaz_EP'], 

1201 'left_intz_EP': fit_results['left_intz_EP'], 

1202 'left_gammaz_EP': fit_results['left_gammaz_EP'], 

1203 

1204 'right_sigmac': fit_results['right_sigmac'], 

1205 'right_sigmad': fit_results['right_sigmad'], 

1206 'right_sigmas': fit_results['right_sigmas'], 

1207 'right_gamma': fit_results['right_gamma'], 

1208 'right_zline': fit_results['right_zline'], 

1209 'right_sigmaz': fit_results['right_sigmaz'], 

1210 'right_intz': fit_results['right_intz'], 

1211 'right_gammaz': fit_results['right_gammaz'], 

1212 'right_zline_EP': fit_results['right_zline_EP'], 

1213 'right_sigmaz_EP': fit_results['right_sigmaz_EP'], 

1214 'right_intz_EP': fit_results['right_intz_EP'], 

1215 'right_gammaz_EP': fit_results['right_gammaz_EP'], 

1216 

1217 'k': fit_results['k'], 

1218 'extraGaussCenter': fit_results['extraGaussCenter'], 

1219 'extraGaussSig': fit_results['extraGaussSig'], 

1220 'extraGaussArea': fit_results['extraGaussArea'], 

1221 } 

1222 if 'Speaks' in fit_results: 

1223 plot_params['Speaks'] = fit_results['Speaks'] 

1224 return cardiacFit(x=x, **plot_params) 

1225 

1226def theta(h, k=-1): 

1227 """ 

1228 Theta value that used for distance calculation. S10 * theta 

1229 :param h: index 1 

1230 :param k: index 2, if k is not specified, h will be index count by peak from center 

1231 :return: theta value 

1232 """ 

1233 if k == -1: 

1234 # add more coords if necessary 

1235 SList = [(1, 0), (1, 1), (2, 0), (2, 1), (3, 0), (2, 2), (3, 1), (4, 0), (3, 2), (4, 1), 

1236 (5, 0), (3, 3), (4, 2), (5, 1), (6, 0), (4, 3), (5, 2), (6, 1), (4, 4), (5, 3), 

1237 (7, 0), (6, 2), (7, 1), (5, 4), (6, 3), (8, 0), (7, 2), (8, 1), (5, 5), (6, 4), 

1238 (7, 3), (8, 2), (6, 5), (7, 4), (8, 3), (6, 6), (7, 5), (8, 4), (7, 6), (8, 5), 

1239 (7, 7), (8, 6), (8, 7), (8, 8)] 

1240 

1241 hk = SList[h] 

1242 return theta(hk[0], hk[1]) 

1243 return np.sqrt(float(h ** 2 + k ** 2 + h * k))