Coverage for src / sdynpy / fileio / sdynpy_escdf.py: 9%

1078 statements  

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

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

2""" 

3Created on Fri Feb 7 10:47:58 2025 

4 

5@author: dprohe 

6""" 

7 

8from ..core.sdynpy_geometry import (Geometry,NodeArray,CoordinateSystemArray, 

9 coordinate_system_array,node_array, 

10 global_coord,_element_types,_exodus_elem_type_map) 

11from ..core.sdynpy_shape import ShapeArray,shape_array 

12from ..core.sdynpy_data import data_array,FunctionTypes,NDDataArray,GUIPlot,join 

13from ..core.sdynpy_colors import color_list 

14from ..core.sdynpy_coordinate import coordinate_array 

15from .sdynpy_rattlesnake import (read_rattlesnake_output,read_system_id_nc4, 

16 read_modal_data,read_random_spectral_data, 

17 read_transient_control_data) 

18 

19from qtpy.QtWidgets import (QMainWindow) 

20from qtpy import QtWidgets, uic, QtGui, QtCore 

21import netCDF4 as nc4 

22import os 

23import numpy as np 

24 

25try: 

26 import escdf 

27except ImportError: 

28 escdf = None 

29 

30def to_geometry(geometry_dataset): 

31 if not (geometry_dataset.istype("geometry") or geometry_dataset.istype("point_cloud")): 

32 raise ValueError( 

33 "geometry_dataset must have be an ESCDF `geometry` or `point_cloud` dataset." 

34 ) 

35 if geometry_dataset.istype("geometry"): 

36 element_type_string = None 

37 nodes = [] 

38 css = [coordinate_system_array()] 

39 for id, position, x, y, z in zip( 

40 geometry_dataset.node_id, 

41 geometry_dataset.node_position, 

42 geometry_dataset.node_x_direction, 

43 geometry_dataset.node_y_direction, 

44 geometry_dataset.node_z_direction, 

45 ): 

46 nodes.append(node_array(id, position, def_cs=1, disp_cs=id + 1, color=0)) 

47 css.append( 

48 coordinate_system_array( 

49 id + 1, "Node {:} Disp CS".format(id), matrix=np.array((x, y, z, np.zeros(3))) 

50 ) 

51 ) 

52 nodes = np.array(nodes).view(NodeArray) 

53 css = np.array(css).view(CoordinateSystemArray) 

54 

55 geometry = Geometry(nodes, css) 

56 try: 

57 for i, connection in enumerate(geometry_dataset.line_connection): 

58 try: 

59 color = geometry_dataset.line_color[i, :] 

60 color_index = np.argmin(np.linalg.norm(color / 255 - color_list, axis=-1)) 

61 except TypeError: 

62 color_index = 0 

63 geometry.add_traceline(connection, color=color_index) 

64 except TypeError: 

65 # No tracelines 

66 pass 

67 

68 try: 

69 for i, connection in enumerate(geometry_dataset.element_connection): 

70 try: 

71 color = geometry_dataset.element_color[i, :] 

72 color_index = np.argmin(np.linalg.norm(color / 255 - color_list, axis=-1)) 

73 except TypeError: 

74 color_index = 0 

75 type_name = geometry_dataset.element_type[i][()] 

76 try: 

77 element_type = _exodus_elem_type_map[type_name.lower()] 

78 except KeyError: 

79 if element_type_string is None: 

80 element_type_string = { 

81 val.lower(): key for key, val in _element_types.items() 

82 } 

83 try: 

84 element_type = element_type_string[type_name.lower()] 

85 except KeyError: 

86 print( 

87 "Unknown Element Type at index {:}, {:}. Skipping...".format( 

88 i, type_name 

89 ) 

90 ) 

91 continue 

92 geometry.add_element(element_type, connection, color=color_index) 

93 except TypeError: 

94 # No elements 

95 pass 

96 return geometry 

97 elif geometry_dataset.istype("point_cloud"): 

98 element_type_string = None 

99 nodes = [] 

100 css = [coordinate_system_array()] 

101 if geometry_dataset.node_id is None: 

102 node_id = np.arange(1, geometry_dataset.node_position.shape[0] + 1) 

103 else: 

104 node_id = geometry_dataset.node_id[...] 

105 for id, position in zip(node_id, geometry_dataset.node_position): 

106 nodes.append(node_array(id, position, def_cs=1, disp_cs=1, color=0)) 

107 nodes = np.array(nodes).view(NodeArray) 

108 css = np.array(css).view(CoordinateSystemArray) 

109 

110 geometry = Geometry(nodes, css) 

111 return geometry 

112 else: 

113 raise ValueError( 

114 "geometry_dataset must have be an ESCDF `geometry` or `point_cloud` dataset." 

115 ) 

116 

117def from_geometry(dataset_name, geometry : Geometry, descriptive_name = ''): 

118 if escdf is None: 

119 raise ImportError('Could not import module `escdf`, unable to create dataset.') 

120 esgeo = escdf.classes.geometry(dataset_name,descriptive_name = descriptive_name) 

121 esgeo.node_id = geometry.node.id 

122 esgeo.node_position = geometry.global_node_coordinate(geometry.node.id) 

123 esgeo.node_x_direction = geometry.global_deflection( 

124 coordinate_array(geometry.node.id,'X+')) 

125 esgeo.node_y_direction = geometry.global_deflection( 

126 coordinate_array(geometry.node.id,'Y+')) 

127 esgeo.node_z_direction = geometry.global_deflection( 

128 coordinate_array(geometry.node.id,'Z+')) 

129 if geometry.traceline.size > 0: 

130 esgeo.line_color = [np.array(color_list[i])*255 for i in geometry.traceline.color] 

131 esgeo.line_connection = geometry.traceline.connectivity 

132 if geometry.element.size > 0: 

133 esgeo.element_connection = geometry.element.connectivity 

134 esgeo.element_color = [np.array(color_list[i])*255 for i in geometry.element.color] 

135 esgeo.element_type = [_element_types[t] for t in geometry.element.type] 

136 return esgeo 

137 

138def to_shape(shape_dataset): 

139 if not shape_dataset.istype('mode'): 

140 raise ValueError('shape_dataset must be an ESCDF `mode` dataset.') 

141 coordinate = coordinate_array(string_array=shape_dataset.dof_name[...]) 

142 shape_matrix = shape_dataset.shape[...].T 

143 frequency = shape_dataset.frequency[...] 

144 damping = shape_dataset.damping_ratio[...] 

145 try: 

146 modal_mass = shape_dataset.modal_mass[...] 

147 except TypeError: 

148 modal_mass = 1 

149 try: 

150 description = shape_dataset.description[...] 

151 except TypeError: 

152 description = '' 

153 return shape_array(coordinate,shape_matrix,frequency,damping,modal_mass, 

154 description) 

155 

156def from_shape(dataset_name, shape : ShapeArray, descriptive_name = ''): 

157 if escdf is None: 

158 raise ImportError('Could not import module `escdf`, unable to create dataset.') 

159 shape = shape.flatten() 

160 modes = escdf.classes.mode(dataset_name,descriptive_name = descriptive_name) 

161 modes.damping_ratio = shape.damping 

162 modes.frequency = shape.frequency 

163 dofs = np.unique(shape.coordinate) 

164 modes.shape = shape[dofs].T 

165 modes.dof_name = dofs.string_array() 

166 modes.modal_mass = shape.modal_mass 

167 if not np.all(shape.comment1 == ''): 

168 modes.description = shape.comment1.astype(object) 

169 return modes 

170 

171datatype_enum_names = { 

172 FunctionTypes.TIME_RESPONSE:'time response', 

173 FunctionTypes.CROSSSPECTRUM:'power spectrum', 

174 FunctionTypes.FREQUENCY_RESPONSE_FUNCTION:'frequency response function', 

175 FunctionTypes.TRANSMISIBILITY:'transmissibility', 

176 FunctionTypes.COHERENCE:'coherence', 

177 FunctionTypes.CROSSCORRELATION:'correlation', 

178 FunctionTypes.POWER_SPECTRAL_DENSITY:'power spectral density', 

179 FunctionTypes.SPECTRUM:'spectrum', 

180 FunctionTypes.MODE_INDICATOR_FUNCTION:'mode indicator function', 

181 FunctionTypes.PARTIAL_COHERENCE:'partial coherence', 

182 FunctionTypes.SHOCK_RESPONSE_SPECTRUM:'shock response spectrum', 

183 FunctionTypes.IMPULSE_RESPONSE_FUNCTION:'impulse response function', 

184 FunctionTypes.MULTIPLE_COHERENCE:'multiple coherence'} 

185 

186datatype_names = { 

187 FunctionTypes.GENERAL: 

188 ['general','none'], 

189 FunctionTypes.TIME_RESPONSE: 

190 ['time history','time response', 'th', 'time data', 'time', 

191 'time histories','time responses', 'ths', 'times',], 

192 FunctionTypes.AUTOSPECTRUM: 

193 ['autospectrum','as','autospectra'], 

194 FunctionTypes.CROSSSPECTRUM: 

195 ['crossspectrum','cs','crossspectra'], 

196 FunctionTypes.FREQUENCY_RESPONSE_FUNCTION: 

197 ['frf','frequency response','frequency response function', 

198 'frfs','frequency responses', 'frequency response functions', 

199 'transfer function','transfer functions', 'tf'], 

200 FunctionTypes.TRANSMISIBILITY: 

201 ['transmisibility','transmisibility function', 

202 'transmisibilities','transmisibility functions'], 

203 FunctionTypes.COHERENCE:['coherence','coh'], 

204 FunctionTypes.AUTOCORRELATION: 

205 ['corr','correlation','autocorrelation','corrs','correlations', 

206 'autocorrelations'], 

207 FunctionTypes.CROSSCORRELATION: 

208 ['ccorr','crosscorrelation''ccorrs', 

209 'crosscorrelations'], 

210 FunctionTypes.POWER_SPECTRAL_DENSITY: 

211 ['power spectral density','power spectral densities','psd','psds', 

212 'apsd','apsds','cpsd','cpsds','autopower spectral density', 

213 'autopower spectral densities','crosspower spectral density', 

214 'crosspower spectral densities'], 

215 FunctionTypes.ENERGY_SPECTRAL_DENSITY: 

216 ['esd','energy spectral density','energy spectral densities','esds'], 

217 FunctionTypes.PROBABILITY_DENSITY_FUNCTION: 

218 ['pdf','pdfs','probability density','probability densities', 

219 'probability density function','probability density functions'], 

220 FunctionTypes.SPECTRUM: 

221 ['fft','ffts','spectrum','spectra'], 

222 FunctionTypes.CUMULATIVE_FREQUENCY_DISTRIBUTION: 

223 ['cfd','cfds','cumulative frequency distribution', 

224 'cumulative frequency distributions'], 

225 FunctionTypes.PEAKS_VALLEY: 

226 ['peaks valley'], 

227 FunctionTypes.STRESS_PER_CYCLE: 

228 ['stress per cycle','stress/cycle'], 

229 FunctionTypes.STRAIN_PER_CYCLE: 

230 ['strain per cycle','strain/cycle'], 

231 FunctionTypes.ORBIT:['orbit','orbits'], 

232 FunctionTypes.MODE_INDICATOR_FUNCTION: 

233 ['mif','cmif','mmif','nmif','mode indicator function','mode indicator functions', 

234 'complex mode indicator function','complex mode indicator functions', 

235 'multimode indicator function','multimode indicator functions', 

236 'normal mode indicator function normal mode indicator functions'], 

237 FunctionTypes.FORCE_PATTERN:['force_pattern'], 

238 FunctionTypes.PARTIAL_POWER:['partial power'], 

239 FunctionTypes.PARTIAL_COHERENCE:['partial coherence','partial coh','pcoh'], 

240 FunctionTypes.EIGENVALUE:['eigenvalue','eigenvalues'], 

241 FunctionTypes.EIGENVECTOR:['eigenvector','eigenvectors'], 

242 FunctionTypes.SHOCK_RESPONSE_SPECTRUM: 

243 ['shock response spectrum','shock response spectra','srs','srss'], 

244 FunctionTypes.FINITE_IMPULSE_RESPONSE_FILTER: 

245 ['finite impulse response function','firf'], 

246 FunctionTypes.MULTIPLE_COHERENCE: 

247 ['mcoh','multiple coherence'], 

248 FunctionTypes.ORDER_FUNCTION:['order','order function','order functions'], 

249 FunctionTypes.PHASE_COMPENSATION:['phase comp','phase compensation'], 

250 FunctionTypes.IMPULSE_RESPONSE_FUNCTION: 

251 ['irf','irfs','impulse response function','impulse response functions', 

252 'impuse response','impulse responses'] 

253 } 

