Coverage for src / sdynpy / modal / sdynpy_ccmif.py: 12%

461 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-03-11 16:22 +0000

1# -*- coding: utf-8 -*- 

2""" 

3Graphical tool for selecting final mode sets from single-reference data 

4""" 

5""" 

6Copyright 2022 National Technology & Engineering Solutions of Sandia, 

7LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. 

8Government retains certain rights in this software. 

9 

10This program is free software: you can redistribute it and/or modify 

11it under the terms of the GNU General Public License as published by 

12the Free Software Foundation, either version 3 of the License, or 

13(at your option) any later version. 

14 

15This program is distributed in the hope that it will be useful, 

16but WITHOUT ANY WARRANTY; without even the implied warranty of 

17MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

18GNU General Public License for more details. 

19 

20You should have received a copy of the GNU General Public License 

21along with this program. If not, see <https://www.gnu.org/licenses/>. 

22""" 

23import os 

24from typing import List 

25 

26import numpy as np 

27import matplotlib.cm as cm 

28import pyqtgraph as pqtg 

29 

30from ..core.sdynpy_data import (TransferFunctionArray, ModeIndicatorFunctionArray, 

31 data_array, FunctionTypes, GUIPlot) 

32from ..core.sdynpy_coordinate import CoordinateArray, outer_product 

33from ..core.sdynpy_geometry import Geometry 

34from ..core.sdynpy_shape import ShapeArray, mac, matrix_plot 

35 

36from qtpy import QtWidgets, uic, QtGui 

37from qtpy.QtGui import QIcon, QFont 

38from qtpy.QtCore import Qt, QCoreApplication, QRect 

39from qtpy.QtWidgets import (QToolTip, QLabel, QPushButton, QApplication, 

40 QGroupBox, QWidget, QMessageBox, QHBoxLayout, 

41 QVBoxLayout, QSizePolicy, QMainWindow, 

42 QFileDialog, QErrorMessage, QListWidget, QListWidgetItem, 

43 QLineEdit, 

44 QDockWidget, QGridLayout, QButtonGroup, QDialog, 

45 QCheckBox, QRadioButton, QMenuBar, QMenu) 

46try: 

47 from qtpy.QtGui import QAction 

48except ImportError: 

49 from qtpy.QtWidgets import QAction 

50import traceback 

51 

52 

53class PropertiesDialog(QDialog): 

54 def __init__(self, shape, *args, **kwargs): 

55 super(QDialog, self).__init__(*args, **kwargs) 

56 uic.loadUi(os.path.join(os.path.abspath(os.path.dirname( 

57 os.path.abspath(__file__))), 'mode_properties.ui'), self) 

58 self.frequencyDoubleSpinBox.setValue(shape.frequency) 

59 self.dampingDoubleSpinBox.setValue(shape.damping * 100) 

60 self.comment1LineEdit.setText(shape.comment1) 

61 self.comment2LineEdit.setText(shape.comment2) 

62 self.comment3LineEdit.setText(shape.comment3) 

63 self.comment4LineEdit.setText(shape.comment4) 

64 self.comment5LineEdit.setText(shape.comment5) 

65 self.buttonBox.accepted.connect(self.accept) 

66 self.buttonBox.rejected.connect(self.reject) 

67 

68 @staticmethod 

69 def show(shape, parent=None): 

70 dialog = PropertiesDialog(shape, parent) 

71 result = dialog.exec_() == QtWidgets.QDialog.Accepted 

72 frequency = dialog.frequencyDoubleSpinBox.value() 

73 damping = dialog.dampingDoubleSpinBox.value() 

74 comment1 = dialog.comment1LineEdit.text() 

75 comment2 = dialog.comment2LineEdit.text() 

76 comment3 = dialog.comment3LineEdit.text() 

77 comment4 = dialog.comment4LineEdit.text() 

78 comment5 = dialog.comment5LineEdit.text() 

79 return (result, frequency, damping, comment1, comment2, comment3, comment4, comment5) 

80 

81 

82class ColoredCMIF(QMainWindow): 

83 """An Interactive Window for Selecting Shapes from Single Reference Data""" 

84 

