Coverage for modules/EquatorImage.py: 63%
734 statements
« prev ^ index » next coverage.py v7.0.4, created at 2023-01-10 09:27 -0600
« prev ^ index » next coverage.py v7.0.4, created at 2023-01-10 09:27 -0600
1"""
2Copyright 1999 Illinois Institute of Technology
4Permission is hereby granted, free of charge, to any person obtaining
5a copy of this software and associated documentation files (the
6"Software"), to deal in the Software without restriction, including
7without limitation the rights to use, copy, modify, merge, publish,
8distribute, sublicense, and/or sell copies of the Software, and to
9permit persons to whom the Software is furnished to do so, subject to
10the following conditions:
12The above copyright notice and this permission notice shall be
13included in all copies or substantial portions of the Software.
15THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18IN NO EVENT SHALL ILLINOIS INSTITUTE OF TECHNOLOGY BE LIABLE FOR ANY
19CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23Except as contained in this notice, the name of Illinois Institute
24of Technology shall not be used in advertising or otherwise to promote
25the sale, use or other dealings in this Software without prior written
26authorization from Illinois Institute of Technology.
27"""
29import 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 *
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"
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")
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
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("")
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]
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)
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
177 self.image = img
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']))
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...")
207 if self.quadrant_folded:
208 print("Quadrant folded image: ignoring rotation angle computation")
209 self.info['rotationAngle'] = 0
210 return
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
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
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"]
228 print("Done. Rotation Angle is " + str(self.info['rotationAngle']))
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
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']
246 if img.shape == (1043, 981):
247 det = "pilatus1m"
248 else:
249 det = "agilent_titan"
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
259 print("Done. R-min is " + str(self.info['rmin']))
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
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[
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
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]
290 return self.rotated_img[3]
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
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
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
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)
339 self.removeInfo('hist') # Remove histograms from info dict to make it be re-calculated
341 print("Done. Integrated Area is " + str(self.info['int_area']))
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
356 print("Done.")
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
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
395 hull = copy.copy(list(left_hull[::-1]))
396 hull.extend(list(right_hull[:]))
398 self.info['hulls'] = {'right': right_hull,
399 'left': left_hull,
400 'all': hull}
402 self.info['hists'] = {'right': right_hist,
403 'left': left_hist,
404 'all': hist}
406 self.removeInfo('tmp_peaks') # Remove temp peaks from info dict to make it be re-calculated
408 print("Done")
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
425 print("Done. Peaks are found : " + str(self.info['tmp_peaks']))
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)
440 self.removeInfo('fit_results') # Remove fit results from info dict to make it be re-calculated
442 if first_left is None:
443 print('WARNING: '+str(self.filename) + '- no effective peaks detected. Model will not be fit.')
444 return
446 self.hexagonalPattern(first_left, first_right, left_peaks, right_peaks)
447 print("Done. Selected Peaks are" + str(self.info['peaks']))
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
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
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']))
484 # self.info['peak'] = [symP, peak]
485 S10 = int((first_left + first_right) / 2.)
486 self.info['S'] = [S10]
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)
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
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)
520 self.info['peaks'] = allpeaks
521 # self.info['nPeaks'] = maximum_nPeaks
522 self.info['MissedPeaks'] = missed_peaks
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']
540 S = self.info['S']
542 left_widths = self.getPeakWidths('left')
543 right_widths = self.getPeakWidths('right')
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))]
548 # init sigma D
549 sigmaD = max(left_widths)
551 # initial sigma S
552 sigmaS = self.sigmaS
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))]
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]
564 margin = 10.
565 S0=0
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)
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)
578 left_sigmac = self.info['left_fix_sigmac'] if 'left_fix_sigmac' in self.info else self.info['left_sigmac']
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 }
592 # Set initial parameters or independent parameters on each side
593 for side in ['left', 'right']:
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)
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)
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.)
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.)
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)
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.)
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.)
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.)
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)
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.)
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.)
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
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
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.))
687 # Fit model
688 model = Model(cardiacFit, nan_policy='propagate', independent_vars=int_vars.keys())
689 min_err = 999999999
690 final_result = None
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
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))]
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
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))])
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
732 if "lambda_sdd" in self.info.keys():
733 fit_result['d10'] = self.info["lambda_sdd"] / fit_result['S10']
735 self.info['fit_results'] = fit_result
736 self.saveParamInfo(params, int_vars, fit_result)
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.")
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
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
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']
783 hull_hist = self.info['hulls']['all']
784 x = np.arange(0, len(hull_hist))
785 histNdarray = np.array(hull_hist)
787 params = Parameters()
788 int_vars = {}
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'])
797 # When using previous fit to update
798 S = self.info['S']
800 left_hull = self.info['hulls']['left']
801 right_hull = self.info['hulls']['right']
803 left_widths = self.getPeakWidths('left')
804 right_widths = self.getPeakWidths('right')
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))]
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))]
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)
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.)
837 int_vars['x'] = x
839 # Fit model
840 model = Model(cardiacFit, nan_policy='propagate', independent_vars=int_vars.keys())
841 min_err = 999999999
842 final_result = None
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
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)))]
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
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))])
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
884 if "lambda_sdd" in self.info.keys():
885 fit_result['d10'] = self.info["lambda_sdd"] / fit_result['S10']
887 self.info['fit_results'] = fit_result
888 self.saveParamInfo(params, int_vars, fit_result)
889 self.saveCache()
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.")
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
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
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')
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
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')
960 # Create cache path if it does not exist
961 if not exists(cache_path):
962 makedirs(cache_path)
964 self.info['program_version'] = self.version
965 pickle.dump(self.info, open(cache_file, "wb"))
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)
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)
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
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]
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
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
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]
1082 sigmahk = np.sqrt(sigmac ** 2 + (sigmad * theta(hk)) ** 2 + (sigmas * (theta(hk) ** 2)) ** 2)
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)
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)
1101 return result
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]
1131 areas = sorted(areas_dict.items(), key=lambda kv: kv[0])
1132 areas = [v for (k, v) in areas]
1134 nPeaks = int(len(areas)/2)
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)
1144 sigmahk = np.sqrt(sigmac ** 2 + (sigmad * theta(hk)) ** 2 + (sigmas * (theta(hk) ** 2)) ** 2)
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)
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)
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
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'],
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'],
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'],
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)
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)]
1241 hk = SList[h]
1242 return theta(hk[0], hk[1])
1243 return np.sqrt(float(h ** 2 + k ** 2 + h * k))