254 

255datatype_map = {} 

256for key,name_list in datatype_names.items(): 

257 for name in name_list: 

258 datatype_map[name.replace(' ','').replace('_','').replace('-','').lower()] = key 

259 

260def to_data(data_dataset): 

261 if not data_dataset.istype('data'): 

262 raise ValueError('data_dataset must be an ESCDF `data` dataset.') 

263 try: 

264 function_type = datatype_map[ 

265 data_dataset.data_type[...][()].replace(' ','').replace('_','').replace('-','').lower()] 

266 except KeyError: 

267 raise ValueError('Unknown data type {:}'.format(data_dataset.data_type[...][()])) 

268 coordinate = coordinate_array(string_array=data_dataset.channel[...]) 

269 ordinate_unit = np.char.add('Ordinate Unit: ', data_dataset.ordinate_unit[...]) 

270 abscissa_unit = np.char.add(' -- Abscissa Unit: ', data_dataset.abscissa_unit[...]) 

271 comment1 = np.char.add(ordinate_unit,abscissa_unit) 

272 try: 

273 abscissa = np.arange(data_dataset.ordinate.shape[-1])*data_dataset.abscissa_step[...] + data_dataset.abscissa_start[...] 

274 except TypeError: 

275 abscissa = data_dataset.abscissa[...] 

276 data = data_array(function_type, abscissa, data_dataset.ordinate[...], 

277 coordinate, comment1) 

278 return data 

279 

280def from_data(dataset_name, data : NDDataArray,descriptive_name=''): 

281 if escdf is None: 

282 raise ImportError('Could not import module `escdf`, unable to create dataset.') 

283 data = data.flatten() 

284 try: 

285 abscissa_spacing = data.abscissa_spacing 

286 except ValueError: 

287 abscissa_spacing = None 

288 starting_abscissa = data.abscissa[...,0] 

289 if not np.all(starting_abscissa == starting_abscissa[0]): 

290 starting_abscissa = None 

291 else: 

292 starting_abscissa = starting_abscissa[0] 

293 dataset = escdf.classes.data(dataset_name,descriptive_name = descriptive_name) 

294 if starting_abscissa is None or abscissa_spacing is None: 

295 if np.all(data.abscissa == data.abscissa[0]): 

296 abscissa = data.abscissa[0] 

297 else: 

298 abscissa = data.abscissa 

299 dataset.abscissa = abscissa 

300 else: 

301 dataset.abscissa_step = abscissa_spacing 

302 dataset.abscissa_start = starting_abscissa 

303 dataset.ordinate = data.ordinate 

304 dataset.data_type = datatype_enum_names[data.function_type] 

305 dataset.channel = data.coordinate.string_array().astype(object) 

306 return dataset 

307 

308def from_rattlesnake_channel_info(dataset_name, channel_info, descriptive_name=''): 

309 """Generates a "vibration_channel_table" dataset from the channel information 

310 in a Rattlesnake netcdf4 file. 

311 

312 Operates on channel tables like the one from  

313 

314 Parameters 

315 ---------- 

316 dataset_name : str 

317 The name of the dataset to be generated 

318 channel_info : pandas.DataFrame 

319 A dataframe returned from reading Rattlesnake netcdf4 output into SDynPy 

320 descriptive_name : str 

321 A description of the dataset to be generated 

322 

323 Returns 

324 ------- 

325 escdf.Dataset 

326 A dataset with the type 'vibration_channel_table' containing channel 

327 table information 

328 """ 

329 if escdf is None: 

330 raise ImportError('Could not import module `escdf`, unable to create dataset.') 

331 

332 escdf_channels = escdf.Dataset(dataset_name,'vibration_channel_table', 

333 descriptive_name) 

334 

335 escdf_channels.coupling = [v.lower() for v in channel_info['coupling']] 

336 escdf_channels.daq = [daq+'/'+channel for daq,channel in zip(channel_info['physical_device'],channel_info['physical_channel'])] 

337 escdf_channels.data_type = [v.lower() for v in channel_info['channel_type']] 

338 escdf_channels.node_direction = channel_info['node_direction'] 

339 escdf_channels.node_id = channel_info['node_number'] 

340 escdf_channels.sensitivity = channel_info['sensitivity'] 

341 escdf_channels.sensitivity_unit = channel_info['unit'] 

342 escdf_channels.make = channel_info['make'] 

343 escdf_channels.model = channel_info['model'] 

344 escdf_channels.serial_number = [serial_number+triax_dof for serial_number,triax_dof in zip(channel_info['serial_number'],channel_info['triax_dof'])] 

345 

346 return escdf_channels 

347 

348def from_rattlesnake_modal_parameters(dataset_name, modal_dataset, environment_name = None, descriptive_name=''): 

349 """Generates a "modal_test_parameters" object from modal output 

350 

351 Parameters 

352 ---------- 

353 dataset_name : str 

354 The name of the dataset to be generated 

355 modal_dataset : netCDF4.Dataset 

356 A dataset produced by loading the netcdf4 output file. 

357 environment_name : str, optional 

358 Name of the environment. Must be specified if more than one environment 

359 exists in the file 

360 descriptive_name : str, optional 

361 A description of the generated dataset, by default ''. 

362 

363 Returns 

364 ------- 

365 escdf.Dataset 

366 A dataset with the type "modal_test_parameters" containing 

367 modal parameters 

368 """ 

369 if escdf is None: 

370 raise ImportError('Could not import module `escdf`, unable to create dataset.') 

371 

372 environment_names = [name for name in modal_dataset.groups if name != 'channels'] 

373 if environment_name is None: 

374 environment_names = [name for name in modal_dataset.groups if name != 'channels'] 

375 if len(environment_names) != 1: 

376 raise ValueError('Found {:} environments in the file. Could not select correctly.'.format(len(environment_names))) 

377 environment_name = environment_names[0] 

378 modal_parameters = escdf.Dataset(dataset_name, 'modal_test_parameters', descriptive_name) 

379 modal_parameters.sample_rate = modal_dataset.sample_rate 

380 modal_parameters.samples_per_frame = modal_dataset[environment_name].samples_per_frame 

381 modal_parameters.averaging_type = modal_dataset[environment_name].averaging_type.lower() 

382 modal_parameters.averages = modal_dataset[environment_name].num_averages 

383 if modal_dataset[environment_name].averaging_type.lower() == 'exponential': 

384 modal_parameters.averaging_coefficient = modal_dataset[environment_name].averaging_coefficient 

385 modal_parameters.overlap = modal_dataset[environment_name].overlap 

386 modal_parameters.window = modal_dataset[environment_name].frf_window.lower() 

387 if modal_dataset[environment_name].frf_window.lower() == 'exponential': 

388 modal_parameters.window_parameters = [modal_dataset[environment_name].exponential_window_value_at_frame_end] 

389 modal_parameters.window_parameter_names = ['Window Value at Frame End'] 

390 modal_parameters.frf_technique = modal_dataset[environment_name].frf_technique.lower() 

391 trigger = modal_dataset[environment_name].trigger_type.lower() 

392 modal_parameters.trigger_type = trigger 

393 if trigger != 'free run': 

394 modal_parameters.trigger_slope = 'positive' if modal_dataset[environment_name].trigger_slope_positive else 'negative' 

395 modal_parameters.trigger_level = modal_dataset[environment_name].trigger_level 

396 modal_parameters.pretrigger = modal_dataset[environment_name].pretrigger 

397 signal_generator_type = modal_dataset[environment_name].signal_generator_type 

398 modal_parameters.signal_generator_type = signal_generator_type 

399 if signal_generator_type != 'none': 

400 modal_parameters.signal_generator_level = modal_dataset[environment_name].signal_generator_level 

401 modal_parameters.signal_generator_min_freq = modal_dataset[environment_name].signal_generator_min_frequency 

402 modal_parameters.signal_generator_max_freq = modal_dataset[environment_name].signal_generator_max_frequency 

403 if signal_generator_type == 'burst': 

404 modal_parameters.signal_generator_on_fraction = modal_dataset[environment_name].signal_generator_on_fraction 

405 environment_index = modal_dataset['environment_names'][...]==environment_name 

406 active_channel_indices = modal_dataset['environment_active_channels'][...,environment_index][:,0].astype(bool) 

407 channel_names = np.array([str(modal_dataset['channels']['node_number'][i])+str(modal_dataset['channels']['node_direction'][i]) for i in range(modal_dataset.dimensions['response_channels'].size)])[active_channel_indices] 

408 reference_channels = channel_names[modal_dataset[environment_name]['reference_channel_indices'][:]] 

409 modal_parameters.reference_channel = reference_channels 

410 excitation_indices = np.array([modal_dataset['channels']['feedback_device'][i] != '' for i in range(modal_dataset.dimensions['response_channels'].size)]) 

411 excitation_channels = channel_names[excitation_indices] 

412 modal_parameters.excitation_channel = excitation_channels 

413 return modal_parameters 

414 

415def from_rattlesnake_system_id_parameters(dataset_name, system_id_dataset, environment_name=None, descriptive_name=''): 