85 def __init__(self, frfs: List[TransferFunctionArray] = None, 

86 shapes: List[ShapeArray] = None): 

87 super(ColoredCMIF, self).__init__() 

88 uic.loadUi(os.path.join(os.path.abspath(os.path.dirname( 

89 os.path.abspath(__file__))), 'colored_cmif.ui'), self) 

90 if ((not (frfs is None and shapes is None)) and 

91 (((frfs is None and shapes is not None) or 

92 (frfs is not None and shapes is None) or 

93 (len(frfs) != len(shapes))))): 

94 raise ValueError('frfs and shapes must both be specified and the ' + 

95 'same size, or neither must be specified') 

96 self.ccmif_data = None 

97 self.ccmif_curves = [] 

98 self.mode_scatter_plots = [] 

99 self.selection_plots = {} 

100 self.shape_array = [] if shapes is None else [shape.flatten() for shape in shapes] 

101 self.shape_points_on_plot = None 

102 self.file_names = [] 

103 self.references = [] 

104 self.cm = cm.Dark2 

105 self.cmif_plot.setLogMode(False, True) 

106 self.mac_plot.getPlotItem().vb.setAspectLocked(True) 

107 self.frf_resynth = None 

108 self.external_plots = {} 

109 self.geometry = None 

110 self.enabled_files = [] 

111 if frfs is not None: 

112 frf_array = [] 

113 for index, frf in enumerate(frfs): 

114 this_frf = frf.reshape_to_matrix() 

115 this_shape = this_frf.shape 

116 if this_shape[-1] > 1: 

117 raise ValueError( 

118 'FRF index {:} has more than one reference. Inputs to ColoredCMIF should be single reference FRFs.'.format(index)) 

119 if index > 0 and frf_array[-1].shape[0] != this_shape[0]: 

120 raise ValueError( 

121 'FRF index {:} does not have the number of responses as previous indices'.format(index)) 

122 if index > 0 and not np.all(frf_array[-1].response_coordinate == this_frf.response_coordinate): 

123 raise ValueError( 

124 'FRF index {:} does not have the same responses as previous indices'.format(index)) 

125 frf_array.append(this_frf) 

126 self.frf_array = np.concatenate(frf_array, axis=-1) 

127 self.references = self.frf_array[0].reference_coordinate 

128 for reference in self.references: 

129 self.file_selector.insertItem(self.file_selector.count() - 1, str(reference)) 

130 self.file_selector.setCurrentIndex(0) 

131 self.file_names = [str(ref) for ref in self.references] 

132 self.selected_modes = [[False for shape in shape_array] 

133 for shape_array in self.shape_array] 

134 self.enabled_files = [True for i in range(self.frf_array.shape[-1])] 

135 self.compute_ccmif() 

136 self.plot_ccmif() 

137 for shape, reference in zip(self.shape_array, self.references): 

138 shape.comment1 = str(reference) 

139 # print(shape.comment1) 

140 self.update_shape_list(no_load=True) 

141 else: 

142 self.frf_array = None 

143 self.selected_modes = [] 

144 

145 self.connect_callbacks() 

146 self.show() 

147 

148 def connect_callbacks(self): 

149 self.line_width_selector.valueChanged.connect(self.update_line_width) 

150 self.mode_selector.itemSelectionChanged.connect(self.update_selection) 

151 self.file_selector.activated.connect(self.update_shape_list) 

152 self.cmif_plot.scene().sigMouseClicked.connect(self.clicked_point) 

153 self.resynthesize_button.clicked.connect(self.resynthesize) 

154 self.plot_frfs_button.clicked.connect(self.plot_frfs) 

155 self.plot_cmifs_button.clicked.connect(self.plot_cmifs) 

156 self.load_geometry_button.clicked.connect(self.load_geometry) 

157 self.plot_shapes_button.clicked.connect(self.plot_shapes) 

158 self.save_shapes_button.clicked.connect(self.save_shapes) 

159 self.save_progress_button.clicked.connect(self.save_progress) 

160 self.load_progress_button.clicked.connect(self.load_progress) 

161 self.export_mode_table_button.clicked.connect(self.export_mode_table) 

162 self.send_to_new_figure_button.clicked.connect(self.export_figure) 

