def ResetMetaData(self): """ Reset the metadata """ self.live_chanset = ChannelSet(self.rec.channels) self.live_chanset.add_channel_dataset(tuple(range(self.rec.channels)), 'time_series')
def create_test_channelset(self): self.cs = ChannelSet(5) t = np.arange(0, 0.5, 1 / 5000) for i, channel in enumerate(self.cs.channels): self.cs.set_channel_metadata(i, {'sample_rate': 5000}) self.cs.add_channel_dataset(i, 'time_series', np.sin(t * 2 * np.pi * 100 * (i + 1))) channel.name = "Channel {}".format(i) self.cs.add_channel_dataset( i, 'time_series', np.sin(t * 2 * np.pi * 100 * (i + 1)) * np.exp(-t / t[-1]))
def pickle_file(self): ''' This is probably a temporary solution to saving data. The pickle file is only intended for internal use only. Please make sure no one tampers with the files. Also, only open files that YOU have pickled. ''' url = QFileDialog.getSaveFileName(self, "Export Data", "", "Pickle Files (*.pickle)")[0] if url: saved_cs = ChannelSet(len(self.order)) for i, n in enumerate(self.order): saved_cs.channels[i] = self.cs.channels[n] with open(url, 'wb') as f: pickle.dump(saved_cs, f)
def clear(self): self.new_cs = ChannelSet() self.set_channel_set(self.new_cs)
def __init__(self, parent): super().__init__(parent) self.new_cs = ChannelSet() self.init_UI()
def import_from_mat(file, channel_set=None): """ A function for importing data and metadata to a ChannelSet from an old-style DataLogger ``.mat`` file. Parameters ---------- file : path_to_file The path to the ``.mat`` file to import data from. channel_set : ChannelSet The ChannelSet to save the imported data and metadata to. If ``None``, a new ChannelSet is created and returned. """ if channel_set is None: new_channel_set = True channel_set = ChannelSet() else: new_channel_set = False # Load the matlab file as a dict file = sio.loadmat(file) # Work out how many channels to create num_time_series_datasets = file["dt2"][0][0] num_spectrum_datasets = file["dt2"][0][1] num_sonogram_datasets = file["dt2"][0][2] num_channels = np.amax( np.asarray([ num_time_series_datasets, num_spectrum_datasets, num_sonogram_datasets ])) # # Extract metadata sample_rate = file["freq"][0][0] if "tsmax" in file.keys(): time_series_scale_factor = file["tsmax"][0][0] else: time_series_scale_factor = 1 if "npts" in file.keys(): fft_num_samples = file["npts"] else: fft_num_samples = None if "tfun" in file.keys(): is_transfer_function = bool(file["tfun"]) else: is_transfer_function = False if "step" in file.keys(): sonogram_fft_step = file["step"] else: sonogram_fft_step = None # # Extract data # Transpose the data so it's easier to work with: # In the matlab file it is in the form # (num_samples_per_channel, num_channels) - each channel is a column # Numpy works more easily if it is in the form # (num_channels, num_samples_per_channel) - each channel is a row if "indata" in file.keys(): time_series = file["indata"].transpose() if "yspec" in file.keys(): spectrum = file["yspec"].transpose() if "yson" in file.keys(): sonogram_amplitude = file["yson"].transpose() if "yphase" in file.keys(): sonogram_phase = file["yphase"].transpose() # # Save everything for i in np.arange(num_channels, dtype=np.int): # Create a new channel channel_set.add_channels() # Set channel metadata channel_set.set_channel_metadata( i, { "name": "Imported {}".format(i), "sample_rate": sample_rate, "calibration_factor": time_series_scale_factor }) # Set channel data if i < num_time_series_datasets: channel_set.add_channel_dataset(i, "time_series", time_series[i]) # Differentiate between TF and FFT if i < num_spectrum_datasets: if is_transfer_function: channel_set.add_channel_dataset(i, "transfer_function", spectrum[i], ' ') else: channel_set.add_channel_dataset(i, "spectrum", spectrum[i], 'Hz') #print(channel_set.channels[i].data("frequency")) if i < num_sonogram_datasets: channel_set.add_channel_dataset(i, "sonogram", sonogram_amplitude[i]) channel_set.add_channel_dataset(i, "sonogram_phase", sonogram_phase[i], 'rad') if new_channel_set: return channel_set
def load_pickle(self): ''' This is probably a temporary solution to loading data. Probably have to write a better way of storing data. PLEASE DO NOT OPEN ANY UNTRUSTED PICKLE FILES. UNPICKLING A FILE CAN EXECUTE ARBITRARY CODE, WHICH IS DANGEROUS TO YOUR COMPUTER. ''' url = QFileDialog.getOpenFileName(self, "Load Channel Set", "addons", "Pickle Files (*.pickle)")[0] with open(url, 'rb') as f: self.new_cs = pickle.load(f) self.set_channel_set(self.new_cs) def clear(self): self.new_cs = ChannelSet() self.set_channel_set(self.new_cs) def extend_data(self): self.sig_extend_channelset.emit(self.new_cs) def replace_data(self): self.sig_replace_channelset.emit(self.new_cs) if __name__ == '__main__': cs = ChannelSet() import_from_mat("../../tests/transfer_function_clean.mat", cs) print(cs.channel_ids(0))
class AcquisitionWindow(QMainWindow): """ This is the window that acts as the central hub for all the widgets. Contains a left Toolbox for streaming configuration and a right Toolbox for recording configuration. The center contain the Time Domain and Frequency Domain Plots, along with a status bar below them. The channel levels plot is placed in the right Toolbox. The widgets communicate with each other using the pyqt Signal and Slot method, so most callback functions are self-contained in the respective widget. However, some callback function are still present in the main body, mostly functions to handle the streaming and the recording processes. Attributes ---------- sig_time_series_data_saved: pyqtSignal(ChannelSet) Emitted when time series data is recorded sig_transfer_function_data_saved: pyqtSignal(ChannelSet) Emitted when transfer function data is recorded playing: bool Indicate whether the stream is playing rec: Recorder object Object which handles the streaming and recording See documentation for Recorder classes """ sig_time_series_data_saved = pyqtSignal(object) sig_transfer_function_data_saved = pyqtSignal(object) sig_closed = pyqtSignal() def __init__(self, parent=None, recType=mR, configs=['', 44100, 2, 1024, 6]): """ Initialise and construct the window application. Parameters ---------- parent: object The object which the window interacts with e.g. The analysis window """ super().__init__() self.parent = parent # Set window parameter self.setGeometry(400, 300, WIDTH, HEIGHT) self.setWindowTitle('AcquisitionWindow') self.meta_window = None # Set recorder object self.playing = False self.rec = recType.Recorder(rate=configs[1], channels=configs[2], chunk_size=configs[3], num_chunk=configs[4], device_name=configs[0]) # Set up the TimeSeries and FreqSeries self.timedata = None self.freqdata = None # Set up tallies for the average transfer function calculation self.autospec_in_tally = [] self.autospec_out_tally = [] self.crossspec_tally = [] try: # Construct UI self.initUI() except Exception: # If it fails, show the window and stop t, v, tb = sys.exc_info() print(t) print(v) print(traceback.format_tb(tb)) self.show() return # Connect the recorder Signals self.connect_rec_signals() # Attempt to start streaming self.init_and_check_stream() # Center and show window self.adjust_position() self.setFocus() self.show() def initUI(self): """ Construct the window by calling the widgets classes, and any additional widgets (such as Qsplitters, Toolbox) Then, perform the signal connection among the widgets. Also sets up the update timer for the streaming. Lastly, finalise the plots and misc. items. There is also an experimental styling section just after the UI construction. """ # Set up the main widget self.main_widget = QWidget(self) main_layout = QHBoxLayout(self.main_widget) #------------------------- STREAM TOOLBOX ------------------------------ self.stream_toolbox = MasterToolbox() self.stream_tools = Toolbox('left', self.main_widget) self.stream_toolbox.add_toolbox(self.stream_tools) self.chantoggle_UI = ChanToggleUI(self.main_widget) self.chanconfig_UI = ChanConfigUI(self.main_widget) self.devconfig_UI = DevConfigUI(self.main_widget) self.devconfig_UI.set_recorder(self.rec) self.devconfig_UI.config_setup() NI_btn = self.devconfig_UI.typegroup.findChildren(QRadioButton)[1] if not NI_drivers: NI_btn.setDisabled(True) self.stream_tools.addTab(self.chantoggle_UI, 'Channel Toggle') self.stream_tools.addTab(self.chanconfig_UI, 'Channel Config') self.stream_tools.addTab(self.devconfig_UI, 'Device Config') main_layout.addWidget(self.stream_toolbox) main_layout.setStretchFactor(self.stream_toolbox, 0) #---------------------------PLOT + STATUS WIDGETS----------------------------- self.mid_splitter = QSplitter(self.main_widget, orientation=Qt.Vertical) self.timeplot = TimeLiveGraph(self.mid_splitter) self.freqplot = FreqLiveGraph(self.mid_splitter) self.stats_UI = StatusUI(self.mid_splitter) self.mid_splitter.addWidget(self.timeplot) self.mid_splitter.addWidget(self.freqplot) self.mid_splitter.addWidget(self.stats_UI) self.mid_splitter.setCollapsible(2, False) main_layout.addWidget(self.mid_splitter) main_layout.setStretchFactor(self.mid_splitter, 1) #---------------------------RECORDING TOOLBOX------------------------------- self.recording_toolbox = MasterToolbox() self.recording_tools = Toolbox('right', self.main_widget) self.recording_toolbox.add_toolbox(self.recording_tools) self.right_splitter = QSplitter(self.main_widget, orientation=Qt.Vertical) self.RecUI = RecUI(self.main_widget) self.RecUI.set_recorder(self.rec) self.right_splitter.addWidget(self.RecUI) self.levelsplot = LevelsLiveGraph(self.rec, self.right_splitter) self.right_splitter.addWidget(self.levelsplot) self.recording_tools.addTab(self.right_splitter, 'Record Time Series') main_layout.addWidget(self.recording_toolbox) main_layout.setStretchFactor(self.recording_toolbox, 0) #-----------------------EXPERIMENTAL STYLING---------------------------- #self.main_splitter.setFrameShape(QFrame.Panel) #self.main_splitter.setFrameShadow(QFrame.Sunken) """ self.main_widget.setStyleSheet(''' .QWidget{ background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #eee, stop:1 #ccc); border: 1px solid #777; width: 13px; margin-top: 2px; margin-bottom: 2px; border-radius: 4px; } .QSplitter::handle{ background: #737373; } .QGroupBox{ border: 1px solid black; margin-top: 0.5em; font: italic; } .QGroupBox::title { top: -6px; left: 10px; } .QWidget #subWidget{ background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #eee, stop:1 #ccc); border: 1px solid #777; width: 13px; margin-top: 2px; margin-bottom: 2px; border-radius: 4px; } ''') """ #----------------------SIGNAL CONNECTIONS--------------------------- self.chantoggle_UI.sigToggleChanged.connect( self.timeplot.toggle_plotline) self.chantoggle_UI.sigToggleChanged.connect( self.freqplot.toggle_plotline) self.chanconfig_UI.chans_num_box.currentIndexChanged.connect( self.display_chan_config) self.chanconfig_UI.meta_btn.clicked.connect(self.open_meta_window) self.chanconfig_UI.sigTimeOffsetChanged.connect( self.timeplot.set_offset) self.chanconfig_UI.sigFreqOffsetChanged.connect( self.freqplot.set_offset) self.chanconfig_UI.sigHoldChanged.connect(self.timeplot.set_sig_hold) self.chanconfig_UI.sigColourChanged.connect( self.timeplot.set_plot_colour) self.chanconfig_UI.sigColourChanged.connect( self.freqplot.set_plot_colour) self.chanconfig_UI.sigColourChanged.connect( self.levelsplot.set_plot_colour) self.chanconfig_UI.sigColourReset.connect( self.timeplot.reset_default_colour) self.chanconfig_UI.sigColourReset.connect( self.freqplot.reset_default_colour) self.chanconfig_UI.sigColourReset.connect( self.levelsplot.reset_default_colour) self.devconfig_UI.configRecorder.connect(self.ResetRecording) self.timeplot.plotColourChanged.connect( self.chanconfig_UI.set_colour_btn) self.freqplot.plotColourChanged.connect( self.chanconfig_UI.set_colour_btn) self.levelsplot.plotColourChanged.connect( self.chanconfig_UI.set_colour_btn) self.levelsplot.thresholdChanged.connect( self.RecUI.rec_boxes[4].setText) self.stats_UI.statusbar.messageChanged.connect(self.default_status) self.stats_UI.resetView.pressed.connect(self.ResetSplitterSizes) self.stats_UI.togglebtn.pressed.connect(lambda: self.toggle_rec()) self.stats_UI.sshotbtn.pressed.connect(self.get_snapshot) self.RecUI.rec_boxes[4].textEdited.connect( self.levelsplot.change_threshold) self.RecUI.startRecording.connect(self.start_recording) self.RecUI.cancelRecording.connect(self.cancel_recording) self.RecUI.undoLastTfAvg.connect(self.undo_tf_tally) self.RecUI.clearTfAvg.connect(self.remove_tf_tally) #---------------------------RESETTING METHODS--------------------------- self.ResetMetaData() self.ResetChanBtns() self.ResetPlots() self.ResetChanConfigs() self.levelsplot.reset_channel_levels() self.ResetSplitterSizes() #-----------------------FINALISE THE MAIN WIDGET------------------------- #Set the main widget as central widget self.main_widget.setFocus() self.setCentralWidget(self.main_widget) # Set up a timer to update the plot self.plottimer = QTimer(self) self.plottimer.timeout.connect(self.update_line) #self.plottimer.timeout.connect(self.update_chanlvls) self.plottimer.start(self.rec.chunk_size * 1000 // self.rec.rate) self.show() def adjust_position(self): """ If it has a parent, adjust its position to be slightly out of the parent window towards the left """ if self.parent: pr = self.parent.frameGeometry() qr = self.frameGeometry() self.move(pr.topLeft()) self.move(qr.left() / 2, qr.top()) #----------------CHANNEL CONFIGURATION WIDGET--------------------------- def display_chan_config(self, arg): """ Displays the channel plot offsets, colours, and signal holding. """ # This function is here because it is easier for channel config to # get data from the plot widgets if type(arg) == pg.PlotDataItem: num = self.timeplot.check_line(arg) if num == None: num = self.freqplot.check_line(arg) self.chanconfig_UI.chans_num_box.setCurrentIndex(num) else: num = arg self.chanconfig_UI.colbox.setColor(self.timeplot.plot_colours[num]) self.chanconfig_UI.time_offset_config[0].setValue( self.timeplot.plot_xoffset[num]) self.chanconfig_UI.time_offset_config[1].setValue( self.timeplot.plot_yoffset[num]) self.chanconfig_UI.hold_tickbox.setCheckState( self.timeplot.sig_hold[num]) self.chanconfig_UI.fft_offset_config[0].setValue( self.freqplot.plot_xoffset[num]) self.chanconfig_UI.fft_offset_config[1].setValue( self.freqplot.plot_yoffset[num]) def open_meta_window(self): """ Open the metadata window """ if not self.meta_window: try: self.meta_window = ChanMetaWin(self) self.meta_window.show() self.meta_window.finished.connect(self.meta_win_closed) except: t, v, tb = sys.exc_info() print(t) print(v) print(traceback.format_tb(tb)) def meta_win_closed(self): """ Callback when the metadata window is closed """ self.meta_window = None self.update_chan_names() #----------------------PLOT WIDGETS----------------------------------- # Updates the plots def update_line(self): """ Callback to update the time domain and frequency domain plot """ # Get the buffer data = self.rec.get_buffer() # Take the last chunk for the levels plot currentdata = data[len(data) - self.rec.chunk_size:, :] currentdata -= np.mean(currentdata) rms = np.sqrt(np.mean(currentdata**2, axis=0)) maxs = np.amax(abs(currentdata), axis=0) self.levelsplot.set_channel_levels(rms, maxs) # Prepare the window and weightage for FFT plot window = np.hanning(data.shape[0]) weightage = np.exp(2 * self.timedata / self.timedata[-1]) # Update each plot item's data + level peaks for i in range(data.shape[1]): plotdata = data[:, i].reshape((len(data[:, i]), )) # Check for zero crossing if needed zc = 0 if self.timeplot.sig_hold[i] == Qt.Checked: avg = np.mean(plotdata) zero_crossings = np.where( np.diff(np.sign(plotdata - avg)) > 0)[0] if zero_crossings.shape[0]: zc = zero_crossings[0] + 1 self.timeplot.update_line(i, x=self.timedata[:len(plotdata) - zc], y=plotdata[zc:]) fft_data = rfft(plotdata * window * weightage) psd_data = abs(fft_data)**0.5 self.freqplot.update_line(i, x=self.freqdata, y=psd_data) self.levelsplot.set_peaks(i, maxs[i]) #-------------------------STATUS BAR WIDGET-------------------------------- def toggle_rec(self, stop=None): """ Callback to pause/resume the stream, unless explicitly specified to stop or not Parameters ---------- stop: bool Specify whether to stop the stream. None to toggle the current state """ if not stop == None: self.playing = stop if self.playing: self.rec.stream_stop() self.stats_UI.togglebtn.setText('Resume') self.RecUI.recordbtn.setDisabled(True) else: self.rec.stream_start() self.stats_UI.togglebtn.setText('Pause') self.RecUI.recordbtn.setEnabled(True) self.playing = not self.playing # Clear the status, allow it to auto update itself self.stats_UI.statusbar.clearMessage() # Get the current instantaneous plot and transfer to main window def get_snapshot(self): """ Callback to take the current buffer data and send it out to parent window """ snapshot = self.rec.get_buffer() for i in range(snapshot.shape[1]): self.live_chanset.set_channel_data(i, 'time_series', snapshot[:, i]) self.live_chanset.set_channel_metadata(tuple(range(snapshot.shape[1])), {'sample_rate': self.rec.rate}) self.save_time_series() self.stats_UI.statusbar.showMessage('Snapshot Captured!', 1500) def default_status(self, msg): """ Callback to set the status message to the default messages if it is empty (ie when cleared) """ # Placed here because it accesses self.playing (might change it soon?) if not msg: if self.playing: self.stats_UI.statusbar.showMessage('Streaming') else: self.stats_UI.statusbar.showMessage('Stream Paused') #---------------------------RECORDING WIDGET------------------------------- def start_recording(self): """ Callback to start the data recording """ success = False rec_configs = self.RecUI.get_record_config() if rec_configs[2] >= 0: # Set up the trigger if specified if self.rec.trigger_start(posttrig=rec_configs[0], duration=rec_configs[1], pretrig=rec_configs[2], channel=rec_configs[3], threshold=rec_configs[4]): success = True self.stats_UI.statusbar.showMessage('Trigger Set!') else: # Record normally self.rec.record_init(samples=rec_configs[0], duration=rec_configs[1]) if self.rec.record_start(): success = True self.stats_UI.statusbar.showMessage('Recording...') if success: # Disable buttons for btn in [ self.stats_UI.togglebtn, self.devconfig_UI.config_button, self.RecUI.recordbtn ]: btn.setDisabled(True) self.RecUI.switch_rec_box.setDisabled(True) self.RecUI.spec_settings_widget.setDisabled(True) # Enable the cancel buttons self.RecUI.cancelbtn.setEnabled(True) # def stop_recording(self): """ Callback when the recording finishes. Process the data first, if specify, then transfer the data to the parent window """ # Enable the buttons for btn in self.main_widget.findChildren(QPushButton): btn.setEnabled(True) # Disable the cancel button self.RecUI.cancelbtn.setDisabled(True) # Get the recorded data and compute DFT data = self.rec.flush_record_data() ft_datas = np.zeros((int(data.shape[0] / 2) + 1, data.shape[1]), dtype=np.complex) for i in range(data.shape[1]): self.live_chanset.set_channel_data(i, 'time_series', data[:, i]) ft = rfft(data[:, i]) self.live_chanset.add_channel_dataset(i, 'spectrum', ft) ft_datas[:, i] = ft self.live_chanset.set_channel_metadata(tuple(range(data.shape[1])), {'sample_rate': self.rec.rate}) # Check recording mode rec_mode = self.RecUI.get_recording_mode() if rec_mode == 'Normal': # Send data normally self.live_chanset.add_channel_dataset(tuple(range(data.shape[1])), 'frequency', []) self.live_chanset.add_channel_dataset(tuple(range(data.shape[1])), 'transfer_function', []) self.live_chanset.add_channel_dataset(tuple(range(data.shape[1])), 'coherence', []) self.save_transfer_function() elif rec_mode == 'TF Avg.': # Compute the auto- and crossspectrum for average transfer function # while store them in the tally chans = list(range(self.rec.channels)) in_chan = self.RecUI.get_input_channel() chans.remove(in_chan) input_chan_data = ft_datas[:, in_chan] # Check for incorrect data length with previous recorded data if not len(self.autospec_in_tally) == 0: if not input_chan_data.shape[0] == self.autospec_in_tally[ -1].shape[0]: print( 'Data shape does not match, you may have fiddle the settings' ) print( 'Please either clear the past data, or revert the settings' ) self.stats_UI.statusbar.clearMessage() self.RecUI.spec_settings_widget.setEnabled(True) self.RecUI.switch_rec_box.setEnabled(True) return self.autospec_in_tally.append( calculate_auto_spectrum(input_chan_data)) autospec_out = np.zeros((ft_datas.shape[0], ft_datas.shape[1] - 1), dtype=np.complex) crossspec = np.zeros(autospec_out.shape, dtype=np.complex) for i, chan in enumerate(chans): autospec_out[:, i] = calculate_auto_spectrum(ft_datas[:, chan]) crossspec[:, i] = calculate_cross_spectrum( input_chan_data, ft_datas[:, chan]) self.autospec_out_tally.append(autospec_out) self.crossspec_tally.append(crossspec) auto_in_sum = np.array(self.autospec_in_tally).sum(axis=0) auto_out_sum = np.array(self.autospec_out_tally).sum(axis=0) cross_sum = np.array(self.crossspec_tally).sum(axis=0) for i, chan in enumerate(chans): tf_avg, cor = compute_transfer_function( auto_in_sum, auto_out_sum[:, i], cross_sum[:, i]) self.live_chanset.add_channel_dataset(chan, 'transfer_function', tf_avg) self.live_chanset.add_channel_dataset(chan, 'coherence', cor) # Update the average count and send the data self.RecUI.update_TFavg_count(len(self.autospec_in_tally)) self.save_transfer_function() elif rec_mode == 'TF Grid': # TODO: Implement recording for grid transfer function pass else: # TODO?: Failsafe for unknown mode pass # Enable more widgets self.stats_UI.statusbar.clearMessage() self.RecUI.spec_settings_widget.setEnabled(True) self.RecUI.switch_rec_box.setEnabled(True) def undo_tf_tally(self): """ Callback to remove the last autospectrum and crossspectrum in the tally """ if self.autospec_in_tally: self.autospec_in_tally.pop() self.autospec_out_tally.pop() self.crossspec_tally.pop() self.RecUI.update_TFavg_count(len(self.autospec_in_tally)) def remove_tf_tally(self): """ Callback to clear the autospectrum and crossspectrum tallies """ if self.autospec_in_tally: self.autospec_in_tally = [] self.autospec_out_tally = [] self.crossspec_tally = [] self.RecUI.update_TFavg_count(len(self.autospec_in_tally)) # Cancel the data recording def cancel_recording(self): """ Callback to cancel the recording and re-enable the UIs """ self.rec.record_cancel() for btn in self.main_widget.findChildren(QPushButton): btn.setEnabled(True) self.RecUI.switch_rec_box.setEnabled(True) self.RecUI.spec_settings_widget.setEnabled(True) self.RecUI.cancelbtn.setDisabled(True) self.stats_UI.statusbar.clearMessage() #--------------------------- RESET METHODS------------------------------------- def ResetRecording(self): """ Reset the stream to the specified configuration, then calls upon other reset methods """ # Spew errors to console at each major step of the resetting self.stats_UI.statusbar.showMessage('Resetting...') # Stop the update and Delete the stream self.playing = False self.plottimer.stop() self.rec.close() del self.rec try: # Get Input from the Device Configuration UI Rtype, settings = self.devconfig_UI.read_device_config() # Reinitialise the recording object self.rec = Rtype.Recorder() # Set the recorder parameters dev_name = self.rec.available_devices()[0] sel_ind = min(settings[0], len(dev_name) - 1) self.rec.set_device_by_name(dev_name[sel_ind]) self.rec.rate = settings[1] self.rec.channels = settings[2] self.rec.chunk_size = settings[3] self.rec.num_chunk = settings[4] self.devconfig_UI.configboxes[0].setCurrentIndex( dev_name.index(self.rec.device_name)) except: t, v, tb = sys.exc_info() print(t) print(v) print(traceback.format_tb(tb)) print('Cannot set up new recorder') try: # Open the stream self.init_and_check_stream() # Reset channel configs self.ResetPlots() self.levelsplot.reset_channel_peaks(self.rec) self.ResetChanConfigs() self.levelsplot.reset_channel_levels() except: t, v, tb = sys.exc_info() print(t) print(v) print(traceback.format_tb(tb)) print('Cannot stream,restart the app') try: # Set the recorder reference to RecUI and devconfig_UI, and remove # average transfer function tally self.RecUI.set_recorder(self.rec) self.devconfig_UI.set_recorder(self.rec) self.remove_tf_tally() except: t, v, tb = sys.exc_info() print(t) print(v) print(traceback.format_tb(tb)) print('Cannot recording configs') # Reset metadata and change channel toggles and re-connect the signals self.ResetMetaData() self.ResetChanBtns() self.connect_rec_signals() # Restart the plot update timer self.plottimer.start(self.rec.chunk_size * 1000 // self.rec.rate) def ResetPlots(self): """ Reset the plots """ self.ResetXdata() self.timeplot.reset_plotlines() self.freqplot.reset_plotlines() for i in range(self.rec.channels): tplot = self.timeplot.plot() tplot.sigClicked.connect(self.display_chan_config) fplot = self.freqplot.plot() fplot.sigClicked.connect(self.display_chan_config) self.freqplot.plotItem.setRange(xRange=(0, self.freqdata[-1]), yRange=(0, 100 * self.rec.channels)) self.freqplot.plotItem.setLimits(xMin=0, xMax=self.freqdata[-1], yMin=-20) def ResetXdata(self): """ Reset the time and frequencies plot data """ data = self.rec.get_buffer() self.timedata = np.arange(data.shape[0]) / self.rec.rate self.freqdata = np.arange(int(data.shape[0] / 2) + 1) / data.shape[0] * self.rec.rate def ResetChanBtns(self): """ Reset the amount of channel toggling buttons """ self.chantoggle_UI.adjust_channel_checkboxes(self.rec.channels) self.update_chan_names() def ResetChanConfigs(self): """ Reset the channel plot configuration settings """ self.timeplot.reset_offsets() self.timeplot.reset_plot_visible() self.timeplot.reset_colour() self.timeplot.reset_sig_hold() self.freqplot.reset_offsets() self.freqplot.reset_plot_visible() self.freqplot.reset_colour() self.levelsplot.reset_colour() self.chanconfig_UI.chans_num_box.clear() self.chanconfig_UI.chans_num_box.addItems( [str(i) for i in range(self.rec.channels)]) self.chanconfig_UI.chans_num_box.setCurrentIndex(0) self.display_chan_config(0) def ResetMetaData(self): """ Reset the metadata """ self.live_chanset = ChannelSet(self.rec.channels) self.live_chanset.add_channel_dataset(tuple(range(self.rec.channels)), 'time_series') def ResetSplitterSizes(self): """ Reset the metadata """ self.mid_splitter.setSizes( [HEIGHT * 0.48, HEIGHT * 0.48, HEIGHT * 0.04]) self.right_splitter.setSizes([HEIGHT * 0.05, HEIGHT * 0.85]) def update_chan_names(self): """ Update the channel names, obtained from the ChannelSet """ names = self.live_chanset.channel_metadata( tuple(range(self.rec.channels)), 'name') for n, name in enumerate(names): chan_btn = self.chantoggle_UI.chan_btn_group.button(n) chan_btn.setText(name) #----------------------- DATA TRANSFER METHODS ------------------------------- def save_time_series(self): """ Transfer time series data to parent window """ print('Saving time series...') self.sig_time_series_data_saved.emit(self.live_chanset) print('Time series saved!') def save_transfer_function(self): """ Transfer transfer function data to parent window """ print('Saving transfer function...') self.sig_transfer_function_data_saved.emit(self.live_chanset) print('Transfer function saved!') #-------------------------- STREAM METHODS ------------------------------------ def init_and_check_stream(self): """ Attempts to initialise the stream """ if self.rec.stream_init(playback=PLAYBACK): self.stats_UI.togglebtn.setEnabled(True) self.toggle_rec(stop=False) self.stats_UI.statusbar.showMessage('Streaming') else: self.stats_UI.togglebtn.setDisabled(True) self.toggle_rec(stop=True) self.stats_UI.statusbar.showMessage('Stream not initialised!') def connect_rec_signals(self): """ Connects the signals from the recorder object """ self.rec.rEmitter.recorddone.connect(self.stop_recording) self.rec.rEmitter.triggered.connect(self.stats_UI.trigger_message) #self.rec.rEmitter.newdata.connect(self.update_line) #self.rec.rEmitter.newdata.connect(self.update_chanlvls) #----------------------OVERRIDDEN METHODS------------------------------------ def closeEvent(self, event): """ Reimplemented from QMainWindow Stops the update timer, disconnect any signals and delete self """ if self.plottimer.isActive(): self.plottimer.stop() self.sig_closed.emit() self.rec.close() self.sig_transfer_function_data_saved.disconnect() self.sig_time_series_data_saved.disconnect() event.accept() self.deleteLater()
transfer_function_tab_layout.addWidget(self.show_transfer_fn_checkbox, 1, 0) self.transfer_function_tab.setLayout(transfer_function_tab_layout) transfer_function_tab_layout.setColumnStretch(1, 1) transfer_function_tab_layout.setRowStretch(2, 1) self.addTab(self.transfer_function_tab, "Transfer function") if __name__ == '__main__': CurrentWorkspace = Workspace() app = 0 app = QApplication(sys.argv) c = CircleFitWidget() c.CurrentWorkspace = CurrentWorkspace c.showMaximized() cs = ChannelSet() #c.load_transfer_function(cs) import_from_mat("../../tests/transfer_function_clean.mat", cs) # cs.channels[0].dataset("transfer_function").data /= 1j*cs.channels[0].data("omega") # c.transfer_function_type = 'velocity' c.transfer_function_type = 'acceleration' c.set_selected_channels(cs.channels) #c.set_data(np.linspace(0, cs.channel_metadata(0, "sample_rate"), a.size), a) #""" sys.exit(app.exec_())
class AnalysisWindow(QMainWindow): """ The main window for analysing and processing data. Attributes ---------- cs : :class:`~cued_datalogger.api.channel.ChannelSet` The ChannelSet containing all the data. Data is accessed through this ChannelSet. menubar : :class:`PyQt5.QtWidgets.QMenuBar` toolbox : :class:`~cued_datalogger.api.toolbox.MasterToolbox` The widget containing local tools and operations. Contains four toolboxes: :attr:`time_toolbox`, :attr:`frequency_toolbox`, :attr:`sonogram_toolbox`, :attr:`circle_fit_toolbox`. time_toolbox : :class:`~cued_datalogger.analysis.time_domain.TimeToolbox` frequency_toolbox : :class:`~cued_datalogger.analysis.frequency_domain.FrequencyToolbox` sonogram_toolbox : :class:`~cued_datalogger.analysis.sonogram.SonogramToolbox` circle_fit_toolbox : :class:`~cued_datalogger.analysis.circle_fit.CircleFitToolbox` display_tabwidget : :class:`~cued_datalogger.analysis_window.AnalysisDisplayTabWidget` The central widget for display. freqdomain_widget : :class`~cued_datalogger.analysis.frequency_domain.FrequencyDomainWidget` sonogram_widget : :class:`~cued_datalogger.analysis.sonogram.SonogramDisplayWidget` circle_widget : :class:`~cued_datalogger.analysis.circle_fit.CircleFitWidget` global_master_toolbox : :class:`~cued_datalogger.api.toolbox.MasterToolbox` The master toolbox containing the :attr:`global_toolbox`. global_toolbox : :class:`~cued_datalogger.api.toolbox.Toolbox` The widget containing global tools and operations. Has five tabs, containing: <acquisition window launcher>, :attr:`channel_select_widget`, :attr:`channel_metadata_widget`, :attr:`addon_widget`, :attr:`import_widget`. acquisition_window : :class:`~cued_datalogger.acquisition_window.AcquisitionWindow` channel_select_widget : :class:`~cued_datalogger.api.channel.ChannelSelectWidget` channel_metadata_widget : :class:`~cued_datalogger.api.channel.ChannelMetadataWidget` addon_widget : :class:`~cued_datalogger.api.addons.AddonManager` import_widget : :class:`~cued_datalogger.api.file_import.DataImportWidget` """ def __init__(self): super().__init__() self.setWindowTitle('AnalysisWindow') self.create_test_channelset() self._init_ui() self.setFocus() self.showMaximized() def _init_ui(self): # Add the drop-down menu self.menubar = self.menuBar() self.menubar.addMenu(ProjectMenu(self)) # # Create the main widget self.splitter = QSplitter(self) self.splitter.setHandleWidth(5) self.splitter.setChildrenCollapsible(False) self.setCentralWidget(self.splitter) # Create the analysis tools tab widget self._init_display_tabwidget() # Create the toolbox self._init_toolbox() self.display_tabwidget.currentChanged.connect(self.toolbox.set_toolbox) self.display_tabwidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) # Create the global toolbox self._init_global_master_toolbox() # Configure self.update_channelset() self.goto_time_series() # Add the widgets self.splitter.addWidget(self.toolbox) self.splitter.addWidget(self.display_tabwidget) self.splitter.addWidget(self.global_master_toolbox) def _init_display_tabwidget(self): """Create the display tabwidget.""" self.display_tabwidget = QTabWidget(self) self.timedomain_widget = TimeDomainWidget() self.freqdomain_widget = FrequencyDomainWidget() self.sonogram_widget = SonogramDisplayWidget() self.circle_widget = CircleFitWidget() # Create the tabs self.display_tabwidget.addTab(self.timedomain_widget, "Time Domain") self.display_tabwidget.addTab(self.freqdomain_widget, "Frequency Domain") self.display_tabwidget.addTab(self.sonogram_widget, "Sonogram") self.display_tabwidget.addTab(self.circle_widget, "Circle Fit") self.display_tabwidget.currentChanged.connect( lambda: self.set_selected_channels(self.channel_select_widget. selected_channels())) def _init_toolbox(self): """Create the master toolbox""" self.toolbox = MasterToolbox(self) self.toolbox.sig_collapsed_changed.connect(self._update_splitter) # # Time toolbox self.time_toolbox = TimeToolbox(self.toolbox) self.time_toolbox.sig_convert_to_fft.connect( self.goto_frequency_spectrum) self.time_toolbox.sig_convert_to_sonogram.connect(self.goto_sonogram) # # Frequency toolbox self.frequency_toolbox = FrequencyToolbox(self.toolbox) self.frequency_toolbox.sig_calculate_transfer_function.connect( self.freqdomain_widget.calculate_transfer_function) self.frequency_toolbox.sig_convert_to_circle_fit.connect( self.goto_circle_fit) self.frequency_toolbox.sig_plot_frequency_spectrum.connect( lambda: self.freqdomain_widget.update_plot(False)) self.frequency_toolbox.sig_plot_transfer_function.connect( lambda: self.freqdomain_widget.update_plot(True)) self.frequency_toolbox.sig_plot_type_changed.connect( self.freqdomain_widget.set_plot_type) self.frequency_toolbox.sig_show_coherence.connect( self.freqdomain_widget.set_show_coherence) # # Sonogram toolbox self.sonogram_toolbox = SonogramToolbox(self.toolbox) self.sonogram_toolbox.sig_contour_spacing_changed.connect( self.sonogram_widget.update_contour_spacing) self.sonogram_toolbox.sig_num_contours_changed.connect( self.sonogram_widget.update_num_contours) self.sonogram_toolbox.sig_window_overlap_fraction_changed.connect( self.sonogram_widget.update_window_overlap_fraction) self.sonogram_toolbox.sig_window_width_changed.connect( self.sonogram_widget.update_window_width) # # Circle Fit toolbox self.circle_fit_toolbox = CircleFitToolbox(self.toolbox) self.circle_fit_toolbox.sig_show_transfer_fn.connect( self.circle_widget.show_transfer_fn) self.circle_fit_toolbox.sig_construct_transfer_fn.connect( self.circle_widget.construct_transfer_fn) self.toolbox.add_toolbox(self.time_toolbox) self.toolbox.add_toolbox(self.frequency_toolbox) self.toolbox.add_toolbox(self.sonogram_toolbox) self.toolbox.add_toolbox(self.circle_fit_toolbox) self.toolbox.set_toolbox(0) def _init_global_master_toolbox(self): self.global_master_toolbox = MasterToolbox() self.global_master_toolbox.sig_collapsed_changed.connect( self._update_splitter) self.global_toolbox = Toolbox('right', self.global_master_toolbox) # # Acquisition Window self.acquisition_window = None self.device_config = DevConfigUI() self.device_config.config_button.setText('Open AcquisitionWindow') self.device_config.config_button.clicked.connect( self.open_acquisition_window) self.global_toolbox.addTab(self.device_config, 'Acquisition Window') # # Channel Selection self.channel_select_widget = ChannelSelectWidget(self.global_toolbox) self.channel_select_widget.sig_channel_selection_changed.connect( self.set_selected_channels) self.global_toolbox.addTab(self.channel_select_widget, 'Channel Selection') # # Channel Metadata self.channel_metadata_widget = ChannelMetadataWidget( self.global_toolbox) self.global_toolbox.addTab(self.channel_metadata_widget, 'Channel Metadata') self.channel_metadata_widget.metadataChange.connect( self.update_channelset) # # Addon Manager self.addon_widget = AddonManager(self) self.global_toolbox.addTab(self.addon_widget, 'Addon Manager') # # Import self.import_widget = DataImportWidget(self) self.import_widget.sig_extend_channelset.connect( self.extend_channelset) self.import_widget.sig_replace_channelset.connect( self.replace_channelset) self.global_toolbox.addTab(self.import_widget, 'Import Files') # # Export self.export_widget = DataExportWidget(self) self.global_toolbox.addTab(self.export_widget, 'Export Files') self.global_master_toolbox.add_toolbox(self.global_toolbox) def _update_splitter(self): # Get the current sizes of everything sizes = self.splitter.sizes() # Adjust the size of the sender to be equal to its size hint for i in range(self.splitter.count()): if self.sender() == self.splitter.widget(i): sizes[i] = self.sender().sizeHint().width() # Set the sizes self.splitter.setSizes(sizes) self.splitter.setStretchFactor(0, 0) self.splitter.setStretchFactor(1, 1) self.splitter.setStretchFactor(2, 0) #---------------------- Acquisition window methods ------------------------ def open_acquisition_window(self): if not self.acquisition_window: recType, configs = self.device_config.read_device_config() if not any([c is None for c in configs]): self.acquisition_window = AcquisitionWindow( self, recType, configs) else: self.acquisition_window = AcquisitionWindow(self) self.acquisition_window.sig_closed.connect( self.close_acquisition_window) self.acquisition_window.sig_transfer_function_data_saved.connect( self.receive_data) self.acquisition_window.sig_time_series_data_saved.connect( self.receive_data) self.acquisition_window.show() def receive_data(self, received_cs): self.replace_channelset(received_cs) if self.cs.channels[0].is_dataset("transfer_function"): self.goto_transfer_function() else: self.goto_time_series() def auto_change_tab(self): current_widget = self.display_tabwidget.currentWidget() if (current_widget is self.circle_widget and self.cs.channels[0].is_dataset("transfer_function")): # Remain here pass elif (current_widget is self.sonogram_widget and self.cs.channels[0].is_dataset("sonogram")): # Remain here pass elif (current_widget is self.freqdomain_widget and self.cs.channels[0].is_dataset("spectrum")): # Remain here # Switch the plot type self.frequency_toolbox.set_plot_spectrum() elif (current_widget is self.freqdomain_widget and self.cs.channels[0].is_dataset("transfer_function")): # Remain here # Switch the plot type self.frequency_toolbox.set_plot_transfer_function() else: if self.cs.channels[0].is_dataset("transfer_function"): self.display_tabwidget.setCurrentWidget(self.freqdomain_widget) self.frequency_toolbox.set_plot_transfer_function() elif self.cs.channels[0].is_dataset("spectrum"): self.display_tabwidget.setCurrentWidget(self.freqdomain_widget) self.frequency_toolbox.set_plot_spectrum() elif self.cs.channels[0].is_dataset("sonogram"): self.display_tabwidget.setCurrentWidget(self.sonogram_widget) else: self.display_tabwidget.setCurrentWidget(self.timedomain_widget) def close_acquisition_window(self): self.acquisition_window.sig_closed.disconnect() self.acquisition_window = None #---------------------------- Tab methods --------------------------------- def goto_time_series(self, switch_to_tab=True): if switch_to_tab: self.display_tabwidget.setCurrentWidget(self.timedomain_widget) self.timedomain_widget.set_selected_channels( self.channel_select_widget.selected_channels()) def goto_frequency_spectrum(self, switch_to_tab=True): if switch_to_tab: # Switch to frequency domain tab self.display_tabwidget.setCurrentWidget(self.freqdomain_widget) self.freqdomain_widget.set_selected_channels( self.channel_select_widget.selected_channels()) self.freqdomain_widget.calculate_spectrum() self.frequency_toolbox.set_plot_spectrum() def goto_transfer_function(self, switch_to_tab=True): if switch_to_tab: self.display_tabwidget.setCurrentWidget(self.freqdomain_widget) self.freqdomain_widget.set_selected_channels( self.channel_select_widget.selected_channels()) # TODO: calculate TF function if none is found self.freqdomain_widget.calculate_transfer_function() self.frequency_toolbox.set_plot_transfer_function() def goto_sonogram(self, switch_to_tab=True): if switch_to_tab: self.display_tabwidget.setCurrentWidget(self.sonogram_widget) self.sonogram_widget.set_selected_channels( self.channel_select_widget.selected_channels()) self.sonogram_widget.calculate_sonogram() def goto_circle_fit(self, switch_to_tab=True): if switch_to_tab: self.display_tabwidget.setCurrentWidget(self.circle_widget) self.circle_widget.set_selected_channels( self.channel_select_widget.selected_channels()) #------------------ ChannelSet methods ------------------------------------ def create_test_channelset(self): self.cs = ChannelSet(5) t = np.arange(0, 0.5, 1 / 5000) for i, channel in enumerate(self.cs.channels): self.cs.set_channel_metadata(i, {'sample_rate': 5000}) self.cs.add_channel_dataset(i, 'time_series', np.sin(t * 2 * np.pi * 100 * (i + 1))) channel.name = "Channel {}".format(i) self.cs.add_channel_dataset( i, 'time_series', np.sin(t * 2 * np.pi * 100 * (i + 1)) * np.exp(-t / t[-1])) def extend_channelset(self, cs): if isinstance(cs, ChannelSet): self.cs.channels.extend(cs.channels) self.update_channelset() self.auto_change_tab() else: print("Failed to extend ChannelSet: {} not a ChannelSet".format( type(cs))) def replace_channelset(self, cs): if isinstance(cs, ChannelSet): self.cs = cs self.update_channelset() self.auto_change_tab() else: print("Failed to replace ChannelSet: {} not a ChannelSet".format( type(cs))) def set_selected_channels(self, channels): self.display_tabwidget.currentWidget().set_selected_channels(channels) if self.display_tabwidget.currentWidget() is self.sonogram_widget: self.sonogram_toolbox.set_selected_channels(channels) def update_channelset(self): self.channel_select_widget.set_channel_set(self.cs) self.channel_metadata_widget.set_channel_set(self.cs) self.export_widget.set_channel_set(self.cs)
def export_to_mat(file, order, channel_set=None, back_comp=False): """ Export data and metadata from ChannelsSet to a mat file. Support backward compatibility with the old logger. Still in alpha stage. Parameters ---------- file : path_to_file The path to the ``.mat`` file to import data from. order : int Order of the channel saved channel_set : ChannelSet The ChannelSet to save the imported data and metadata to. If ``None``, a new ChannelSet is created and returned. back_comp: bool Whether to export as the old format """ if channel_set is None: new_channel_set = True channel_set = ChannelSet(len(order)) else: new_channel_set = False # Obtain the ids and metadata names data_ids = channel_set.channel_ids(order) var_names = set([y for x in data_ids for y in x]) meta_names = channel_set.channel_metadata(0) if not back_comp: # Save all the variable names and metadata names as dicts variables = {} for name in var_names: data = channel_set.channel_data(order, name) variables[name] = data for mname in meta_names: mdata = channel_set.channel_metadata(order, mname) variables[mname] = mdata # Save the dict as matlab file sio.savemat(file, variables, appendmat=False) if new_channel_set: return channel_set else: # Save as the old format, with individual MAT file for each type of data sampling_rate = channel_set.channel_metadata(0, 'sample_rate') calibration_factor = channel_set.channel_metadata( 0, 'calibration_factor') # Save Time Series if 'time_series' in var_names: time_series_data = np.array( channel_set.channel_data(order, 'time_series')) n_samples = time_series_data[0].shape[0] variables = { 'indata': np.transpose(time_series_data), 'freq': float(sampling_rate), 'dt2': [float(len(order)), 0, 0], 'buflen': float(n_samples), 'tsmax': float(calibration_factor) } time_series_fname = file[:-4] + '_TimeSeries.mat' sio.savemat(time_series_fname, variables, appendmat=False) # Save FFT if 'spectrum' in var_names: fft_data = np.array(channel_set.channel_data(order, 'spectrum')) n_samples = fft_data[0].shape[0] variables = { 'yspec': np.transpose(fft_data), 'freq': float(sampling_rate), 'dt2': [0, float(len(order)), 0], 'npts': float((n_samples - 1) * 2), 'tfun': 0 } time_series_fname = file[:-4] + '_FFT.mat' sio.savemat(time_series_fname, variables, appendmat=False) # Save TF if 'TF' in var_names: TF_data = np.array(channel_set.channel_data(order, 'TF')) TF_data = [data for data in TF_data if not data.shape[0] == 0] n_samples = TF_data[0].shape[0] variables = { 'yspec': np.transpose(TF_data), 'freq': float(sampling_rate), 'dt2': [0, float(len(order) - 1), 0], 'npts': float((n_samples - 1) * 2), 'tfun': 1 } time_series_fname = file[:-4] + '_TF.mat' sio.savemat(time_series_fname, variables, appendmat=False) # Save Sonogram if 'sonogram' in var_names: sono_data = channel_set.channel_data(order[0], 'sonogram') if not sono_data.shape[0] == 0: sono_phase = channel_set.channel_data(order[0], 'sonogram_phase') sono_step = channel_set.channel_data(order[0], 'sonogram_step') n_samples = sono_data[0].shape[0] variables = { 'yson': to_dB(np.abs(sono_data)), 'freq': float(sampling_rate), 'dt2': [0, 0, 1], 'npts': float((n_samples - 1) * 2), 'sonstep': float(sono_step), 'yphase': sono_phase } time_series_fname = file[:-4] + '_sonogram.mat' sio.savemat(time_series_fname, variables, appendmat=False)
def __init__(self, parent): super().__init__(parent) self.cs = ChannelSet() self.order = list(range(len(self.cs))) self.init_UI()
class DataExportWidget(QWidget): """ A proof-of-concept widget to show that exporting data is possible. Currently, it saves all of the available variables. Alpha stage. Attributes ---------- cs : ChannelSet Reference to the channelSet order : list The order of channels to be saved """ def __init__(self, parent): super().__init__(parent) self.cs = ChannelSet() self.order = list(range(len(self.cs))) self.init_UI() def init_UI(self): """ Construct the UI """ layout = QVBoxLayout(self) self.channel_listview = QListWidget(self) layout.addWidget(self.channel_listview) shift_btn_layout = QHBoxLayout() shift_up_btn = QPushButton('Shift Up', self) shift_up_btn.clicked.connect(lambda: self.shift_list('up')) shift_down_btn = QPushButton('Shift Down', self) shift_down_btn.clicked.connect(lambda: self.shift_list('down')) shift_btn_layout.addWidget(shift_up_btn) shift_btn_layout.addWidget(shift_down_btn) self.pickle_export_btn = QPushButton('Export as Pickle', self) self.pickle_export_btn.clicked.connect(self.pickle_file) self.back_comp_btn = QCheckBox('Backward Compatibility?', self) self.mat_export_btn = QPushButton('Export as MAT', self) self.mat_export_btn.clicked.connect(self.export_files) # TODO: Have a preview of what is being saved? layout.addWidget(QLabel('ChannelSet Saving Order', self)) layout.addWidget(self.channel_listview) layout.addLayout(shift_btn_layout) layout.addWidget(self.pickle_export_btn) layout.addWidget(self.back_comp_btn) layout.addWidget(self.mat_export_btn) def set_channel_set(self, channel_set): """ Set the ChannelSet reference """ self.cs = channel_set self.order = list(range(len(self.cs))) self.update_list() self.channel_listview.setCurrentRow(0) def shift_list(self, mode): """ Shift the channel saving order Parameter --------- mode : str either 'up' or 'down', indicating the shifting direction """ ind = self.channel_listview.currentRow() if mode == 'up': if not ind == 0: self.order[ind - 1], self.order[ind] = self.order[ind], self.order[ ind - 1] ind -= 1 elif mode == 'down': if not ind == self.channel_listview.count() - 1: self.order[ind], self.order[ind + 1] = self.order[ind + 1], self.order[ind] ind += 1 self.update_list() self.channel_listview.setCurrentRow(ind) def update_list(self): """ Update the list with the current saving order """ self.channel_listview.clear() names = self.cs.channel_metadata(tuple(self.order), 'name') full_names = [ str(self.order[i]) + ' : ' + names[i] for i in range(len(names)) ] self.channel_listview.addItems(full_names) def export_files(self): """ Export the file to the url selected """ url = QFileDialog.getSaveFileName(self, "Export Data", "", "MAT Files (*.mat)")[0] if url: if self.back_comp_btn.checkState() == Qt.Checked: export_to_mat(url, tuple(self.order), self.cs, back_comp=True) else: export_to_mat(url, tuple(self.order), self.cs) def pickle_file(self): ''' This is probably a temporary solution to saving data. The pickle file is only intended for internal use only. Please make sure no one tampers with the files. Also, only open files that YOU have pickled. ''' url = QFileDialog.getSaveFileName(self, "Export Data", "", "Pickle Files (*.pickle)")[0] if url: saved_cs = ChannelSet(len(self.order)) for i, n in enumerate(self.order): saved_cs.channels[i] = self.cs.channels[n] with open(url, 'wb') as f: pickle.dump(saved_cs, f)
"MAT Files (*.mat)")[0] if url: if self.back_comp_btn.checkState() == Qt.Checked: export_to_mat(url, tuple(self.order), self.cs, back_comp=True) else: export_to_mat(url, tuple(self.order), self.cs) def pickle_file(self): ''' This is probably a temporary solution to saving data. The pickle file is only intended for internal use only. Please make sure no one tampers with the files. Also, only open files that YOU have pickled. ''' url = QFileDialog.getSaveFileName(self, "Export Data", "", "Pickle Files (*.pickle)")[0] if url: saved_cs = ChannelSet(len(self.order)) for i, n in enumerate(self.order): saved_cs.channels[i] = self.cs.channels[n] with open(url, 'wb') as f: pickle.dump(saved_cs, f) if __name__ == '__main__': cs = ChannelSet(4) cs.add_channel_dataset((0, 1, 2, 3), 'time_series', np.random.rand(5, 1)) cs.set_channel_metadata(0, {'name': 'Nope'}) cs.set_channel_metadata(1, {'name': 'Lol'}) export_to_mat('btest.mat', (0, 1, 2, 3), cs, True)