Coverage for ui/QuadrantFoldingh.py: 60%
245 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 sys
30import json
31from os.path import splitext
32import traceback
33import fabio
34import pandas as pd
35from musclex import __version__
36try:
37 from ..utils.file_manager import *
38 from ..utils.image_processor import *
39 from ..modules.QuadrantFolder import QuadrantFolder
40 from ..csv_manager.QF_CSVManager import QF_CSVManager
41 from .pyqt_utils import *
42except: # for coverage
43 from utils.file_manager import *
44 from utils.image_processor import *
45 from modules.QuadrantFolder import QuadrantFolder
46 from csv_manager.QF_CSVManager import QF_CSVManager
47 from ui.pyqt_utils import *
49class QuadrantFoldingh:
50 """
51 Window displaying all information of a selected image.
52 This window contains 2 tabs : image, and result
53 """
54 def __init__(self, filename, inputsettings, delcache, settingspath='musclex/settings/qfsettings.json'):
55 """
56 :param filename: selected file name
57 :param inputsettings: flag for input setting file
58 :param delcache: flag for deleting cache
59 :param settingspath: setting file directory
60 """
61 self.version = __version__
62 self.quadFold = None # QuadrantFolder object
63 self.img_zoom = None # zoom location of original image (x,y range)
64 self.default_img_zoom = None # default zoom calculated after processing image
65 self.default_result_img_zoom = None # default result image zoom calculated after processing image
66 self.result_zoom = None # zoom location of result image (x,y range)
67 self.function = None # current active function
68 self.updated = {'img': False, 'result': False} # update state of 2 tabs
69 self.BGImages = []
70 self.calSettings = None
71 self.ignoreFolds = set()
72 self.csv_bg = None
73 self.orientationModel = None
74 self.modeOrientation = None
75 self.newImgDimension = None
77 self.dir_path, self.imgList, self.currentFileNumber, self.fileList, self.ext = getImgFiles(str(filename))
78 self.numberOfFiles = 0
79 if len(self.imgList) == 0:
80 self.inputerror()
81 return
82 self.inputsettings=inputsettings
83 self.delcache=delcache
84 self.settingspath=settingspath
85 fileName = self.imgList[self.currentFileNumber]
86 self.csvManager = QF_CSVManager(self.dir_path)
87 self.quadFold = QuadrantFolder(self.dir_path, fileName, self, self.fileList, self.ext)
89 if self.inputsettings:
90 self.setCalibrationImage()
91 self.processImage()
92 else:
93 self.onImageChanged()
95 def inputerror(self):
96 """
97 Display input error to screen
98 """
99 print('Invalid Input')
100 print("Please select non empty failedcases.txt or an image\n\n")
102 def ableToProcess(self):
103 """
104 Check if image can be processed
105 """
106 return self.quadFold is not None
108 def deleteInfo(self, delList):
109 """
110 Remove input keys from info dict of current QuadrantFolder object
111 :param delList: list of keys
112 """
113 if self.ableToProcess():
114 for inf in delList:
115 if inf in self.quadFold.info.keys():
116 del self.quadFold.info[inf]
118 def onImageChanged(self):
119 """
120 Need to be called when image is change i.e. to the next image.
121 This will create a new QuadrantFolder object for the new image and syncUI if cache is available
122 Process the new image if there's no cache.
123 """
124 fileName = self.imgList[self.currentFileNumber]
125 file=fileName+'.info'
126 cache_path = os.path.join(self.dir_path, "qf_cache", file)
127 cache_exist=os.path.isfile(cache_path)
128 if self.delcache:
129 if os.path.isfile(cache_path):
130 os.remove(cache_path)
132 if 'ignore_folds' in self.quadFold.info:
133 self.ignoreFolds = self.quadFold.info['ignore_folds']
135 # self.updateParams()
136 self.markFixedInfo(self.quadFold.info)
137 print("Settings in onImageChange before update")
138 print(self.calSettings)
140 # Process new image
141 self.processImage()
143 print('---------------------------------------------------')
145 if self.inputsettings and cache_exist and not self.delcache:
146 print('cache exists, provided setting file was not used ')
147 elif self.inputsettings and (not cache_exist or self.delcache):
148 print('setting file provided and used for fitting')
149 elif not self.inputsettings and cache_exist and not self.delcache:
150 print('cache exist, no fitting was performed')
151 elif not self.inputsettings and (self.delcache or not cache_exist):
152 print('fitting with default settings')
154 print('---------------------------------------------------')
156 def markFixedInfo(self, currentInfo):
157 """
158 Deleting the center for appropriate recalculation
159 """
160 if 'center' in currentInfo:
161 del currentInfo['center']
163 if 'calib_center' in currentInfo:
164 del currentInfo['calib_center']
166 def getExtentAndCenter(self):
167 """
168 Give the extent and center of the image
169 """
170 if self.quadFold is None:
171 return [0,0], (0,0)
172 if self.quadFold.orig_image_center is None:
173 self.quadFold.findCenter()
174 self.statusPrint("Done.")
175 if 'calib_center' in self.quadFold.info:
176 center = self.quadFold.info['calib_center']
177 elif 'manual_center' in self.quadFold.info:
178 center = self.quadFold.info['manual_center']
179 else:
180 center = self.quadFold.orig_image_center
182 extent = [self.quadFold.info['center'][0] - center[0], self.quadFold.info['center'][1] - center[1]]
183 return extent, center
185 def processImage(self):
186 """
187 Process Image by getting all flags and call process() of QuadrantFolder object
188 Then, write data and update UI
189 """
190 if self.ableToProcess():
191 flags = self.getFlags()
192 print("Flags in processImage:")
193 print(flags)
194 try:
195 self.quadFold.process(flags)
196 except Exception:
197 print('Unexpected error')
198 msg = 'Please report the problem with error message below and the input image\n\n'
199 msg += "Error : " + str(sys.exc_info()[0]) + '\n\n' + str(traceback.format_exc())
200 print(msg)
201 raise
203 self.updateParams()
204 self.csvManager.writeNewData(self.quadFold)
206 # Save result to folder qf_results
207 if 'resultImg' in self.quadFold.imgCache:
208 result_path = fullPath(self.dir_path, 'qf_results')
209 createFolder(result_path)
211 result_file = str(join(result_path, self.imgList[self.currentFileNumber]))
212 result_file, _ = splitext(result_file)
213 img = self.quadFold.imgCache['resultImg']
215 # if self.quadFold.info['imgType'] == 'float32':
216 # img = get16bitImage(img)
217 # else:
218 # img = img.astype(self.quadFold.info['imgType'])
219 img = img.astype("float32")
220 result_file += '_folded.tif'
221 # metadata = json.dumps([True, self.quadFold.initImg.shape])
222 # imsave(result_file, img, description=metadata)
223 fabio.tifimage.tifimage(data=img).write(result_file)
224 self.saveBackground()
226 def saveBackground(self):
227 """
228 Save the background image in bg folder
229 """
230 info = self.quadFold.info
231 result = self.quadFold.imgCache["BgSubFold"]
232 avg_fold = info["avg_fold"]
233 background = avg_fold-result
234 resultImg = self.quadFold.makeFullImage(background)
236 if 'rotate' in info and info['rotate']:
237 resultImg = np.rot90(resultImg)
239 filename = self.imgList[self.currentFileNumber]
240 bg_path = fullPath(self.dir_path, "qf_results/bg")
241 result_path = fullPath(bg_path, filename + ".bg.tif")
243 # create bg folder
244 createFolder(bg_path)
245 resultImg = resultImg.astype("float32")
246 # imsave(result_path, resultImg)
247 fabio.tifimage.tifimage(data=resultImg).write(result_path)
249 total_inten = np.sum(resultImg)
250 csv_path = join(bg_path, 'background_sum.csv')
251 if self.csv_bg is None:
252 # create csv file to save total intensity for background
253 if exists(csv_path):
254 self.csv_bg = pd.read_csv(csv_path)
255 else:
256 self.csv_bg = pd.DataFrame(columns=['Name', 'Sum'])
257 self.csv_bg = self.csv_bg.set_index('Name')
259 if filename in self.csv_bg.index:
260 self.csv_bg = self.csv_bg.drop(index=filename)
262 self.csv_bg.loc[filename] = pd.Series({'Sum':total_inten})
263 self.csv_bg.to_csv(csv_path)
265 def updateParams(self):
266 """
267 Update the parameters
268 """
269 info = self.quadFold.info
270 if 'orientation_model' in info:
271 self.orientationModel = info['orientation_model']
272 if self.calSettings is not None and 'center' in self.calSettings and 'calib_center' in info:
273 # Update cal settings center with the corresponding coordinate in original (or initial) image
274 # so that it persists correctly on moving to next image
275 self.calSettings['center'] = info['calib_center']
276 self.getExtentAndCenter()
278 def getFlags(self):
279 """
280 Get all flags for QuadrantFolder process() from widgets
281 :return: flags (dict)
282 """
283 flags = {}
285 flags['orientation_model'] = self.orientationModel
286 flags['ignore_folds'] = self.ignoreFolds
287 # default values, same as QuadrantFoldingGUI.py default
288 flags['bgsub'] = 'None'
289 flags["cirmin"] = 0.0
290 flags["cirmax"] = 25.0
291 flags['win_size_x'] = 10
292 flags['win_size_y'] = 10
293 flags['win_sep_x'] = 10
294 flags['win_sep_y'] = 10
295 flags["bin_theta"] = 30
296 flags['radial_bin'] = 10
297 flags['smooth'] = 0.1
298 flags['tension'] = 1.0
299 flags["tophat1"] = 5
300 flags['tophat2'] = 20
301 flags['mask_thres'] = getMaskThreshold(self.quadFold.orig_img, self.quadFold.img_type)
302 flags['sigmoid'] = 0.1
303 flags['fwhm'] = 10
304 flags['boxcar_x'] = 10
305 flags['boxcar_y'] = 10
306 flags['cycles'] = 5
307 flags['blank_mask'] = False
308 flags['rotate'] = False
310 if 'center' in flags:
311 flags.pop('center')
313 return flags
315 def processFolder(self):
316 """
317 Process the folder selected
318 """
319 fileList = os.listdir(self.dir_path)
320 self.imgList = []
321 for f in fileList:
322 if isImg(fullPath(self.dir_path, f)):
323 self.imgList.append(f)
325 self.imgList.sort()
326 self.numberOfFiles = len(self.imgList)
328 flags = self.getFlags()
329 print("\nCurrent Settings")
330 if 'center' in flags:
331 print("\n - Center : " + str(flags["center"]))
332 if len(self.ignoreFolds) > 0:
333 print("\n - Ignore Folds : " + str(list(self.ignoreFolds)))
334 print("\n - Mask Threshold : " + str(flags["mask_thres"]))
336 if flags['bgsub'] != 'None':
337 if 'fixed_rmin' in flags:
338 print("\n - R-min : " + str(flags["fixed_rmin"]))
339 print("\n - R-max : " + str(flags["fixed_rmax"]))
341 if flags['bgsub'] in ['Circularly-symmetric', 'Roving Window']:
342 print("\n - Pixel Range (Percentage) : " + str(flags["cirmin"]) + "% - "+str(flags["cirmax"])+"%")
344 if flags['bgsub'] == 'Circularly-symmetric':
345 print("\n - Radial Bin : " + str(flags["radial_bin"]))
346 print("\n - Smooth : " + str(flags["smooth"]))
347 elif flags['bgsub'] == 'White-top-hats':
348 print("\n - Tophat (inside R-max) : " + str(flags["tophat1"]))
349 elif flags['bgsub'] == 'Smoothed-Gaussian':
350 print("\n - FWHM : " + str(flags["fwhm"]))
351 print("\n - Number of cycle : " + str(flags["cycles"]))
352 elif flags['bgsub'] == 'Smoothed-BoxCar':
353 print("\n - Box car width : " + str(flags["boxcar_x"]))
354 print("\n - Box car height : " + str(flags["boxcar_y"]))
355 print("\n - Number of cycle : " + str(flags["cycles"]))
357 print("\n - Tophat (outside R-max) : " + str(flags["tophat2"]))
358 print("\n - Merge Gradient : " + str(flags["sigmoid"]))
360 print('\n\nAre you sure you want to process ' + str(self.numberOfFiles) + ' image(s) in this Folder? \nThis might take a long time.')
362 def statusPrint(self, text):
363 """
364 Print the text in the window or in the terminal depending on if we are using GUI or headless.
365 :param text: text to print
366 :return: -
367 """
368 print(text)
370 def setCalibrationImage(self):
371 """
372 Popup Calibration Settings window, if there's calibration settings in cache or force to open
373 :param force: force to popup the window
374 :return: True if calibration set, False otherwise
375 """
376 settingspath=self.settingspath
377 if self.inputsettings:
378 try:
379 with open(settingspath, 'r') as f:
380 self.calSettings = json.load(f)
381 except Exception:
382 print("Can't load setting file")
383 self.inputsettings=False
384 self.calSettings = {"center": [1030.22, 1015.58], "radius": 686, "silverB": 5.83803, "type": "img"}
385 self.quadFold.info['calib_center'] = self.calSettings['center']
386 if 'manual_center' in self.quadFold.info:
387 del self.quadFold.info['manual_center']
388 if 'center' in self.quadFold.info:
389 del self.quadFold.info['center']
390 else:
391 if self.quadFold is not None and 'calib_center' in self.quadFold.info:
392 del self.quadFold.info['calib_center']
393 if self.quadFold is not None and 'center' in self.quadFold.info:
394 del self.quadFold.info['center']