163 self.cluster_modes_button.clicked.connect(self.cluster_modes) 

164 self.mark_modes_checkbox.stateChanged.connect(self.plot_ccmif) 

165 self.plot_vertical_lines_checkbox.stateChanged.connect(self.plot_ccmif) 

166 self.label_selector.currentIndexChanged.connect(self.plot_ccmif) 

167 self.part_selector.currentIndexChanged.connect(self.compute_and_plot_ccmif) 

168 self.enable_button.clicked.connect(self.enable_file) 

169 self.disable_button.clicked.connect(self.disable_file) 

170 self.remove_file_button.clicked.connect(self.remove_file) 

171 self.replace_file_button.clicked.connect(self.replace_file) 

172 self.mode_selector.itemDoubleClicked.connect(self.set_mode_properties) 

173 self.cmif_plot.getPlotItem().ctrl.logXCheck.toggled.connect(self.plot_ccmif) 

174 self.cmif_plot.getPlotItem().ctrl.logYCheck.toggled.connect(self.plot_ccmif) 

175 self.autoresynth_checkbox.stateChanged.connect(self.toggle_auto_resynth) 

176 

177 def toggle_auto_resynth(self): 

178 self.resynthesize_button.setEnabled(not self.autoresynth_checkbox.isChecked()) 

179 

180 def set_mode_properties(self, item): 

181 try: 

182 shape_index = [self.mode_selector.item(i) for i in range( 

183 self.mode_selector.count())].index(item) 

184 file_index = self.file_selector.currentIndex() 

185 item.setSelected(not item.isSelected()) 

186 self.set_properties(file_index, shape_index) 

187 self.update_shape_list(no_load=True) 

188 except Exception: 

189 print(traceback.format_exc()) 

190 

191 def set_properties(self, file_index, shape_index): 

192 shape = self.shape_array[file_index][shape_index] 

193 # if not self.geometry is None: 

194 # plotter = self.geometry.plot_shape(shape) 

195 dialog = PropertiesDialog(shape, self) 

196 dialog.comment1LineEdit.setReadOnly(True) 

197 result = dialog.exec_() == QtWidgets.QDialog.Accepted 

198 if result: 

199 self.shape_array[file_index][shape_index].comment2 = dialog.comment2LineEdit.text() 

200 self.shape_array[file_index][shape_index].comment3 = dialog.comment3LineEdit.text() 

201 self.shape_array[file_index][shape_index].comment4 = dialog.comment4LineEdit.text() 

202 self.shape_array[file_index][shape_index].comment5 = dialog.comment5LineEdit.text() 

203 # if not self.geometry is None: 

204 # plotter.close() 

205 

206 def compute_and_plot_ccmif(self): 

207 self.compute_ccmif() 

208 self.plot_ccmif() 

209 

210 def compute_ccmif(self): 

211 try: 

212 self.frequencies = self.frf_array[0, 0].abscissa 

213 # Compute the SVD 

214 if self.part_selector.currentIndex() == 0: 

215 H = np.imag(self.frf_array.ordinate) 

216 elif self.part_selector.currentIndex() == 1: 

217 H = np.real(self.frf_array.ordinate) 

218 elif self.part_selector.currentIndex() == 2: 

219 H = self.frf_array.ordinate 

220 H = H[:, self.enabled_files, :] 

221 U, S, Vh = np.linalg.svd(np.moveaxis(H, -1, 0)) 

222 Vh = np.abs(Vh) 

223 best_references = np.argmax(Vh, axis=-1) 

224 self.ccmif_data = np.empty(S.shape[1:] + S.shape) 

225 self.ccmif_data[:] = np.nan 

226 for i in range(S.shape[-1]): 

227 logical_indices = best_references == i 

228 # Extend each side by 1 

229 logical_indices[1:] += logical_indices[:-1] 

230 logical_indices[:-1] += logical_indices[1:] 

231 keep_indices = np.where(logical_indices) 

232 self.ccmif_data[(i,) + keep_indices] = S[keep_indices] 

233 # Find the shape points on the plot 

234 self.shape_points_on_plot = [] 