416 """Gets system identification parameters from a NetCDF4 streaming file 

417 

418 Parameters 

419 ---------- 

420 dataset_name : str 

421 The name of the dataset to be generated 

422 system_id_dataset : netCDF4.Dataset 

423 A dataset created by loading a system identification streaming file 

424 environment_name : str, optional 

425 Name of the environment. Must be specified if more than one environment 

426 exists in the file 

427 descriptive_name : str, optional 

428 A description for the dataset that will be generated, by default ''. 

429 

430 Returns 

431 ------- 

432 escdf.Dataset 

433 A dataset with the type "rattlesnake_sysid_parameters" containing 

434 system identification parameters. 

435 """ 

436 if escdf is None: 

437 raise ImportError('Could not import module `escdf`, unable to create dataset.') 

438 if environment_name is None: 

439 environment_names = [name for name in system_id_dataset.groups if name != 'channels'] 

440 if len(environment_names) != 1: 

441 raise ValueError('Found {:} environments in the file. Could not select correctly.'.format(len(environment_names))) 

442 environment_name = environment_names[0] 

443 sysid_params = escdf.classes.rattlesnake_sysid_parameters(dataset_name, 

444 descriptive_name) 

445 sysid_params.sample_rate = system_id_dataset.sample_rate 

446 sysid_params.frame_size = system_id_dataset[environment_name].sysid_frame_size 

447 sysid_params.averaging_type = system_id_dataset[environment_name].sysid_averaging_type 

448 sysid_params.noise_averages = system_id_dataset[environment_name].sysid_noise_averages 

449 sysid_params.averages = system_id_dataset[environment_name].sysid_averages 

450 if system_id_dataset[environment_name].sysid_averaging_type != 'Linear': 

451 sysid_params.exponential_averaging_coefficient = system_id_dataset[environment_name].sysid_exponential_averaging_coefficient 

452 sysid_params.estimator = system_id_dataset[environment_name].sysid_estimator 

453 sysid_params.level = system_id_dataset[environment_name].sysid_level 

454 sysid_params.level_ramp_time = system_id_dataset[environment_name].sysid_level_ramp_time 

455 sysid_params.signal_type = system_id_dataset[environment_name].sysid_signal_type 

456 if system_id_dataset[environment_name].sysid_signal_type == 'Burst Random': 

457 sysid_params.burst_on = system_id_dataset[environment_name].sysid_burst_on 

458 sysid_params.pretrigger = system_id_dataset[environment_name].sysid_pretrigger 

459 sysid_params.burst_ramp_fraction = system_id_dataset[environment_name].burst_ramp_fraction 

460 sysid_params.window = system_id_dataset[environment_name].sysid_window 

461 sysid_params.overlap = system_id_dataset[environment_name].sysid_overlap 

462 environment_index = system_id_dataset['environment_names'][...]==environment_name 

463 active_channel_indices = system_id_dataset['environment_active_channels'][...,environment_index][:,0].astype(bool) 

464 channel_names = np.array([str(system_id_dataset['channels']['node_number'][i])+str(system_id_dataset['channels']['node_direction'][i]) for i in range(system_id_dataset.dimensions['response_channels'].size)])[active_channel_indices] 

465 control_channels = channel_names[system_id_dataset[environment_name]['control_channel_indices'][:]] 

466 sysid_params.control_channel = control_channels 

467 excitation_indices = np.array([system_id_dataset['channels']['feedback_device'][i] != '' for i in range(system_id_dataset.dimensions['response_channels'].size)]) 

468 excitation_channels = channel_names[excitation_indices] 

469 sysid_params.excitation_channel = excitation_channels 

470 if 'response_transformation_matrix' in system_id_dataset[environment_name].variables: 

471 sysid_params.control_transformation_matrix = system_id_dataset[environment_name]['response_transformation_matrix'][...] 

472 if 'reference_transformation_matrix' in system_id_dataset[environment_name].variables: 

473 sysid_params.excitation_transformation_matrix = system_id_dataset[environment_name]['reference_transformation_matrix'][...] 

474 return sysid_params 

475 

476def from_rattlesnake_random_parameters(dataset_name, random_dataset, environment_name=None, descriptive_name=''): 

477 """Gets random vibration parameters from a NetCDF4 streaming file 

478 

479 Parameters 

480 ---------- 

481 dataset_name : str 

482 The name of the dataset to be generated 

483 random_dataset : netCDF4.Dataset 

484 A dataset created by loading the streaming file 

485 environment_name : str, optional 

486 Name of the environment. Must be specified if more than one environment 

487 exists in the file 

488 descriptive_name : str, optional 

489 A description for the dataset that will be generated, by default ''. 

490 

491 Returns 

492 ------- 

493 escdf.Dataset 

494 A dataset with the type "rattlesnake_random_control_parameters" containing 

495 random vibration control parameters. 

496 """ 

497 if escdf is None: 

498 raise ImportError('Could not import module `escdf`, unable to create dataset.') 

499 if environment_name is None: 

500 environment_names = [name for name in random_dataset.groups if name != 'channels'] 

501 if len(environment_names) != 1: 

502 raise ValueError('Found {:} environments in the file. Could not select correctly.'.format(len(environment_names))) 

503 environment_name = environment_names[0] 

504 params = escdf.classes.rattlesnake_random_control_parameters(dataset_name, 

505 descriptive_name) 

506 params.sample_rate = random_dataset.sample_rate 

507 environment_index = random_dataset['environment_names'][...]==environment_name 

508 active_channel_indices = random_dataset['environment_active_channels'][...,environment_index][:,0].astype(bool) 

509 channel_names = np.array([str(random_dataset['channels']['node_number'][i])+str(random_dataset['channels']['node_direction'][i]) for i in range(random_dataset.dimensions['response_channels'].size)])[active_channel_indices] 

510 control_channels = channel_names[random_dataset[environment_name]['control_channel_indices'][:]] 

511 params.control_channel = control_channels 

512 excitation_indices = np.array([random_dataset['channels']['feedback_device'][i] != '' for i in range(random_dataset.dimensions['response_channels'].size)]) 

513 excitation_channels = channel_names[excitation_indices] 

514 params.excitation_channel = excitation_channels 

515 params.samples_per_frame = random_dataset[environment_name].samples_per_frame 

516 params.control_frequency_lines = random_dataset[environment_name]['specification_frequency_lines'][...] 

517 params.test_level_ramp_time = random_dataset[environment_name].test_level_ramp_time 

518 params.cpsd_overlap = random_dataset[environment_name].cpsd_overlap 

519 params.cola_window = random_dataset[environment_name].cola_window 

520 params.update_tf_during_control = random_dataset[environment_name].update_tf_during_control 

521 params.cola_overlap = random_dataset[environment_name].cola_overlap 

522 params.cola_window_exponent = random_dataset[environment_name].cola_window_exponent 

523 params.frames_in_cpsd = random_dataset[environment_name].frames_in_cpsd 

524 params.cpsd_window = random_dataset[environment_name].cpsd_window 

525 params.control_python_script = random_dataset[environment_name].control_python_script 

526 params.control_python_function = random_dataset[environment_name].control_python_function 

527 params.control_python_function_type = random_dataset[environment_name].control_python_function_type 

528 params.control_python_function_parameters = random_dataset[environment_name].control_python_function_parameters 

529 params.allow_automatic_aborts = random_dataset[environment_name].allow_automatic_aborts 

530 units = [f'({unit})^2/Hz' for unit in np.array( 

531 [random_dataset['channels']['unit'][i] != '' for i in range(random_dataset.dimensions['response_channels'].size)] 

532 )[active_channel_indices][random_dataset[environment_name]['control_channel_indices'][:]]] 

533 params.specification_unit = units 

534 if not np.all(np.isnan(random_dataset[environment_name]['specification_warning_matrix'][...])): 

535 params.specification_warning_matrix = random_dataset[environment_name]['specification_warning_matrix'][...] 

536 if not np.all(np.isnan(random_dataset[environment_name]['specification_abort_matrix'][...])): 

537 params.specification_abort_matrix = random_dataset[environment_name]['specification_abort_matrix'][...] 

538 if 'response_transformation_matrix' in random_dataset[environment_name].variables: 

539 params.control_transformation_matrix = random_dataset[environment_name]['response_transformation_matrix'][...] 

540 if 'reference_transformation_matrix' in random_dataset[environment_name].variables: 

541 params.excitation_transformation_matrix = random_dataset[environment_name]['reference_transformation_matrix'][...] 

542 return params 

543 

544def from_rattlesnake_transient_parameters(dataset_name, transient_dataset, environment_name=None, descriptive_name=''): 

545 """Gets transient vibration parameters from a NetCDF4 streaming file 

546 

547 Parameters 

548 ---------- 

549 dataset_name : str 

550 The name of the dataset to be generated 

551 transient_dataset : netCDF4.Dataset 

552 A dataset created by loading the streaming file 

553 environment_name : str, optional 

554 Name of the environment. Must be specified if more than one environment 

555 exists in the file 

556 descriptive_name : str, optional 

557 A description for the dataset that will be generated, by default ''. 

558 

559 Returns 

560 ------- 

561 escdf.Dataset 

562 A dataset with the type "rattlesnake_transient_control_parameters" containing 

563 random vibration control parameters. 

564 """ 

565 if escdf is None: 

566 raise ImportError('Could not import module `escdf`, unable to create dataset.') 

567 if environment_name is None: 

568 environment_names = [name for name in transient_dataset.groups if name != 'channels'] 

569 if len(environment_names) != 1: 

570 raise ValueError('Found {:} environments in the file. Could not select correctly.'.format(len(environment_names))) 

571 environment_name = environment_names[0] 

572 params = escdf.classes.rattlesnake_transient_control_parameters(dataset_name, 

573 descriptive_name) 

574 params.sample_rate = transient_dataset.sample_rate 

575 environment_index = transient_dataset['environment_names'][...]==environment_name 

576 active_channel_indices = transient_dataset['environment_active_channels'][...,environment_index][:,0].astype(bool) 

577 channel_names = np.array([str(transient_dataset['channels']['node_number'][i])+str(transient_dataset['channels']['node_direction'][i]) for i in range(transient_dataset.dimensions['response_channels'].size)])[active_channel_indices] 

578 control_channels = channel_names[transient_dataset[environment_name]['control_channel_indices'][:]] 

579 params.control_channel = control_channels 

580 excitation_indices = np.array([transient_dataset['channels']['feedback_device'][i] != '' for i in range(transient_dataset.dimensions['response_channels'].size)]) 

581 excitation_channels = channel_names[excitation_indices] 

582 params.excitation_channel = excitation_channels 

583 params.test_level_ramp_time = transient_dataset[environment_name].test_level_ramp_time 

584 params.control_python_script = transient_dataset[environment_name].control_python_script 

585 params.control_python_function = transient_dataset[environment_name].control_python_function 

586 params.control_python_function_type = transient_dataset[environment_name].control_python_function_type 

587 params.control_python_function_parameters = transient_dataset[environment_name].control_python_function_parameters 

