Coverage for ui/QuadrantFoldingh.py: 60%

245 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 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 * 

48 

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 

76 

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) 

88 

89 if self.inputsettings: 

90 self.setCalibrationImage() 

91 self.processImage() 

92 else: 

93 self.onImageChanged() 

94 

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

101 

102 def ableToProcess(self): 

103 """ 

104 Check if image can be processed 

105 """ 

106 return self.quadFold is not None 

107 

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] 

117 

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) 

131 

132 if 'ignore_folds' in self.quadFold.info: 

133 self.ignoreFolds = self.quadFold.info['ignore_folds'] 

134 

135 # self.updateParams() 

136 self.markFixedInfo(self.quadFold.info) 

137 print("Settings in onImageChange before update") 

138 print(self.calSettings) 

139 

140 # Process new image 

141 self.processImage() 

142 

143 print('---------------------------------------------------') 

144 

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

153 

154 print('---------------------------------------------------') 

155 

156 def markFixedInfo(self, currentInfo): 

157 """ 

158 Deleting the center for appropriate recalculation 

159 """ 

160 if 'center' in currentInfo: 

161 del currentInfo['center'] 

162 

163 if 'calib_center' in currentInfo: 

164 del currentInfo['calib_center'] 

165 

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 

181 

182 extent = [self.quadFold.info['center'][0] - center[0], self.quadFold.info['center'][1] - center[1]] 

183 return extent, center 

184 

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 

202 

203 self.updateParams() 

204 self.csvManager.writeNewData(self.quadFold) 

205 

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) 

210 

211 result_file = str(join(result_path, self.imgList[self.currentFileNumber])) 

212 result_file, _ = splitext(result_file) 

213 img = self.quadFold.imgCache['resultImg'] 

214 

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

225 

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) 

235 

236 if 'rotate' in info and info['rotate']: 

237 resultImg = np.rot90(resultImg) 

238 

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

242 

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) 

248 

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

258 

259 if filename in self.csv_bg.index: 

260 self.csv_bg = self.csv_bg.drop(index=filename) 

261 

262 self.csv_bg.loc[filename] = pd.Series({'Sum':total_inten}) 

263 self.csv_bg.to_csv(csv_path) 

264 

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

277 

278 def getFlags(self): 

279 """ 

280 Get all flags for QuadrantFolder process() from widgets 

281 :return: flags (dict) 

282 """ 

283 flags = {} 

284 

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 

309 

310 if 'center' in flags: 

311 flags.pop('center') 

312 

313 return flags 

314 

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) 

324 

325 self.imgList.sort() 

326 self.numberOfFiles = len(self.imgList) 

327 

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

335 

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

340 

341 if flags['bgsub'] in ['Circularly-symmetric', 'Roving Window']: 

342 print("\n - Pixel Range (Percentage) : " + str(flags["cirmin"]) + "% - "+str(flags["cirmax"])+"%") 

343 

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

356 

357 print("\n - Tophat (outside R-max) : " + str(flags["tophat2"])) 

358 print("\n - Merge Gradient : " + str(flags["sigmoid"])) 

359 

360 print('\n\nAre you sure you want to process ' + str(self.numberOfFiles) + ' image(s) in this Folder? \nThis might take a long time.') 

361 

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) 

369 

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