235 for shape_array, ccmif_data in zip(self.enabled_shape_array, self.ccmif_data): 

236 point_locations = np.array( 

237 [np.interp(shape_array.frequency, self.frequencies, data) for data in ccmif_data.T]) 

238 self.shape_points_on_plot.append(point_locations) 

239 except Exception: 

240 print(traceback.format_exc()) 

241 

242 @property 

243 def enabled_shape_array(self): 

244 return [shape for i, shape in enumerate(self.shape_array) if self.enabled_files[i]] 

245 

246 @property 

247 def enabled_selected_modes(self): 

248 return [selection for i, selection in enumerate(self.selected_modes) if self.enabled_files[i]] 

249 

250 def update_shape_list(self, event=None, no_load=False): 

251 # print(event,no_load) 

252 if self.file_selector.currentIndex() == self.file_selector.count() - 1 and (not no_load): 

253 self.load_file() 

254 return 

255 self.mode_selector.blockSignals(True) 

256 shape_array = self.shape_array[self.file_selector.currentIndex()] 

257 self.mode_selector.clear() 

258 for i, shape in enumerate(shape_array): 

259 self.mode_selector.addItem('{:}: {:0.2f} Hz, {:0.2f}%, {:}'.format( 

260 i + 1, shape.frequency, shape.damping * 100, shape.comment2)) 

261 selected_modes = self.selected_modes[self.file_selector.currentIndex()] 

262 # print(selected_modes) 

263 for i, selected in enumerate(selected_modes): 

264 if selected: 

265 self.mode_selector.item(i).setSelected(True) 

266 if self.enabled_files[self.file_selector.currentIndex()]: 

267 self.disable_button.setEnabled(True) 

268 self.enable_button.setEnabled(False) 

269 self.mode_selector.setEnabled(True) 

270 else: 

271 self.disable_button.setEnabled(False) 

272 self.enable_button.setEnabled(True) 

273 self.mode_selector.setEnabled(False) 

274 self.mode_selector.blockSignals(False) 

275 

276 def load_file(self): 

277 filename, file_filter = QtWidgets.QFileDialog.getOpenFileName( 

278 self, 'Open Fit Data', filter='Numpy Files (*.npz)') 

279 if filename == '': 

280 return False 

281 else: 

282 try: 

283 file_data = np.load(filename) 

284 frf = file_data['frfs'].view(TransferFunctionArray).reshape_to_matrix() 

285 shapes = file_data['shapes'].view(ShapeArray).flatten() 

286 # Check sizes of things 

287 if (self.frf_array is not None) and (self.frf_array.size > 0): 

288 if (np.setdiff1d(frf.response_coordinate, self.frf_array.response_coordinate).size > 0 or 

289 np.setdiff1d(self.frf_array.response_coordinate, frf.response_coordinate).size > 0): 

290 QMessageBox.critical(self, 'Bad Response Data', 

291 'Loaded FRF does not have consistent response coordinates with existing data') 

292 return 

293 if (not len(self.shape_array) == 0): 

294 if (np.setdiff1d(shapes.coordinate, self.shape_array[0].coordinate).size > 0 or 

295 np.setdiff1d(self.shape_array[0].coordinate, shapes.coordinate).size > 0): 

296 QMessageBox.critical(self, 'Bad Response Data', 

297 'Loaded Shape does not have consistent response coordinates with existing data') 

298 return 

299 if frf.shape[-1] > 1: 

300 QMessageBox.critical(self, 'Bad Response Data', 

301 'Loaded FRF has multiple references. Inputs to ColoredCMIF should be single reference FRFs') 

302 return 

303 if shapes.is_complex(): 

304 QMessageBox.critical(self, 'Complex Shape', 

305 'ColoredCMIF currently does not allow complex shapes') 

306 return 

307 # Otherwise everything looks good 

308 self.shape_array.append(shapes.flatten()) 

309 self.enabled_files.append(True) 

310 self.selected_modes.append([False for shape in shapes]) 

311 self.file_names.append(filename) 

312 if (self.frf_array is None) or (self.frf_array.size == 0): 

313 self.frf_array = frf 

314 self.references = np.unique(frf.reference_coordinate) 

315 else: 