588 if 'response_transformation_matrix' in transient_dataset[environment_name].variables: 

589 params.control_transformation_matrix = transient_dataset[environment_name]['response_transformation_matrix'][...] 

590 if 'reference_transformation_matrix' in transient_dataset[environment_name].variables: 

591 params.excitation_transformation_matrix = transient_dataset[environment_name]['reference_transformation_matrix'][...] 

592 return params 

593 

594def datasets_from_rattlesnake_system_identification( 

595 test_name_slug, 

596 sysid_streaming_data, 

597 sysid_results_data, 

598 verbose=False): 

599 """ 

600 Converts system identification data from Rattlesnake into ESCDF Datasets 

601 

602 Parameters 

603 ---------- 

604 test_name_slug : str 

605 A string identifier that will be prepended to the dataset names 

606 sysid_streaming_data : str or netCDF4.Dataset 

607 A string pointing to a netCDF4 file or the netCDF4 file loaded as a 

608 netCDF4.Dataset object. The file should contain streaming data from 

609 a System Identification phase of the Rattlesnake controller. 

610 sysid_results_data : str or netCDF4.Dataset 

611 A string pointing to a netCDF4 file or the netCDF4 file loaded as a 

612 netCDF4.Dataset object. The file should contain spectral data from 

613 a System Identification phase of the Rattlesnake controller. 

614 verbose : bool, optional 

615 If True, progress will be printed. The default is False. 

616 

617 Raises 

618 ------ 

619 ImportError 

620 If the escdf package is not available. 

621 

622 Returns 

623 ------- 

624 ESCDF Datasets 

625 A nested Tuple of ESCDF datasets. The first index is the metadata and 

626 the second index is the data. Metadata includes system identification 

627 parameters and channel table. Data includes FRFs, Noise and System ID 

628 Response CPSDs, Noise and System ID Drive CPSDs, and Multiple Coherence. 

629 

630 """ 

631 if escdf is None: 

632 raise ImportError('Could not import module `escdf`, unable to create dataset.') 

633 if isinstance(sysid_streaming_data,str): 

634 sysid_streaming_data = nc4.Dataset(sysid_streaming_data) 

635 if isinstance(sysid_results_data,str): 

636 sysid_results_data = nc4.dataset(sysid_results_data) 

637 

638 # Extract the data 

639 if verbose: 

640 print('Reading Time Data') 

641 noise_time_history, channel_table = read_rattlesnake_output( 

642 sysid_streaming_data,read_variable = 'time_data') 

643 sysid_time_history, _ = read_rattlesnake_output( 

644 sysid_streaming_data,read_variable = 'time_data_1') 

645 if verbose: 

646 print('Reading Spectral Data') 

647 (sysid_frfs, sysid_response_cpsd, sysid_drive_cpsd, 

648 sysid_response_noise_cpsd, sysid_drive_noise_cpsd, sysid_coherence) = ( 

649 read_system_id_nc4(sysid_results_data)) 

650 

651 if verbose: 

652 print('Creating ESCDF Data Objects') 

653 # Create escdf data objects 

654 esnoise_time_history = from_data( 

655 f'{test_name_slug}_Noise_Time_History',noise_time_history) 

656 esnoise_time_history.ordinate_unit = [comment.split('::')[1].strip() for comment in noise_time_history.flatten().comment1] 

657 esnoise_time_history.abscissa_unit = 's' 

658 

659 essysid_time_history = from_data( 

660 f'{test_name_slug}_SysID_Time_History',sysid_time_history) 

661 essysid_time_history.ordinate_unit = [comment.split('::')[1].strip() for comment in noise_time_history.flatten().comment1] 

662 essysid_time_history.abscissa_unit = 's' 

663 

664 esspectral_data = [] 

665 for data,joiner,appender,label in zip([sysid_frfs,sysid_response_cpsd,sysid_response_noise_cpsd, 

666 sysid_drive_cpsd,sysid_drive_noise_cpsd], 

667 ['/','*','*','*','*'], 

668 ['','/Hz','/Hz','/Hz','/Hz'], 

669 ['SysID_FRFs','SysID_Response_CPSD', 

670 'SysID_Response_Noise_CPSD', 

671 'SysID_Drive_CPSD', 

672 'SysID_Drive_Noise_CPSD']): 

673 data = data.flatten() 

674 esobj = from_data(f'{test_name_slug}_{label}',data) 

675 try: 

676 if np.all(data.comment1[0] == data.comment1): # All units are the same 

677 comment = data.comment1[0] 

678 esobj.ordinate_unit = joiner.join(['('+value.split('::')[1].strip()+')' for value in comment.split('//')])+appender 

679 else: 

680 esobj.ordinate_unit = [joiner.join(['('+value.split('::')[1].strip()+')' for value in comment.split('//')])+appender for comment in data.comment1] 

681 except IndexError: 

682 print(f'Could not automatically assign units to {esobj.name}') 

683 esobj.abscissa_unit = 'Hz' 

684 esspectral_data.append(esobj) 

685 

686 essysid_coherence = from_data( 

687 f'{test_name_slug}_SysID_Coherence',sysid_coherence) 

688 essysid_coherence.ordinate_unit = '-' 

689 essysid_coherence.abscissa_unit = 'Hz' 

690 

691 if verbose: 

692 print('Creating ESCDF Metadata Objects') 

693 # Channel table 

694 eschan = from_rattlesnake_channel_info( 

695 f'{test_name_slug}_Channel_Table', channel_table) 

696 

697 # System Identification Parameters 

698 essysid = from_rattlesnake_system_id_parameters( 

699 f'{test_name_slug}_Params', sysid_streaming_data) 

700 

701 return ((essysid, eschan), 

702 tuple([esnoise_time_history, essysid_time_history] + esspectral_data 

703 + [essysid_coherence])) 

704 

705def datasets_from_rattlesnake_random_vibration(test_name_slug, 

706 streaming_data, 

707 spectral_data, 

708 verbose=False): 

709 """ 

710 Converts random vibration data from Rattlesnake into ESCDF Datasets 

711 

712 Parameters 

713 ---------- 

714 test_name_slug : str 

715 A string identifier that will be prepended to the dataset names 

716 streaming_data : str or netCDF4.Dataset 

717 A string pointing to a netCDF4 file or the netCDF4 file loaded as a 

718 netCDF4.Dataset object. The file should contain streaming data from 

719 a random vibration phase of the Rattlesnake controller. 

720 spectral_data : str or netCDF4.Dataset 

721 A string pointing to a netCDF4 file or the netCDF4 file loaded as a 

722 netCDF4.Dataset object. The file should contain spectral data from 

723 a random vibration phase of the Rattlesnake controller. 

724 verbose : bool, optional 

725 If True, progress will be printed. The default is False. 

726 

727 Raises 

728 ------ 

729 ImportError 

730 If the escdf package is not available. 

731 

732 Returns 

733 ------- 

734 ESCDF Datasets 

735 A nested tuple of ESCDF datasets. The first index is the metadata and 

736 the second index is the data. Metadata includes specification, random 

737 vibration parameters, and channel table. Data includes Response CPSD 

738 and Drive CPSD. 

739 """ 

740 

741 if escdf is None: 

742 raise ImportError('Could not import module `escdf`, unable to create dataset.') 

743 if isinstance(streaming_data,str): 

744 streaming_data = nc4.Dataset(streaming_data) 

745 if isinstance(spectral_data,str): 

746 spectral_data = nc4.dataset(spectral_data) 

747 

748 # Extract the data 

749 time_dataset_names = [variable for variable in streaming_data.variables if 'time_data' in variable] 

750 if verbose: 

751 print('Reading Time Data') 

752 time_datasets = [] 

753 for name in time_dataset_names: 

754 time_history, channel_table = read_rattlesnake_output( 

755 streaming_data,read_variable = name) 

756 time_datasets.append(time_history) 

757 

758 if verbose: 

759 print('Reading Spectral Data') 

760 response_cpsd,spec_cpsd,drive_cpsd = read_random_spectral_data(spectral_data) 

761 

762 if verbose: 

763 print('Creating ESCDF Data Objects') 

764 esdata = [] 

765 for i,data in enumerate(time_datasets): 

766 this_esdata = from_data( 

767 f'{test_name_slug}_Time_History{"_"+str(i) if len(time_datasets)>1 else ""}', 

768 data) 

769 this_esdata.ordinate_unit = [comment.split('::')[1].strip() for comment in data.flatten().comment1] 

770 this_esdata.abscissa_unit = 's' 

771 esdata.append(this_esdata) 

772 

773 esspec = [] 

774 for i,(data, label) in enumerate(zip([spec_cpsd, response_cpsd, drive_cpsd], 

775 ['Specification_CPSD','Response_CPSD','Drive_CPSD'])): 

776 data = data.flatten() 

777 this_esdata = from_data(f'{test_name_slug}_{label}',data) 

778 try: 

779 if np.all(data.comment1[0] == data.comment1): # All units are the same 

780 comment = data.comment1[0] 

781 this_esdata.ordinate_unit = '*'.join(['('+value.split('::')[1].strip()+')' for value in comment.split('//')])+'/Hz' 

782 else: 

783 this_esdata.ordinate_unit = ['*'.join(['('+value.split('::')[1].strip()+')' for value in comment.split('//')])+'/Hz' for comment in data.comment1] 

784 except IndexError: 

785 print(f'Could not automatically assign units to {this_esdata.name}') 

786 this_esdata.abscissa_unit = 'Hz' 

787 if i == 0: 

788 esspec.append(this_esdata) 

789 else: 

790 esdata.append(this_esdata) 

791 

792 if verbose: 

793 print('Creating ESCDF Metadata Objects') 

794 

795 # Channel table 

796 eschan = from_rattlesnake_channel_info( 

797 f'{test_name_slug}_Channel_Table', channel_table) 

798 # Random Vibration Parameters 

799 esrand = from_rattlesnake_random_parameters( 

800 f'{test_name_slug}_Params', streaming_data) 

801 

802 # Pull the spec into metadata 

803 return ((esrand, eschan, esspec[0]), tuple(esdata)) 

804 

805def datasets_from_rattlesnake_transient_vibration(test_name_slug, 

806 streaming_data, 

807 control_data, 

808 verbose=False): 

809 """ 

810 Converts transient vibration data from Rattlesnake into ESCDF Datasets 

811 

812 Parameters 

813 ---------- 

814 test_name_slug : str 

815 A string identifier that will be prepended to the dataset names 

816 streaming_data : str or netCDF4.Dataset 

817 A string pointing to a netCDF4 file or the netCDF4 file loaded as a 

818 netCDF4.Dataset object. The file should contain streaming data from 

819 a random vibration phase of the Rattlesnake controller. 

820 control_data : str or netCDF4.Dataset 

821 A string pointing to a netCDF4 file or the netCDF4 file loaded as a 

822 netCDF4.Dataset object. The file should contain control data from 

823 a transient vibration phase of the Rattlesnake controller. 

824 verbose : bool, optional 

825 If True, progress will be printed. The default is False. 

826 

827 Raises 

828 ------ 

829 ImportError 

830 If the escdf package is not available. 

831 

832 Returns 

833 ------- 

834 ESCDF Datasets 

835 A nested tuple of ESCDF datasets. The first index is the metadata and 

836 the second index is the data. Metadata includes specification, transient 

837 vibration parameters, and channel table. Data includes Response 

838 and Drive time histories. 

839 """ 

