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
« 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
5@author: dprohe
6"""
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)
19from qtpy.QtWidgets import (QMainWindow)
20from qtpy import QtWidgets, uic, QtGui, QtCore
21import netCDF4 as nc4
22import os
23import numpy as np
25try:
26 import escdf
27except ImportError:
28 escdf = None
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)
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
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)
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 )
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
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)
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
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'}
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 }
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
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
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
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.
312 Operates on channel tables like the one from
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
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.')
332 escdf_channels = escdf.Dataset(dataset_name,'vibration_channel_table',
333 descriptive_name)
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'])]
346 return escdf_channels
348def from_rattlesnake_modal_parameters(dataset_name, modal_dataset, environment_name = None, descriptive_name=''):
349 """Generates a "modal_test_parameters" object from modal output
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 ''.
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.')
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
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
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 ''.
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
476def from_rattlesnake_random_parameters(dataset_name, random_dataset, environment_name=None, descriptive_name=''):
477 """Gets random vibration parameters from a NetCDF4 streaming file
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 ''.
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
544def from_rattlesnake_transient_parameters(dataset_name, transient_dataset, environment_name=None, descriptive_name=''):
545 """Gets transient vibration parameters from a NetCDF4 streaming file
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 ''.
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
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
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.
617 Raises
618 ------
619 ImportError
620 If the escdf package is not available.
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.
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)
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))
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'
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'
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)
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'
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)
697 # System Identification Parameters
698 essysid = from_rattlesnake_system_id_parameters(
699 f'{test_name_slug}_Params', sysid_streaming_data)
701 return ((essysid, eschan),
702 tuple([esnoise_time_history, essysid_time_history] + esspectral_data
703 + [essysid_coherence]))
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
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.
727 Raises
728 ------
729 ImportError
730 If the escdf package is not available.
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 """
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)
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)
758 if verbose:
759 print('Reading Spectral Data')
760 response_cpsd,spec_cpsd,drive_cpsd = read_random_spectral_data(spectral_data)
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)
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)
792 if verbose:
793 print('Creating ESCDF Metadata Objects')
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)
802 # Pull the spec into metadata
803 return ((esrand, eschan, esspec[0]), tuple(esdata))
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
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.
827 Raises
828 ------
829 ImportError
830 If the escdf package is not available.
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 """
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)
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)
858 if verbose:
859 print('Reading Control Data')
860 response_th,spec_th,drive_th = read_transient_control_data(control_data)
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)
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)
892 if verbose:
893 print('Creating ESCDF Metadata Objects')
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)
902 # Pull the spec into metadata
903 return ((estrans, eschan, esspec[0]), tuple(esdata))
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
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.
931 Raises
932 ------
933 ImportError
934 If the escdf package is not available.
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).
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)
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()
956 if verbose:
957 print('Creating ESCDF Data Objects')
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'
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}')
975 es_coh = from_data(
976 f'{test_name_slug}_Coherence',mcoh)
977 es_coh.ordinate_unit = '-'
978 es_coh.abscissa_unit = 'Hz'
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)
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)
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)
1006 # System Identification Parameters
1007 esmod = from_rattlesnake_modal_parameters(
1008 f'{test_name_slug}_Params', modal_results_data)
1010 return ((esmod, eschan),
1011 tuple([es_th, es_frf, es_coh] + additional_data))
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
1020 # Create layout
1021 layout = QtWidgets.QVBoxLayout()
1023 # Add message label
1024 self.message_label = QtWidgets.QLabel(message)
1025 layout.addWidget(self.message_label)
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")
1048 layout.addWidget(self.input_widget)
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)
1058 # Connect buttons to their respective slots
1059 self.ok_button.clicked.connect(self.accept)
1060 self.cancel_button.clicked.connect(self.reject)
1062 self.setLayout(layout)
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
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
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
1103class ESCDFTableModel(QtCore.QAbstractTableModel):
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)
1115 def rowCount(self, parent=QtCore.QModelIndex()):
1116 return self.table_dimensions[0]
1118 def columnCount(self, parent=QtCore.QModelIndex()):
1119 return self.table_dimensions[1]
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)
1131 def data(self, index, role=QtCore.Qt.DisplayRole):
1132 if role == QtCore.Qt.DisplayRole:
1133 row = index.row()
1134 col = index.column()
1136 output_slice = self.get_slice(row, col)
1138 # Fetch the data lazily
1139 value = self.prop[output_slice]
1140 return str(value)
1141 return None
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
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)
1156 def valueFromText(self, text):
1157 # Override to interpret ":" as -1
1158 if text == ":":
1159 return -1
1160 return int(text)
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
1172class ESCDFVisualizer(QMainWindow):
1173 """An interactive window allowing users to explore an ESCDF file"""
1175 monofont = QtGui.QFont("monospace")
1176 monofont.setStyleHint(QtGui.QFont.TypeWriter)
1178 def __init__(self, escdf_file=None):
1179 """Create an ESCDF Visualizer Window to explore an ESCDF file.
1181 A filename or ESCDF object can be passed as an argment, or it can be
1182 loaded at a later point.
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.
1190 Returns
1191 -------
1192 None.
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()
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)
1278 # @trace_callback
1279 def update_tab(self, argument=None):
1280 self.update_viz_buttons()
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()
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!")
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!")
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)
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)
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)
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)
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)
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)
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)
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)
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}')
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
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()
1495 # @trace_callback
1496 def update_metadata_data(self, current = None, previous = None):
1497 self.update_viz_buttons()
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)
1507 # @trace_callback
1508 def load(self,escdf_file):
1509 # Load the file
1510 self.escdf = escdf.ESCDF.load(escdf_file)
1512 # Update the activity selector
1513 self.activity_selector.clear()
1514 for name in self.escdf.activities.names:
1515 self.activity_selector.addItem(name)
1517 self.data_array = []
1518 self.metadata_array = []
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()
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()
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
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
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
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)
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
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
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
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
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
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
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
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
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 )
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))
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())
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)
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)
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)