316 self.frf_array = np.concatenate((self.frf_array, frf), axis=-1) 

317 self.references = np.concatenate( 

318 (self.references, np.unique(frf.reference_coordinate))) 

319 self.file_selector.blockSignals(True) 

320 self.file_selector.insertItem(self.file_selector.count( 

321 ) - 1, str(self.references[-1]) + ': {:}'.format(filename)) 

322 self.file_selector.setCurrentIndex(self.file_selector.count() - 2) 

323 self.file_selector.blockSignals(False) 

324 self.update_shape_list() 

325 self.compute_and_plot_ccmif() 

326 except Exception: 

327 print(traceback.format_exc()) 

328 

329 def plot_ccmif(self): 

330 try: 

331 # print('Plotting CCMIF') 

332 if self.ccmif_data is None: 

333 QMessageBox.critical(self, 'CCMIF Not Yet Computed', 

334 'CCMIF must be computed before it can be plotted') 

335 xlog = self.cmif_plot.getPlotItem().ctrl.logXCheck.isChecked() 

336 ylog = self.cmif_plot.getPlotItem().ctrl.logYCheck.isChecked() 

337 self.clear_plot() 

338 self.cmif_plot.addLegend() 

339 non_disabled_indices = np.arange(len(self.enabled_files))[self.enabled_files] 

340 for i, data in enumerate(self.ccmif_data): 

341 pen = pqtg.mkPen( 

342 color=[int(255 * v) for v in self.cm(non_disabled_indices[i])], width=self.line_width_selector.value()) 

343 for j, singular_value in enumerate(data.T): 

344 if j == 0: 

345 self.ccmif_curves.append( 

346 self.cmif_plot.plot(self.frequencies, singular_value, 

347 pen=pen, name=str(self.references[non_disabled_indices[i]]))) 

348 else: 

349 self.ccmif_curves.append( 

350 self.cmif_plot.plot(self.frequencies, singular_value, 

351 pen=pen)) 

352 if self.mark_modes_checkbox.isChecked(): 

353 for i, (shape_array, point_locations) in enumerate(zip(self.enabled_shape_array, self.shape_points_on_plot)): 

354 pen = pqtg.mkPen(color=[int(255 * v) for v in self.cm(i)]) 

355 brush = pqtg.mkBrush([int(255 * v) for v in self.cm(i)]) 

356 abscissa = shape_array.frequency 

357 for j, ordinate in enumerate(point_locations): 

358 if np.all(np.isnan(ordinate)): 

359 self.mode_scatter_plots.append(None) 

360 else: 

361 self.mode_scatter_plots.append( 

362 pqtg.PlotDataItem(abscissa, ordinate, pen=None, symbolBrush=brush, symbol='x', symbolPen=pen)) 

363 self.cmif_plot.addItem(self.mode_scatter_plots[-1]) 

364 if self.label_selector.currentIndex() == 0: 

365 for k, (o, a) in enumerate(zip(ordinate, abscissa)): 

366 if np.isnan(o): 

367 continue 

368 ti = pqtg.TextItem('{:}: {:}'.format( 

369 non_disabled_indices[i] + 1, k + 1), color=[0, 0, 0]) 

370 self.cmif_plot.addItem(ti, ignoreBounds=True) 

371 ti.setPos(np.log10(a) if xlog else a, 

372 np.log10(o) if ylog else o) 

373 elif self.label_selector.currentIndex() == 1: 

374 for o, a in zip(ordinate, abscissa): 

375 if np.isnan(o): 

376 continue 

377 ti = pqtg.TextItem('{:}: {:0.1f}'.format( 

378 non_disabled_indices[i] + 1, a), color=[0, 0, 0]) 

379 self.cmif_plot.addItem(ti, ignoreBounds=True) 

380 ti.setPos(np.log10(a) if xlog else a, 

381 np.log10(o) if ylog else o) 

382 for i, (selections, shapes, point_locations) in enumerate(zip(self.enabled_selected_modes, 

383 self.enabled_shape_array, 

384 self.shape_points_on_plot)): 

385 for j, (selection, shape, points) in enumerate(zip(selections, shapes, point_locations.T)): 

386 if selection: 