840 

841 if escdf is None: 

842 raise ImportError('Could not import module `escdf`, unable to create dataset.') 

843 if isinstance(streaming_data,str): 

844 streaming_data = nc4.Dataset(streaming_data) 

845 if isinstance(control_data,str): 

846 control_data = nc4.dataset(control_data) 

847 

848 # Extract the data 

849 time_dataset_names = [variable for variable in streaming_data.variables if 'time_data' in variable] 

850 if verbose: 

851 print('Reading Time Data') 

852 time_datasets = [] 

853 for name in time_dataset_names: 

854 time_history, channel_table = read_rattlesnake_output( 

855 streaming_data,read_variable = name) 

856 time_datasets.append(time_history) 

857 

858 if verbose: 

859 print('Reading Control Data') 

860 response_th,spec_th,drive_th = read_transient_control_data(control_data) 

861 

862 if verbose: 

863 print('Creating ESCDF Data Objects') 

864 esdata = [] 

865 for i,data in enumerate(time_datasets): 

866 this_esdata = from_data( 

867 f'{test_name_slug}_Time_History{"_"+str(i) if len(time_datasets)>1 else ""}', 

868 data) 

869 this_esdata.ordinate_unit = [comment.split('::')[1].strip() for comment in data.flatten().comment1] 

870 this_esdata.abscissa_unit = 's' 

871 esdata.append(this_esdata) 

872 

873 esspec = [] 

874 for i,(data, label) in enumerate(zip([spec_th, response_th, drive_th], 

875 ['Specification_Time_History','Control_Time_History','Drive_Time_History'])): 

876 data = data.flatten() 

877 this_esdata = from_data(f'{test_name_slug}_{label}',data) 

878 try: 

879 if np.all(data.comment1[0] == data.comment1): # All units are the same 

880 comment = data.comment1[0] 

881 this_esdata.ordinate_unit = '('+comment.split('::')[1].strip()+')' 

882 else: 

883 this_esdata.ordinate_unit = ['('+comment.split('::')[1].strip()+')' for comment in data.comment1] 

884 except IndexError: 

885 print(f'Could not automatically assign units to {this_esdata.name}') 

886 this_esdata.abscissa_unit = 's' 

887 if i == 0: 

888 esspec.append(this_esdata) 

889 else: 

890 esdata.append(this_esdata) 

891 

892 if verbose: 

893 print('Creating ESCDF Metadata Objects') 

894 

895 # Channel table 

896 eschan = from_rattlesnake_channel_info( 

897 f'{test_name_slug}_Channel_Table', channel_table) 

898 # Transient Vibration Parameters 

899 estrans = from_rattlesnake_transient_parameters( 

900 f'{test_name_slug}_Params', streaming_data) 

901 

902 # Pull the spec into metadata 

903 return ((estrans, eschan, esspec[0]), tuple(esdata)) 

904 

905def datasets_from_rattlesnake_modal( 

906 test_name_slug, 

907 modal_results_data, 

908 mode_fits = None, 

909 resynthesized_frfs = None, 

910 verbose=False): 

911 """ 

912 Converts modal data from Rattlesnake into ESCDF Datasets 

913 

914 Parameters 

915 ---------- 

916 test_name_slug : str 

917 A string identifier that will be prepended to the dataset names 

918 modal_results_data : str or netCDF4.Dataset 

919 A string pointing to a netCDF4 file or the netCDF4 file loaded as a 

920 netCDF4.Dataset object. The file should contain modal results from a 

921 Rattlesnake modal environment. 

922 mode_fits : ShapeArray 

923 A ShapeArray object containing fit modes. If not supplied, no mode 

924 dataset will be created. 

925 resynthesized_frfs : TransferFunctionArray 

926 A TransferFunctionArray object containing resynthesized FRFs. If not 

927 supplied, no resynthesized frf dataset will be created. 

928 verbose : bool, optional 

929 If True, progress will be printed. The default is False. 

930 

931 Raises 

932 ------ 

933 ImportError 

934 If the escdf package is not available. 

935 

936 Returns 

937 ------- 

938 ESCDF Datasets 

939 A nested Tuple of ESCDF datasets. The first index is the metadata and 

940 the second index is the data. Metadata includes modal 

941 parameters and channel table. Data includes FRFs, Coherence, Fit Modes 

942 (if supplied) and resynthesized FRFs (if supplied). 

943 

944 """ 

945 if escdf is None: 

946 raise ImportError('Could not import module `escdf`, unable to create dataset.') 

947 if isinstance(modal_results_data,str): 

948 modal_results_data = nc4.Dataset(modal_results_data) 

949 

950 th, frf, mcoh, channel_table = read_modal_data(modal_results_data) 

951 th_joined = join(th) 

952 th_joined.comment1 = th[0].comment1 

953 frf = frf.flatten() 

954 

955 

956 if verbose: 

957 print('Creating ESCDF Data Objects') 

958 

959 es_th = from_data( 

960 f'{test_name_slug}_Time_History',th) 

961 es_th.ordinate_unit = [comment.split('::')[1].strip() for comment in th.flatten().comment1] 

962 es_th.abscissa_unit = 's' 

963 

964 es_frf = from_data(f'{test_name_slug}_FRF',frf) 

965 es_frf.abscissa_unit = 'Hz' 

966 try: 

967 if np.all(frf.comment1[0] == frf.comment1): # All units are the same 

968 comment = frf.comment1[0] 

969 es_frf.ordinate_unit = '/'.join(['('+value.split('::')[1].strip()+')' for value in comment.split('//')]) 

970 else: 

971 es_frf.ordinate_unit = ['/'.join(['('+value.split('::')[1].strip()+')' for value in comment.split('//')]) for comment in frf.comment1] 

972 except IndexError: 

973 print(f'Could not automatically assign units to {es_frf.name}') 

974 

975 es_coh = from_data( 

976 f'{test_name_slug}_Coherence',mcoh) 

977 es_coh.ordinate_unit = '-' 

978 es_coh.abscissa_unit = 'Hz' 

979 

980 additional_data = [] 

981 if mode_fits is not None: 

982 es_mode = from_shape(f'{test_name_slug}_Fit_Modes',mode_fits) 

983 additional_data.append(es_mode) 

984 

985 if resynthesized_frfs is not None: 

986 resynthesized_frfs = resynthesized_frfs.flatten() 

987 es_frf_res = from_data(f'{test_name_slug}_Resynthesized_FRFs',resynthesized_frfs) 

988 es_frf_res.abscissa_unit = 'Hz' 

989 try: 

990 frf_match = frf[resynthesized_frfs.coordinate] 

991 if np.all(frf_match.comment1[0] == frf_match.comment1): # All units are the same 

992 comment = frf_match.comment1[0] 

993 es_frf_res.ordinate_unit = '/'.join(['('+value.split('::')[1].strip()+')' for value in comment.split('//')]) 

994 else: 

995 es_frf_res.ordinate_unit = ['/'.join(['('+value.split('::')[1].strip()+')' for value in comment.split('//')]) for comment in frf_match.comment1] 

996 except (ValueError,IndexError): 

997 print('Could not automatically assign units to {es_frf_res.name}') 

998 additional_data.append(es_frf_res) 

999 

1000 if verbose: 

1001 print('Creating ESCDF Metadata Objects') 

1002 # Channel table 

1003 eschan = from_rattlesnake_channel_info( 

1004 f'{test_name_slug}_Channel_Table', channel_table) 

1005 

1006 # System Identification Parameters 

1007 esmod = from_rattlesnake_modal_parameters( 

1008 f'{test_name_slug}_Params', modal_results_data) 

1009 

1010 return ((esmod, eschan), 

1011 tuple([es_th, es_frf, es_coh] + additional_data)) 

1012 

1013class ESCDFSettingDialog(QtWidgets.QDialog): 

1014 def __init__(self, data_type, initial_value, message, parent=None): 

1015 super().__init__(parent) 

1016 self.setWindowTitle("Set Settings") 

1017 self.data_type = data_type 

1018 self.value = None # To store the final value if OK is pressed 

1019 

1020 # Create layout 

1021 layout = QtWidgets.QVBoxLayout() 

1022 

1023 # Add message label 

1024 self.message_label = QtWidgets.QLabel(message) 

1025 layout.addWidget(self.message_label) 

1026 

1027 # Create the appropriate input widget based on data type 

1028 if data_type == int: 

1029 self.input_widget = QtWidgets.QSpinBox() 

1030 self.input_widget.setMaximum(999999999) 

1031 self.input_widget.setMinimum(-999999999) 

1032 self.input_widget.setValue(initial_value) 

1033 elif data_type == float: 

1034 self.input_widget = QtWidgets.QDoubleSpinBox() 

1035 self.input_widget.setMaximum(999999999.0) 

1036 self.input_widget.setMinimum(-999999999.0) 

1037 self.input_widget.setValue(initial_value) 

1038 elif data_type == str: 

1039 self.input_widget = QtWidgets.QTextEdit() 

1040 self.input_widget.setText(initial_value) 

1041 elif data_type == list: 

1042 self.input_widget = QtWidgets.QComboBox() 

1043 self.input_widget.addItems(initial_value) 

1044 self.input_widget.setCurrentIndex(0) # Default to first item 

1045 else: 

1046 raise ValueError("Unsupported data type") 

1047 

1048 layout.addWidget(self.input_widget) 

1049 

1050 # Add OK and Cancel buttons 

1051 button_layout = QtWidgets.QHBoxLayout() 

1052 self.ok_button = QtWidgets.QPushButton("OK") 

1053 self.cancel_button = QtWidgets.QPushButton("Cancel") 

1054 button_layout.addWidget(self.ok_button) 

1055 button_layout.addWidget(self.cancel_button) 

1056 layout.addLayout(button_layout) 

1057 

1058 # Connect buttons to their respective slots 

1059 self.ok_button.clicked.connect(self.accept) 

1060 self.cancel_button.clicked.connect(self.reject) 

1061 

1062 self.setLayout(layout) 

1063 

1064 def get_input_value(self): 

1065 """Retrieve the value from the input widget.""" 

1066 if self.data_type == int: 

1067 return self.input_widget.value() 

1068 elif self.data_type == float: 

1069 return self.input_widget.value() 

1070 elif self.data_type == str: 

1071 return self.input_widget.toPlainText() 

1072 elif self.data_type == list: 

1073 return self.input_widget.currentIndex() 

1074 else: 

1075 return None 

1076 

1077 @staticmethod 

1078 def get_value(data_type, initial_value, message, parent=None): 

