コード例 #1
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')
コード例 #2
0
 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]))
コード例 #3
0
 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)
コード例 #4
0
 def clear(self):
     self.new_cs = ChannelSet()
     self.set_channel_set(self.new_cs)
コード例 #5
0
 def __init__(self, parent):
     super().__init__(parent)
     self.new_cs = ChannelSet()
     self.init_UI()
コード例 #6
0
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
コード例 #7
0
    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))
コード例 #8
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()
コード例 #9
0
        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_())
コード例 #10
0
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)
コード例 #11
0
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)
コード例 #12
0
 def __init__(self, parent):
     super().__init__(parent)
     self.cs = ChannelSet()
     self.order = list(range(len(self.cs)))
     self.init_UI()
コード例 #13
0
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)
コード例 #14
0
                                          "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)