387 # print('Drawing {:}'.format((i,j))) 

388 for k, point in enumerate(points): 

389 if np.isnan(point): 

390 continue 

391 self.selection_plots[(non_disabled_indices[i], j, k)] = pqtg.PlotDataItem( 

392 [shape.frequency], [point], pen=None, symbolBrush=pqtg.mkBrush([0, 0, 0, 0]), 

393 symbol='o', symbolPen=pqtg.mkPen(color=[0, 0, 0, 255])) 

394 self.selection_plots[(non_disabled_indices[i], j, k)].setZValue(0) 

395 self.cmif_plot.addItem( 

396 self.selection_plots[(non_disabled_indices[i], j, k)]) 

397 self.mac_plot.clear() 

398 shapes = self.collect_shapes() 

399 if shapes.size > 0: 

400 mac_matrix = mac(shapes) 

401 self.mac_plot.addItem(pqtg.ImageItem(mac_matrix)) 

402 # print('Finished Plotting CCMIF') 

403 except Exception: 

404 print(traceback.format_exc()) 

405 

406 def clear_plot(self): 

407 # for curve in self.ccmif_curves: 

408 # self.cmif_plot.removeItem(curve) 

409 # del(curve) 

410 self.ccmif_curves = [] 

411 # for plot in self.mode_scatter_plots: 

412 # if plot is None: 

413 # continue 

414 # self.cmif_plot.removeItem(plot) 

415 # plot.sigPointsClicked.disconnect() 

416 # del(plot) 

417 self.mode_scatter_plots = [] 

418 # for key,value in self.selection_plots.items(): 

419 # self.cmif_plot.removeItem(value) 

420 # del(value) 

421 self.selection_plots = {} 

422 self.cmif_plot.clear() 

423 

424 def clicked_point(self, mouse_event): 

425 # print('Clicked {:}'.format(repr(mouse_event))) 

426 # print('Position: {:}'.format(mouse_event.pos())) 

427 # print('Scene Position: {:}'.format(mouse_event.scenePos())) 

428 # print('View Position: {:}'.format(self.cmif_plot.getPlotItem().vb.mapSceneToView(mouse_event.scenePos()))) 

429 # print('Button: {:}'.format(repr(mouse_event.button()))) 

430 if mouse_event.button() != Qt.LeftButton: 

431 return 

432 xlog = self.cmif_plot.getPlotItem().ctrl.logXCheck.isChecked() 

433 ylog = self.cmif_plot.getPlotItem().ctrl.logYCheck.isChecked() 

434 ar = self.cmif_plot.getPlotItem().vb.getAspectRatio() 

435 # print('Xlog {:}'.format(xlog)) 

436 # print('YLog {:}'.format(ylog)) 

437 # Go through and find the closest point to the click 

438 min_dist = float('inf') 

439 min_point = None 

440 min_point_xy = None 

441 view_position = self.cmif_plot.getPlotItem().vb.mapSceneToView(mouse_event.scenePos()) 

442 clickx = view_position.x() 

443 clicky = view_position.y() 

444 # Check if the click is outside the range 

445 [[xmin, xmax], [ymin, ymax]] = self.cmif_plot.getPlotItem().vb.viewRange() 

446 if (clickx < xmin) or (clickx > xmax) or (clicky > ymax) or (clicky < ymin): 

447 return 

448 for i, (shapes, point_locations) in enumerate(zip(self.shape_array, 

449 self.shape_points_on_plot)): 

450 for j, (shape, points) in enumerate(zip(shapes, point_locations.T)): 

451 if np.all(np.isnan(points)): 

452 continue 

453 x = np.log10(shape.frequency) if xlog else shape.frequency 

454 y = np.log10(points) if ylog else points 

455 dist = np.nanmin(((clickx - x) * ar)**2 + (clicky - y)**2) 

456 if dist < min_dist: 

457 min_dist = dist 

458 min_point = (i, j) 

459 min_point_xy = (x, y) 

460 # print('Min Point XY: {:}'.format(min_point_xy)) 

461 # print('Min Point {:}'.format(min_point)) 

462 # print('Minimum Point {:}, {:}'.format(min_point,min_dist)) 