1079 """ 

1080 Static method to create the dialog, show it, and return the value if OK is pressed. 

1081 Returns None if Cancel is pressed. 

1082 """ 

1083 dialog = ESCDFSettingDialog(data_type, initial_value, message, parent) 

1084 result = dialog.exec_() # Show the dialog and wait for user interaction 

1085 if result == QtWidgets.QDialog.Accepted: 

1086 return dialog.get_input_value() 

1087 else: 

1088 return None 

1089 

1090# def trace_callback(func): 

1091# """ 

1092# A decorator to trace the execution of callback functions. 

1093# Prints the function name before calling it. 

1094# """ 

1095# def wrapper(*args, **kwargs): 

1096# print(f"Callback triggered: {func.__name__}") 

1097# try: 

1098# return func(*args, **kwargs) 

1099# except Exception as e: 

1100# print(f"Caught: {e}") 

1101# return wrapper 

1102 

1103class ESCDFTableModel(QtCore.QAbstractTableModel): 

1104 

1105 def __init__(self, escdf_property, indices, parent=None): 

1106 super().__init__(parent) 

1107 self.prop = escdf_property 

1108 self.indices = indices 

1109 self.table_dimensions = [dimension_size for index, dimension_size in zip(self.indices, self.prop.shape) if index < 0] 

1110 if len(self.table_dimensions) > 2: 

1111 raise ValueError('Only two dimensions can be scrollable') 

1112 while len(self.table_dimensions) < 2: 

1113 self.table_dimensions.append(1) 

1114 

1115 def rowCount(self, parent=QtCore.QModelIndex()): 

1116 return self.table_dimensions[0] 

1117 

1118 def columnCount(self, parent=QtCore.QModelIndex()): 

1119 return self.table_dimensions[1] 

1120 

1121 def get_slice(self, row, column): 

1122 dynamic_variables = [row, column] 

1123 output_slice = [] 

1124 for index in self.indices: 

1125 if index < 0: 

1126 output_slice.append(dynamic_variables.pop(0)) 

1127 else: 

1128 output_slice.append(index) 

1129 return tuple(output_slice) 

1130 

1131 def data(self, index, role=QtCore.Qt.DisplayRole): 

1132 if role == QtCore.Qt.DisplayRole: 

1133 row = index.row() 

1134 col = index.column() 

1135 

1136 output_slice = self.get_slice(row, col) 

1137 

1138 # Fetch the data lazily 

1139 value = self.prop[output_slice] 

1140 return str(value) 

1141 return None 

1142 

1143 def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole): 

1144 if role == QtCore.Qt.DisplayRole: 

1145 # Return 0-based counting for headers 

1146 return str(section) 

1147 return None 

1148 

1149class DimensionSpinBox(QtWidgets.QSpinBox): 

1150 def textFromValue(self, value): 

1151 # Override to display ":" for -1 

1152 if value == -1: 

1153 return ":" 

1154 return str(value) 

1155 

1156 def valueFromText(self, text): 

1157 # Override to interpret ":" as -1 

1158 if text == ":": 

1159 return -1 

1160 return int(text) 

1161 

1162 def validate(self, text, pos): 

1163 # Allow ":" as valid input, along with numeric values 

1164 if text == ":": 

1165 return QtGui.QValidator.Acceptable, text, pos 

1166 try: 

1167 int(text) # Check if the text can be converted to an integer 

1168 return QtGui.QValidator.Acceptable, text, pos 

1169 except ValueError: 

1170 return QtGui.QValidator.Invalid, text, pos 

1171 

1172class ESCDFVisualizer(QMainWindow): 

1173 """An interactive window allowing users to explore an ESCDF file""" 

1174 

1175 monofont = QtGui.QFont("monospace") 

1176 monofont.setStyleHint(QtGui.QFont.TypeWriter) 

1177 

1178 def __init__(self, escdf_file=None): 

1179 """Create an ESCDF Visualizer Window to explore an ESCDF file. 

1180  

1181 A filename or ESCDF object can be passed as an argment, or it can be 

1182 loaded at a later point. 

1183  

1184 Parameters 

1185 ---------- 

1186 escdf_file : str or ESCDF, optional 

1187 The file or ESCDF object to explore. If not passed, one can be 

1188 loaded through the graphical user interface. 

1189 

1190 Returns 

1191 ------- 

1192 None. 

1193 

1194 """ 

1195 if escdf is None: 

1196 raise ImportError('Could not import module `escdf`, unable to use Visualizer.') 

1197 super().__init__() 

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

1199 os.path.abspath(__file__))), 'escdf_visualizer.ui'), self) 

1200 self.activity_selector : QtWidgets.QListWidget 

1201 self.data_selector : QtWidgets.QListWidget 

1202 self.metadata_selector : QtWidgets.QListWidget 

1203 self.alldata_selector : QtWidgets.QListWidget 

1204 self.allmetadata_selector : QtWidgets.QListWidget 

1205 self.data_property_selector : QtWidgets.QListWidget 

1206 self.metadata_property_selector : QtWidgets.QListWidget 

1207 self.activity_property_string : QtWidgets.QLabel 

1208 self.data_property_string : QtWidgets.QLabel 

1209 self.export_mat_button : QtWidgets.QPushButton 

1210 self.export_npz_button : QtWidgets.QPushButton 

1211 self.plot_coordinate_button : QtWidgets.QPushButton 

1212 self.plot_data_against_metadata_button : QtWidgets.QPushButton 

1213 self.plot_data_button : QtWidgets.QPushButton 

1214 self.plot_data_on_geometry_button : QtWidgets.QPushButton 

1215 self.plot_geometry_button : QtWidgets.QPushButton 

1216 self.plot_shape_button : QtWidgets.QPushButton 

1217 self.tab_widget : QtWidgets.QTabWidget 

1218 for widget in [self.activity_property_string, self.data_property_string]: 

1219 widget.setFont(ESCDFVisualizer.monofont) 

1220 self.setWindowTitle('ESCDF Visualizer') 

1221 self.escdf : escdf.ESCDF 

1222 self.escdf = None 

1223 self.node_size : int 

1224 self.node_size = 5 

1225 self.line_width : int 

1226 self.line_width = 2 

1227 self.label_text_size : int 

1228 self.label_text_size = 10 

1229 self.arrow_size : float 

1230 self.arrow_size = 0.1 

1231 self.opacity : float 

1232 self.opacity = 1.0 

1233 self.undeformed_opacity : float 

1234 self.undeformed_opacity = 0.1 

1235 self.transient_start_time : float 

1236 self.transient_start_time = -10000000.0 

1237 self.transient_end_time : float 

1238 self.transient_end_time = 10000000.0 

1239 self.data_array = [] 

1240 self.metadata_array = [] 

1241 self.data_dimension_spinboxes = [] 

1242 self.metadata_dimension_spinboxes = [] 

1243 if escdf_file is not None: 

1244 self.load(escdf_file) 

1245 self.guiplots = [] 

1246 self.connect_callbacks() 

1247 self.update_viz_buttons() 

1248 self.show() 

1249 

1250 # @trace_callback 

1251 def connect_callbacks(self): 

1252 self.actionLoad_File.triggered.connect(self.select_file) 

1253 self.actionSet_Node_Size.triggered.connect(self.set_node_size) 

1254 self.actionSet_Line_Width.triggered.connect(self.set_line_width) 

1255 self.actionSet_Label_Text_Size.triggered.connect(self.set_label_text_size) 

1256 self.actionSet_Arrow_Size.triggered.connect(self.set_arrow_size) 

1257 self.actionSet_Opacity.triggered.connect(self.set_opacity) 

1258 self.actionSet_Undeformed_Opacity.triggered.connect(self.set_undeformed_opacity) 

1259 self.actionSet_Transient_Start_Time.triggered.connect(self.set_transient_start_time) 

1260 self.actionSet_Transient_End_Time.triggered.connect(self.set_transient_end_time) 

1261 self.activity_selector.currentItemChanged.connect(self.update_activity_data) 

1262 self.data_selector.currentItemChanged.connect(self.update_data_data) 

1263 self.data_selector.itemDoubleClicked.connect(self.go_to_data) 

1264 self.metadata_selector.itemDoubleClicked.connect(self.go_to_metadata) 

1265 self.metadata_selector.currentItemChanged.connect(self.update_metadata_data) 

1266 self.data_property_selector.currentItemChanged.connect(self.update_data_property) 

1267 self.metadata_property_selector.currentItemChanged.connect(self.update_metadata_property) 

1268 self.plot_coordinate_button.clicked.connect(self.plot_coordinate) 

1269 self.plot_data_against_metadata_button.clicked.connect(self.plot_data_against_metadata) 

1270 self.plot_data_button.clicked.connect(self.plot_data) 

1271 self.plot_data_on_geometry_button.clicked.connect(self.plot_data_on_geometry) 

1272 self.plot_geometry_button.clicked.connect(self.plot_geometry) 

1273 self.plot_shape_button.clicked.connect(self.plot_shape) 

1274 self.tab_widget.currentChanged.connect(self.update_tab) 

1275 self.alldata_selector.currentItemChanged.connect(self.update_data_properties) 

1276 self.allmetadata_selector.currentItemChanged.connect(self.update_metadata_properties) 

1277 

1278 # @trace_callback 

1279 def update_tab(self, argument=None): 

1280 self.update_viz_buttons() 

1281 

1282 # @trace_callback 

1283 def update_activity_data(self, current=None, previous = None): 

1284 index = self.activity_selector.currentRow() 

1285 if index < 0: 

1286 self.activity_property_string.setText('Select Activity to See Properties') 

1287 self.update_viz_buttons() 

1288 return 

1289 activity = self.escdf.activities[index] 

1290 self.activity_property_string.setText('\n'.join(activity.repr().split('\n')[:-2])) 

1291 self.data_selector.clear() 

1292 for data in activity.data: 

1293 name = data.name 

1294 type = data.dataset_type 

1295 self.data_selector.addItem(f'{name} ({type})') 

1296 self.metadata_selector.clear() 

1297 for name in activity.metadata_links: 

1298 metadata = self.escdf.metadata[name] 

1299 type = metadata.dataset_type 

1300 self.metadata_selector.addItem(f'{name} ({type})') 

1301 self.update_viz_buttons() 

1302 

1303 # @trace_callback 

1304 def go_to_data(self, item=None): 

1305 found = False 

1306 dataset = self.get_active_data() 

1307 for index, compare_dataset in enumerate(self.data_array): 

1308 if dataset is compare_dataset: 

1309 # print('Found Dataset at {:}'.format(index)) 

1310 found = True 

1311 break 

1312 if found: 

1313 self.tab_widget.setCurrentIndex(1) 

1314 self.alldata_selector.setCurrentRow(index) 

1315 else: 

1316 # print('Did not find dataset!') 

1317 raise ValueError("Did not find dataset!") 

1318 

1319 # @trace_callback 

1320 def go_to_metadata(self, item=None): 