463 file_index, mode_index = min_point 

464 # Toggle Selected 

465 self.selected_modes[file_index][mode_index] = not self.selected_modes[file_index][mode_index] 

466 self.file_selector.setCurrentIndex(file_index) 

467 self.update_shape_list() 

468 self.plot_ccmif() 

469 if self.autoresynth_checkbox.isChecked(): 

470 self.resynthesize() 

471 

472 def update_line_width(self): 

473 for i, curve in enumerate(self.ccmif_curves): 

474 pen = pqtg.mkPen(color=[int(255 * v) for v in self.cm(i // self.frf_array.shape[-1])], 

475 width=self.line_width_selector.value()) 

476 curve.setPen(pen) 

477 

478 def update_selection(self): 

479 self.selected_modes[self.file_selector.currentIndex()] = [ 

480 self.mode_selector.item(i).isSelected() for i in range(self.mode_selector.count())] 

481 self.plot_ccmif() 

482 if self.autoresynth_checkbox.isChecked(): 

483 self.resynthesize() 

484 # print(self.selected_modes) 

485 

486 def collect_shapes(self): 

487 all_shapes = [] 

488 for shapes, keep, enabled, reference in zip(self.shape_array, self.selected_modes, self.enabled_files, self.references): 

489 if not enabled: 

490 continue 

491 this_shapes = shapes[keep].copy() 

492 this_shapes.comment1 = str(reference) 

493 all_shapes.append(this_shapes) 

494 all_shapes = np.concatenate(all_shapes) 

495 isort = np.argsort(all_shapes.frequency) 

496 all_shapes = all_shapes[isort] 

497 return all_shapes 

498 

499 def resynthesize(self): 

500 shapes = self.collect_shapes() 

501 if shapes.size == 0: 

502 QMessageBox.critical(self, 'No Shapes Selected', 

503 'Please Select Shapes Before Resynthesizing') 

504 return 

505 enabled_frf_array = self.frf_array[:, self.enabled_files] 

506 self.frf_resynth = shapes.compute_frf(self.frequencies, 

507 enabled_frf_array[:, 0].response_coordinate, 

508 enabled_frf_array[0].reference_coordinate, 

509 displacement_derivative=self.data_type_selector.currentIndex()) 

510 if 'cmif' in self.external_plots: 

511 if self.part_selector.currentIndex() == 0: 

512 part = 'imag' 

513 if self.part_selector.currentIndex() == 1: 

514 part = 'real' 

515 if self.part_selector.currentIndex() == 2: 

516 part = 'both' 

517 exp_cmif = np.concatenate([enabled_frf_array.compute_cmif( 

518 part)] + [this_frf.compute_cmif(part) for this_frf in enabled_frf_array.T]) 

519 ana_cmif = np.concatenate([self.frf_resynth.compute_cmif( 

520 part)] + [this_frf.compute_cmif(part) for this_frf in self.frf_resynth.T]) 

521 self.external_plots['cmif'].update_data(exp_cmif, ana_cmif) 

522 

523 if 'frf' in self.external_plots: 

524 self.external_plots['frf'].update_data(enabled_frf_array, self.frf_resynth) 

525 

526 def plot_frfs(self): 

527 enabled_frf_array = self.frf_array[:, self.enabled_files] 

528 self.external_plots['frf'] = GUIPlot(enabled_frf_array, self.frf_resynth) 

529 

530 def plot_cmifs(self): 

531 enabled_frf_array = self.frf_array[:, self.enabled_files] 

532 if self.part_selector.currentIndex() == 0: 

533 part = 'imag' 

534 if self.part_selector.currentIndex() == 1: 

535 part = 'real' 

536 if self.part_selector.currentIndex() == 2: 

537 part = 'both' 

538 exp_cmif = np.concatenate([enabled_frf_array.compute_cmif(part)] + 

539 [this_frf.compute_cmif(part) for this_frf in enabled_frf_array.T]) 

540 ana_cmif = np.concatenate([self.frf_resynth.compute_cmif(part)] + 

541 [this_frf.compute_cmif(part) for this_frf in self.frf_resynth.T]) 

542 self.external_plots['cmif'] = GUIPlot(exp_cmif, ana_cmif) 

543 self.external_plots['cmif'].ordinate_log = True 

544 self.external_plots['cmif'].actionOrdinate_Log.setChecked(True) 

545 self.external_plots['cmif'].update() 

546 

547 def load_geometry(self): 

548 filename, file_filter = QtWidgets.QFileDialog.getOpenFileName( 

549 self, 'Open Geometry', filter='Numpy Files (*.npz);;Universal Files (*.unv *.uff)') 

550 if filename == '': 

551 return 

552 self.geometry = Geometry.load(filename) 

553 

554 def plot_shapes(self): 

555 if self.geometry is None: 

556 QMessageBox.critical(self, 'No Geometry Loaded', 

557 'Please load geometry prior to plotting shapes') 

558 return 

559 shapes = self.collect_shapes() 

560 self.external_plots['geometry'] = self.geometry.plot_shape(shapes) 

561 

562 def save_shapes(self): 

563 filename, file_filter = QtWidgets.QFileDialog.getSaveFileName( 

564 self, 'Save Shapes', filter='Numpy Files (*.npy)') 

565 if filename == '': 

566 return 

567 self.collect_shapes().save(filename) 

568 

569 def save_progress(self): 

570 filename, file_filter = QtWidgets.QFileDialog.getSaveFileName( 

571 self, 'Save CCMIF', filter='CCMIF (*.ccm)') 

572 if filename == '': 

573 return 

574 

575 def load_progress(self): 

576 filename, file_filter = QtWidgets.QFileDialog.getOpenFileName( 

577 self, 'Load CCMIF', filter='CCMIF (*.ccm)') 

578 if filename == '': 

579 return 

580 

581 def export_mode_table(self): 

582 filename, file_filter = QtWidgets.QFileDialog.getSaveFileName( 

583 self, 'Save Mode Table', filter='CSV (*.csv);;reStructured Text (*.rst);;Markdown (*.md);;LaTeX (*.tex)') 

584 if filename == '': 

585 return 

586 shapes = self.collect_shapes() 

587 if file_filter == 'CSV (*.csv)': 

588 with open(filename, 'w') as f: 

589 f.write(shapes.mode_table('csv')) 

590 elif file_filter == 'reStructured Text (*.rst)': 

591 with open(filename, 'w') as f: 

592 f.write(shapes.mode_table('rst')) 

593 elif file_filter == 'Markdown (*.md)': 

594 with open(filename, 'w') as f: 

595 f.write(shapes.mode_table('markdown')) 

596 elif file_filter == 'LaTeX (*.tex)': 

597 with open(filename, 'w') as f: 

598 f.write(shapes.mode_table('latex')) 

599 

600 def export_figure(self): 

601 pass 

602 

603 def cluster_modes(self): 

604 pass 

605 

606 def enable_file(self): 

607 self.enabled_files[self.file_selector.currentIndex()] = True 

608 self.update_shape_list() 

609 self.compute_and_plot_ccmif() 

610 

611 def disable_file(self): 

612 self.enabled_files[self.file_selector.currentIndex()] = False 

613 self.update_shape_list() 

614 self.compute_and_plot_ccmif() 

615 

616 def remove_file(self): 

617 index_to_remove = self.file_selector.currentIndex() 

618 self.shape_array.pop(index_to_remove) 

619 self.enabled_files.pop(index_to_remove) 

620 self.selected_modes.pop(index_to_remove) 

621 self.file_names.pop(index_to_remove) 

622 index_array = [False if i == index_to_remove else True for i in range( 

623 self.frf_array.shape[-1])] 

624 self.frf_array = self.frf_array[:, index_array] 

625 if self.frf_array.shape[-1] == 0: 

626 self.frf_array = None 

627 self.references = self.references[index_array] 

628 # Remove entry from the combobox 

629 self.file_selector.blockSignals(True) 

630 self.file_selector.removeItem(index_to_remove) 

631 self.file_selector.setCurrentIndex(0) 

632 self.file_selector.blockSignals(False) 

633 self.update_shape_list(no_load=True) 

634 self.compute_and_plot_ccmif() 

635 

636 def replace_file(self): 

637 pass