1321 found = False 

1322 metadata_index = self.metadata_selector.currentRow() 

1323 metadata_name = self.escdf.activities[self.activity_selector.currentRow()].metadata_links[metadata_index] 

1324 for index, compare_dataset in enumerate(self.metadata_array): 

1325 if compare_dataset.name == metadata_name: 

1326 # print('Found Dataset at {:}'.format(index)) 

1327 found = True 

1328 break 

1329 if found: 

1330 self.tab_widget.setCurrentIndex(2) 

1331 self.allmetadata_selector.setCurrentRow(index) 

1332 else: 

1333 # print('Did not find dataset!') 

1334 raise ValueError("Did not find dataset!") 

1335 

1336 # @trace_callback 

1337 def update_data_properties(self, current=None, previous=None): 

1338 # print(self.alldata_selector.currentRow()) 

1339 data_index = self.alldata_selector.currentRow() 

1340 if data_index < 0: 

1341 self.data_property_selector.clear() 

1342 return 

1343 dataset = self.data_array[data_index] 

1344 widget = self.data_property_selector 

1345 self.populate_properties(dataset, widget) 

1346 

1347 # @trace_callback 

1348 def update_data_property(self, current=None, previous=None): 

1349 data_index = self.alldata_selector.currentRow() 

1350 data = self.data_array[data_index] 

1351 property_index = self.data_property_selector.currentRow() 

1352 if property_index < 0: 

1353 return 

1354 property_name = data.property_names[property_index] 

1355 prop = getattr(data, property_name) 

1356 # Set up spinboxes for the dimensions 

1357 self.set_up_data_dimension_spinboxes(prop) 

1358 self.update_data_dimension(prop=prop) 

1359 

1360 # @trace_callback 

1361 def set_up_data_dimension_spinboxes(self, prop): 

1362 for spinbox in self.data_dimension_spinboxes: 

1363 self.data_dimension_selector_layout.removeWidget(spinbox) 

1364 self.data_dimension_spinboxes.clear() 

1365 if prop is not None: 

1366 for i, dimension in enumerate(prop.shape): 

1367 spinbox = DimensionSpinBox() 

1368 spinbox.blockSignals(True) 

1369 spinbox.setMinimum(-1) 

1370 spinbox.setMaximum(dimension-1) 

1371 spinbox.valueChanged.connect(self.update_data_dimension) 

1372 self.data_dimension_selector_layout.addWidget(spinbox) 

1373 self.data_dimension_spinboxes.append(spinbox) 

1374 if i < 2: 

1375 spinbox.setValue(-1) 

1376 else: 

1377 spinbox.setValue(0) 

1378 for spinbox in self.data_dimension_spinboxes: 

1379 spinbox.blockSignals(False) 

1380 

1381 # @trace_callback 

1382 def update_data_dimension(self, ind=None, prop=None): 

1383 if prop is None: 

1384 data_index = self.alldata_selector.currentRow() 

1385 data = self.data_array[data_index] 

1386 property_index = self.data_property_selector.currentRow() 

1387 if property_index < 0: 

1388 return 

1389 property_name = data.property_names[property_index] 

1390 prop = getattr(data, property_name) 

1391 if prop is None: 

1392 self.data_table_view.setModel(None) 

1393 return 

1394 indices = [spinbox.value() for spinbox in self.data_dimension_spinboxes] 

1395 model = ESCDFTableModel(prop, indices) 

1396 self.data_table_view.setModel(model) 

1397 

1398 # @trace_callback 

1399 def update_metadata_properties(self, current = None, previous = None): 

1400 # print(self.allmetadata_selector.currentRow()) 

1401 data_index = self.allmetadata_selector.currentRow() 

1402 if data_index < 0: 

1403 self.metadata_property_selector.clear() 

1404 return 

1405 dataset = self.metadata_array[data_index] 

1406 widget = self.metadata_property_selector 

1407 self.populate_properties(dataset, widget) 

1408 

1409 # @trace_callback 

1410 def update_metadata_property(self, current=None, previous=None): 

1411 metadata_index = self.allmetadata_selector.currentRow() 

1412 metadata = self.metadata_array[metadata_index] 

1413 property_index = self.metadata_property_selector.currentRow() 

1414 if property_index < 0: 

1415 return 

1416 property_name = metadata.property_names[property_index] 

1417 prop = getattr(metadata, property_name) 

1418 # Set up spinboxes for the dimensions 

1419 self.set_up_metadata_dimension_spinboxes(prop) 

1420 self.update_metadata_dimension(prop=prop) 

1421 

1422 # @trace_callback 

1423 def set_up_metadata_dimension_spinboxes(self, prop): 

1424 for spinbox in self.metadata_dimension_spinboxes: 

1425 self.metadata_dimension_selector_layout.removeWidget(spinbox) 

1426 self.metadata_dimension_spinboxes.clear() 

1427 if prop is not None: 

1428 for i, dimension in enumerate(prop.shape): 

1429 spinbox = DimensionSpinBox() 

1430 spinbox.blockSignals(True) 

1431 spinbox.setMinimum(-1) 

1432 spinbox.setMaximum(dimension-1) 

1433 spinbox.valueChanged.connect(self.update_metadata_dimension) 

1434 self.metadata_dimension_selector_layout.addWidget(spinbox) 

1435 self.metadata_dimension_spinboxes.append(spinbox) 

1436 if i < 2: 

1437 spinbox.setValue(-1) 

1438 else: 

1439 spinbox.setValue(0) 

1440 for spinbox in self.metadata_dimension_spinboxes: 

1441 spinbox.blockSignals(False) 

1442 

1443 # @trace_callback 

1444 def update_metadata_dimension(self, ind=None, prop=None): 

1445 if prop is None: 

1446 metadata_index = self.allmetadata_selector.currentRow() 

1447 metadata = self.metadata_array[metadata_index] 

1448 property_index = self.metadata_property_selector.currentRow() 

1449 if property_index < 0: 

1450 return 

1451 property_name = metadata.property_names[property_index] 

1452 prop = getattr(metadata, property_name) 

1453 if prop is None: 

1454 self.metadata_table_view.setModel(None) 

1455 return 

1456 indices = [spinbox.value() for spinbox in self.metadata_dimension_spinboxes] 

1457 model = ESCDFTableModel(prop, indices) 

1458 self.metadata_table_view.setModel(model) 

1459 

1460 # @trace_callback 

1461 def populate_properties(self, dataset, widget : QtWidgets.QListWidget): 

1462 widget.clear() 

1463 for property_name in dataset.property_names: 

1464 prop = getattr(dataset, property_name) 

1465 if prop is None: 

1466 widget.addItem(f'{property_name}: []') 

1467 continue 

1468 if np.prod(prop.shape) == 1 and not prop.ragged: 

1469 widget.addItem(f'{property_name}: {prop[...]}') 

1470 continue 

1471 widget.addItem(f'{property_name}: {prop.datatype}, {prop.shape}') 

1472 

1473 # @trace_callback 

1474 def get_active_data(self): 

1475 activity_index = self.activity_selector.currentRow() 

1476 if activity_index < 0: 

1477 return 

1478 index = self.data_selector.currentRow() 

1479 if index < 0: 

1480 return 

1481 activity = self.escdf.activities[activity_index] 

1482 data = activity.data[index] 

1483 return data 

1484 

1485 # @trace_callback 

1486 def update_data_data(self, current = None, previous = None): 

1487 data = self.get_active_data() 

1488 if data is None: 

1489 self.data_property_string.setText('Select Data to See Properties') 

1490 self.update_viz_buttons() 

1491 return 

1492 self.data_property_string.setText(data.repr()) 

1493 self.update_viz_buttons() 

1494 

1495 # @trace_callback 

1496 def update_metadata_data(self, current = None, previous = None): 

1497 self.update_viz_buttons() 

1498 

1499 # @trace_callback 

1500 def select_file(self, checked=False): 

1501 filename, file_filter = QtWidgets.QFileDialog.getOpenFileName( 

1502 self, 'Select ESCDF File', filter='ESCDF (*.h5 *.esf)') 

1503 if filename == '': 

1504 return 

1505 self.load(filename) 

1506 

1507 # @trace_callback 

1508 def load(self,escdf_file): 

1509 # Load the file 

1510 self.escdf = escdf.ESCDF.load(escdf_file) 

1511 

1512 # Update the activity selector 

1513 self.activity_selector.clear() 

1514 for name in self.escdf.activities.names: 

1515 self.activity_selector.addItem(name) 

1516 

1517 self.data_array = [] 

1518 self.metadata_array = [] 

1519 

1520 # Update the data selector 

1521 self.alldata_selector.clear() 

1522 for activity in self.escdf.activities: 

1523 for data in activity.data: 

1524 self.alldata_selector.addItem(f'{activity.name}: {data.name} ({data.dataset_type})') 

1525 self.data_array.append(data) 

1526 self.alldata_selector.setCurrentRow(0) 

1527 self.update_data_properties() 

1528 

1529 # Update the metadata selector 

1530 self.allmetadata_selector.clear() 

1531 for metadata in self.escdf.metadata: 

1532 self.allmetadata_selector.addItem(f'{metadata.name} ({metadata.dataset_type})') 

1533 self.metadata_array.append(metadata) 

1534 self.allmetadata_selector.setCurrentRow(0) 

1535 self.update_metadata_properties() 

1536 

1537 # @trace_callback 

1538 def get_activity_geometry(self): 

1539 activity_index = self.activity_selector.currentRow() 

1540 if activity_index < 0: 

1541 return 

1542 activity_metadata = [self.escdf.metadata[name] for name in self.escdf.activities[activity_index].metadata_links] 

1543 geometry = [ 

1544 metadata 

1545 for metadata in activity_metadata 

1546 if metadata.istype("geometry") or metadata.istype("point_cloud") 

1547 ] 

1548 return geometry 

1549 

1550 # @trace_callback 

1551 def get_activity_metadata_data(self): 

1552 activity_index = self.activity_selector.currentRow() 

1553 activity_metadata = [self.escdf.metadata[name] for name in self.escdf.activities[activity_index].metadata_links] 

1554 data = [metadata for metadata in activity_metadata if metadata.istype('data')] 

1555 return data 

1556 

1557 # @trace_callback 

1558 def find_comparable_data(self, active_data): 

1559 data_comparisions = self.get_activity_metadata_data() 

1560 comparable_data = [] 

1561 for data in data_comparisions: 

1562 if data.data_type[...] != active_data.data_type[...]: 

1563 continue 

1564 if data.channel.shape != active_data.channel.shape: 

1565 continue 

1566 if np.any(data.channel[...] != active_data.channel[...]): 

1567 continue 

1568 comparable_data.append(data) 

1569 return comparable_data 

1570 

1571 # @trace_callback 

1572 def update_viz_buttons(self): 

1573 if self.tab_widget.currentIndex() != 0: 

1574 self.plot_shape_button.setVisible(False) 

1575 self.plot_data_on_geometry_button.setVisible(False) 

1576 self.plot_data_button.setVisible(False) 

1577 self.plot_data_against_metadata_button.setVisible(False) 

1578 self.plot_geometry_button.setVisible(False) 

1579 self.plot_coordinate_button.setVisible(False) 

1580 return 

1581 geometries = self.get_activity_geometry() 

1582 if geometries is None: 

1583 self.plot_shape_button.setVisible(False) 

1584 self.plot_data_on_geometry_button.setVisible(False) 

1585 self.plot_data_button.setVisible(False) 

1586 self.plot_data_against_metadata_button.setVisible(False) 

1587 self.plot_geometry_button.setVisible(False) 

1588 self.plot_coordinate_button.setVisible(False) 

1589 return 

1590 geom_active = len(geometries) > 0 

1591 self.plot_geometry_button.setVisible(geom_active) 

1592 self.plot_coordinate_button.setVisible(geom_active) 

1593 active_data = self.get_active_data() 

1594 if active_data is None: 

1595 self.plot_shape_button.setVisible(False) 

1596 self.plot_data_on_geometry_button.setVisible(False) 

1597 self.plot_data_button.setVisible(False) 

1598 self.plot_data_against_metadata_button.setVisible(False) 

1599 return 

1600 shape_active = geom_active and active_data.istype('mode') 

1601 self.plot_shape_button.setVisible(shape_active) 

1602 data_on_geom_active = geom_active and active_data.istype('data') and active_data.data_type[...] in [ 

1603 'time response', 'frequency response function', 'transmissibility', 'spectrum', 

1604 'impulse_response_function'] 

1605 self.plot_data_on_geometry_button.setVisible(data_on_geom_active) 

1606 data_active = active_data.istype('data') 

1607 self.plot_data_button.setVisible(data_active) 

1608 if data_active: 

1609 comparable_data = self.find_comparable_data(active_data) 

1610 data_comparable = len(comparable_data) > 0 

1611 else: 

1612 data_comparable = False 

1613 self.plot_data_against_metadata_button.setVisible(data_comparable) 

1614 

1615 # @trace_callback 

1616 def set_node_size(self, checked=False): 

1617 out = ESCDFSettingDialog.get_value(int, self.node_size, 'Set the Node Size', self) 

1618 if out is not None: 

1619 self.node_size = out 

1620 

1621 # @trace_callback 

1622 def set_line_width(self, checked=False): 

1623 out = ESCDFSettingDialog.get_value(int, self.line_width, 'Set the Line Width', self) 

1624 if out is not None: 

1625 self.line_width = out 

1626 

1627 # @trace_callback 

1628 def set_label_text_size(self, checked=False): 

1629 out = ESCDFSettingDialog.get_value(int, self.label_text_size, 'Set the Text Size for Labels', self) 

1630 if out is not None: 

1631 self.label_text_size = out 

1632 

1633 # @trace_callback 

1634 def set_arrow_size(self, checked=False): 

1635 out = ESCDFSettingDialog.get_value(float, self.arrow_size, 'Set the Arrow Size for Degrees of Freedom', self) 

1636 if out is not None: 

1637 self.arrow_size = out 

1638 

1639 # @trace_callback 

1640 def set_opacity(self, checked=False): 

1641 out = ESCDFSettingDialog.get_value(float, self.opacity, 'Set the Geometry Opacity', self) 

1642 if out is not None: 

1643 self.opacity = out 

1644 

1645 # @trace_callback 

1646 def set_undeformed_opacity(self, checked=False): 

1647 out = ESCDFSettingDialog.get_value(float, self.undeformed_opacity, 'Set the Undeformed Geometry Opacity', self) 

1648 if out is not None: 

1649 self.undeformed_opacity = out 

1650 

1651 # @trace_callback 

1652 def set_transient_start_time(self, checked=False): 

1653 out = ESCDFSettingDialog.get_value(float, self.transient_start_time, 'Set the Starting Time for Transient Plots', self) 

1654 if out is not None: 

1655 self.transient_start_time = out 

1656 

1657 # @trace_callback 

1658 def set_transient_end_time(self, checked=False): 

1659 out = ESCDFSettingDialog.get_value(float, self.transient_end_time, 'Set the Ending Time for Transient Plots', self) 

1660 if out is not None: 

1661 self.transient_end_time = out 

1662 

1663 # @trace_callback 

1664 def plot_coordinate(self): 

1665 geometries = self.get_activity_geometry() 

1666 if len(geometries) > 1: 

1667 index = ESCDFSettingDialog.get_value(list,[geo.name for geo in geometries],'Select Geometry') 

1668 if index is None: 

1669 return 

1670 else: 

1671 index = 0 

1672 geometry = to_geometry(geometries[index]) 

1673 coordinates = coordinate_array(geometry.node.id, [1, 2, 3], force_broadcast=True) 

1674 plot_kwargs = {'node_size': self.node_size, 

1675 'line_width': self.line_width, 

1676 'show_edges': self.actionPlot_Edges.isChecked(), 

1677 'label_nodes': self.actionLabel_Nodes.isChecked(), 

1678 'label_tracelines': self.actionLabel_Tracelines.isChecked(), 

1679 'label_elements': self.actionLabel_Elements.isChecked(), 

1680 'label_font_size': self.label_text_size} 

1681 geometry.plot_coordinate(coordinates, 

1682 arrow_scale=self.arrow_size, 

1683 label_dofs=self.actionLabel_Degrees_of_Freedom.isChecked(), 

1684 label_font_size=self.label_text_size, 

1685 opacity=self.opacity, 

1686 plot_kwargs=plot_kwargs 

1687 ) 

1688 

1689 # @trace_callback 

1690 def plot_data_against_metadata(self): 

1691 data = self.get_active_data() 

1692 compare_data = self.find_comparable_data(data) 

1693 data = to_data(data) 

1694 if len(compare_data) > 1: 

1695 index = ESCDFSettingDialog.get_value(list, [d.name for d in compare_data], 'Select Comparison Data') 

1696 if index is None: 

1697 return 

1698 else: 

1699 index = 0 

1700 compare_data = to_data(compare_data[index]) 

1701 if data.function_type == FunctionTypes.POWER_SPECTRAL_DENSITY and self.actionPlot_APSDs.isChecked(): 

1702 data = data.get_asd() 

1703 compare_data = compare_data.get_asd() 

1704 self.guiplots.append(GUIPlot(Active_Data=data, Comparison_Data=compare_data)) 

1705 

1706 # @trace_callback 

1707 def plot_data(self): 

1708 data = to_data(self.get_active_data()) 

1709 if data.function_type == FunctionTypes.POWER_SPECTRAL_DENSITY and self.actionPlot_APSDs.isChecked(): 

1710 data = data.get_asd() 

1711 self.guiplots.append(data.gui_plot()) 

1712 

1713 # @trace_callback 

1714 def plot_data_on_geometry(self): 

1715 geometries = self.get_activity_geometry() 

1716 if len(geometries) > 1: 

1717 index = ESCDFSettingDialog.get_value(list,[geo.name for geo in geometries],'Select Geometry') 

1718 if index is None: 

1719 return 

1720 else: 

1721 index = 0 

1722 geometry = to_geometry(geometries[index]) 

1723 data = to_data(self.get_active_data()) 

1724 # We need to reshape it to response x reference in order to plot it 

1725 if data.coordinate.shape[-1] > 1: 

1726 data = data.reshape_to_matrix() 

1727 # Get the reference coordinates 

1728 refs = data[0].reference_coordinate 

1729 if len(refs) > 1: 

1730 index = ESCDFSettingDialog.get_value(list, refs.string_array().tolist(), 'Select Reference to Plot') 

1731 if index is None: 

1732 return 

1733 else: 

1734 index = 0 

1735 data = data[:, index] 

1736 plot_kwargs = {'node_size': self.node_size, 

1737 'line_width': self.line_width, 

1738 'show_edges': self.actionPlot_Edges.isChecked(), 

1739 'label_nodes': self.actionLabel_Nodes.isChecked(), 

1740 'label_tracelines': self.actionLabel_Tracelines.isChecked(), 

1741 'label_elements': self.actionLabel_Elements.isChecked(), 

1742 'label_font_size': self.label_text_size} 

1743 if data.function_type in [FunctionTypes.TIME_RESPONSE, FunctionTypes.IMPULSE_RESPONSE_FUNCTION]: 

1744 geometry.plot_transient(data.extract_elements_by_abscissa( 

1745 self.transient_start_time, 

1746 self.transient_end_time 

1747 ), plot_kwargs=plot_kwargs, 

1748 undeformed_opacity=self.undeformed_opacity, 

1749 deformed_opacity=self.opacity) 

1750 else: 

1751 geometry.plot_deflection_shape(data, plot_kwargs=plot_kwargs, 

1752 undeformed_opacity=self.undeformed_opacity, 

1753 deformed_opacity=self.opacity) 

1754 

1755 # @trace_callback 

1756 def plot_geometry(self): 

1757 geometries = self.get_activity_geometry() 

1758 if len(geometries) > 1: 

1759 index = ESCDFSettingDialog.get_value(list,[geo.name for geo in geometries],'Select Geometry') 

1760 if index is None: 

1761 return 

1762 else: 

1763 index = 0 

1764 geometry = to_geometry(geometries[index]) 

1765 plot_kwargs = {'node_size': self.node_size, 

1766 'line_width': self.line_width, 

1767 'show_edges': self.actionPlot_Edges.isChecked(), 

1768 'label_nodes': self.actionLabel_Nodes.isChecked(), 

1769 'label_tracelines': self.actionLabel_Tracelines.isChecked(), 

1770 'label_elements': self.actionLabel_Elements.isChecked(), 

1771 'label_font_size': self.label_text_size, 

1772 'opacity': self.opacity} 

1773 geometry.plot(**plot_kwargs) 

1774 

1775 # @trace_callback 

1776 def plot_shape(self): 

1777 geometries = self.get_activity_geometry() 

1778 if len(geometries) > 1: 

1779 index = ESCDFSettingDialog.get_value(list,[geo.name for geo in geometries],'Select Geometry') 

1780 if index is None: 

1781 return 

1782 else: 

1783 index = 0 

1784 geometry = to_geometry(geometries[index]) 

1785 shapes = to_shape(self.get_active_data()) 

1786 plot_kwargs = {'node_size': self.node_size, 

1787 'line_width': self.line_width, 

1788 'show_edges': self.actionPlot_Edges.isChecked(), 

1789 'label_nodes': self.actionLabel_Nodes.isChecked(), 

1790 'label_tracelines': self.actionLabel_Tracelines.isChecked(), 

1791 'label_elements': self.actionLabel_Elements.isChecked(), 

1792 'label_font_size': self.label_text_size} 

1793 geometry.plot_shape(shapes, plot_kwargs=plot_kwargs, 

1794 undeformed_opacity=self.undeformed_opacity, 

1795 deformed_opacity=self.opacity)