Пример #1
0
class ExceptionHandler(QtCore.QObject):

    errorSignal = QtCore.pyqtSignal()
    silentSignal = QtCore.pyqtSignal()

    def __init__(self):
        super(ExceptionHandler, self).__init__()

    def handler(self, exctype, value, traceback):
        global failure_
        self.errorSignal.emit()
        failure_ = (exctype, value, traceback)
        sys._excepthook(exctype, value, traceback)
Пример #2
0
class ComponentsList(QtCore.QObject):
    '''
    Keep track of Components in a list and emit signals.
    Methods 'append' and 'remove' are provided and emit signals,
    direct access to the list is allowed with 'ComponentList.list',
    but not recommended.'''
    componentAppended = QtCore.pyqtSignal(object, name="ComponentAppended")
    componentRemoved = QtCore.pyqtSignal(object, name="ComponentRemoved")

    def __init__(self):
        super(ComponentsList, self).__init__()
        self.list = []

    def append(self, item):
        self.list.append(item)
        self.componentAppended.emit(item)

    def remove(self, item):
        self.list.remove(item)
        self.componentRemoved.emit(item)

    def index(self, item):
        return self.list.index(item)

    def __delitem__(self, key):
        self.list.__delitem__(key)

    def __getitem__(self, key):
        return self.list.__getitem__(key)

    def __setitem__(self, key, value):
        self.list.__setitem__(key, value)

    def __len__(self):
        return len(self.list)

    def __repr__(self):
        return self.list.__repr__()
Пример #3
0
class Trace(QtCore.QThread):
    """ timer for the IOC measurement"""
    ##--------Signal Definition--------##
    started = QtCore.pyqtSignal()
    finished = QtCore.pyqtSignal('PyQt_PyObject')
    ##--------CONSTRUCTOR--------##    
    def __init__(self,interval): # t--interval call the hFunction
        QtCore.QThread.__init__(self)
        self.interval = interval
        self.evented = threading.Event()  # 引入事件进行定时器的设置
        self.trace_points = None
        self.trace_time = None
        self.trace_value = {} 
        self.trace_triggerlevel = None
        self.trace_offsettime = None     
    #Execute
    def run(self):
        self.started.emit()
        while not self.evented.is_set():
            self.evented.wait(self.interval)
            #self.trace_points = caget('SRFLab-010:RFS-PM-01:Trace_points.VAL', as_string=False)
            self.trace_time = caget('SRFLab-010:RFS-PM-01:Trace_time.VAL', as_string=False)
            self.trace_value = caget('SRFLab-010:RFS-PM-01:Value_trace.VAL', as_string=False)
            self.trace_triggerlevel = caget('SRFLab-010:RFS-PM-01:Trace_level_callback_DBM.VAL', as_string=False)
            self.trace_offsettime = caget('SRFLab-010:RFS-PM-01:Trace_offset_time_callback.VAL',as_string=False)
            self.trace_points = len(self.trace_value)
            psen = TraceMeasure()
            psen._array = self.trace_value
            psen._points = self.trace_points
            psen._time = self.trace_time
            psen._triggerlevel = self.trace_triggerlevel
            psen._offsetTime = self.trace_offsettime
            self.finished.emit(psen)
    #STOP
    def cancel(self):
        self.evented.set()
Пример #4
0
class HelpScreen(QMainWindow):
    clicked = QtCore.pyqtSignal()

    def __init__(self, parent=None):
        super(HelpScreen, self).__init__()

        # Object for data persistence
        # self.settings = QtCore.QSettings('UHSLC', 'com.uhslc.qcsoft')
        # st.SETTINGS.remove("savepath")
        self.ui = parent

        # If a save path hasn't been defined, give it a home directory
        if (st.get_path(st.SAVE_KEY)):
            self.ui.lineEditPath.setPlaceholderText(st.get_path(st.SAVE_KEY))
        else:
            st.SETTINGS.setValue(st.SAVE_KEY, os.path.expanduser('~'))
            self.ui.lineEditPath.setPlaceholderText(os.path.expanduser('~'))

        self.ui.lineEditLoadPath.setPlaceholderText(st.get_path(st.LOAD_KEY))

        # If a fast delivery save path hasn't been defined, give it a home directory
        if (st.get_path(st.FD_PATH)):
            self.ui.lineEditFDPath.setPlaceholderText(st.get_path(st.FD_PATH))
        else:
            st.SETTINGS.setValue(st.FD_PATH, os.path.expanduser('~'))
            self.ui.lineEditFDPath.setPlaceholderText(os.path.expanduser('~'))

        # If a high frequency data save path hasn't been defined, give it a home directory
        if (st.get_path(st.HF_PATH)):
            self.ui.lineEditHFPath.setPlaceholderText(st.get_path(st.HF_PATH))
        else:
            st.SETTINGS.setValue(st.HF_PATH, os.path.expanduser('~'))
            self.ui.lineEditHFPath.setPlaceholderText(os.path.expanduser('~'))

        if st.get_path(st.DIN_PATH):
            self.ui.lineEdit_din.setPlaceholderText(st.get_path(st.DIN_PATH))

        saveButton = self.ui.pushButton_save_folder
        loadButton = self.ui.pushButton_load_folder
        dinSave = self.ui.pushButton_din
        FDSave = self.ui.pushButton_fd_folder
        hf_save = self.ui.pushButton_hf_data

        saveButton.clicked.connect(
            lambda: self.savePath(self.ui.lineEditPath, st.SAVE_KEY))
        loadButton.clicked.connect(
            lambda: self.savePath(self.ui.lineEditLoadPath, st.LOAD_KEY))
        dinSave.clicked.connect(
            lambda: self.saveDIN(self.ui.lineEdit_din, st.DIN_PATH))
        FDSave.clicked.connect(
            lambda: self.savePath(self.ui.lineEditFDPath, st.FD_PATH))
        hf_save.clicked.connect(
            lambda: self.savePath(self.ui.lineEditFDPath, st.HF_PATH))

    def savePath(self, lineEditObj, setStr):
        folder_name = QtWidgets.QFileDialog.getExistingDirectory(
            self, 'Select a Folder')
        if (folder_name):
            st.SETTINGS.setValue(setStr, folder_name)
            st.SETTINGS.sync()
            lineEditObj.setPlaceholderText(st.get_path(setStr))
            lineEditObj.setText("")
        else:
            pass

    def saveDIN(self, lineEditObj, setStr):
        filters = "*.din"
        if st.DIN_PATH:
            path = st.DIN_PATH
        else:
            path = os.path.expanduser('~')
        file_name = QtWidgets.QFileDialog.getOpenFileNames(
            self, 'Open File', path, filters)
        if (file_name):
            st.SETTINGS.setValue(setStr, file_name[0][0])
            st.SETTINGS.sync()
            lineEditObj.setPlaceholderText(st.get_path(setStr))
            lineEditObj.setText("")
        else:
            pass
Пример #5
0
class Start(QMainWindow):
    clicked = QtCore.pyqtSignal()

    def __init__(self, parent=None):
        super(Start, self).__init__()
        self.ui = parent
        self.sens_objects = {
        }  ## Collection of Sensor objects for station for one month
        self.home()

    def home(self):
        print("HOME CALLED")
        # print("static_canvas",self.static_canvas)
        self.ui.mplwidget_top.canvas.figure.clf()
        self.ui.mplwidget_bottom.canvas.figure.clf()
        self._static_ax = self.ui.mplwidget_top.canvas.figure.subplots()
        self._static_fig = self.ui.mplwidget_top.canvas.figure
        self.pid = -99
        self.cid = -98
        self.toolbar1 = self._static_fig.canvas.toolbar  # Get the toolbar handler
        self.toolbar1.update()  # Update the toolbar memory
        # self._residual_fig = self.ui.mplwidget_bottom.canvas.figure
        self._residual_ax = self.ui.mplwidget_bottom.canvas.figure.subplots()
        self.ui.save_btn.clicked.connect(self.save_to_ts_files)
        self.ui.ref_level_btn.clicked.connect(self.show_ref_dialog)

    def make_sensor_buttons(self, sensors):
        # Remove all sensor checkbox widgets from the layout
        # every time new data is loaded
        for i in range(self.ui.verticalLayout_left_top.count()):
            item = self.ui.verticalLayout_left_top.itemAt(i)
            # self.verticalLayout_left_top.removeWidget(item.widget())
            widget = item.widget()
            if widget is not None:
                widget.deleteLater()
        for i in range(self.ui.verticalLayout_bottom.count()):
            item = self.ui.verticalLayout_bottom.itemAt(i)
            # self.verticalLayout_left_top.removeWidget(item.widget())
            widget = item.widget()
            if widget is not None:
                widget.deleteLater()

        # sensors' keys are names of all sensors which carry
        # all of the data associated with it
        # Make copy of it so we can use its keys and assign radio buttons to it
        # If we do not make a copy then the  sensors values would get
        # overwritten by radio button objects
        self.sensor_dict = dict(sensors)
        self.sensor_dict2 = dict(sensors)

        # Counter added to figure out when the last item was added
        # Set alignment of the last item to push all the radio buttons up
        counter = len(sensors.items())
        for key, value in sensors.items():
            counter -= 1
            self.sensor_radio_btns = QtWidgets.QRadioButton(key, self)
            self.sensor_check_btns = QtWidgets.QCheckBox(key, self)
            self.sensor_dict[key] = self.sensor_radio_btns
            self.sensor_dict2[key] = self.sensor_check_btns
            self.ui.buttonGroup_data.addButton(self.sensor_dict[key])
            self.ui.buttonGroup_residual.addButton(self.sensor_dict2[key])
            if (counter > 0):
                self.ui.verticalLayout_left_top.addWidget(
                    self.sensor_dict[key])
                self.ui.verticalLayout_bottom.addWidget(self.sensor_dict2[key])
            else:
                self.ui.verticalLayout_left_top.addWidget(
                    self.sensor_dict[key], 0, QtCore.Qt.AlignTop)
                self.ui.verticalLayout_bottom.addWidget(
                    self.sensor_dict2[key], 0, QtCore.Qt.AlignTop)

            self.sensor_dict[key].setText(key)

        # radio_btn_HF = QtWidgets.QRadioButton("Minute", self)
        # radio_btn_HF.setChecked(True)
        self.mode = self.ui.radioButton_Minute.text()

        self.sensor_dict["PRD"].setChecked(True)
        self.sens_str = "PRD"
        # self.sensor_dict2["PRD"].setEnabled(False)
        self.sensor_dict2["ALL"].setEnabled(False)
        self.plot(all=False)
        self.ui.buttonGroup_data.buttonClicked.connect(self.on_sensor_changed)
        self.ui.buttonGroup_residual.buttonClicked.connect(
            self.on_residual_sensor_changed)
        self.ui.buttonGroup_resolution.buttonClicked.connect(
            self.on_frequency_changed)

    def on_sensor_changed(self, btn):
        print(btn.text())
        if (btn.text() == "ALL"):
            # TODO: plot_all and plot should be merged to one function
            self.ui.save_btn.setEnabled(False)
            self.ui.ref_level_btn.setEnabled(False)
            self.plot(all=True)
        else:
            self.ui.save_btn.setEnabled(True)
            self.ui.ref_level_btn.setEnabled(True)
            self.sens_str = btn.text()
            self._update_top_canvas(btn.text())
            self.ui.lineEdit.setText(
                self.sens_objects[self.sens_str].header[0])
            self.update_graph_values()

        # Update residual buttons and graph when the top sensor is changed
        # self.on_residual_sensor_changed(None)
        # Clear residual buttons and graph when the top sensor is changed
        for button in self.ui.buttonGroup_residual.buttons():
            button.setChecked(False)
        self._residual_ax.cla()
        self._residual_ax.figure.canvas.draw()
        # print("ref height:",self.sens_objects[self.sens_str].height)

    def on_frequency_changed(self, btn):
        print("Frequency changed", btn.text())
        self.mode = btn.text()
        if (self.mode == "Minute"):
            self.sensor_dict2["PRD"].setEnabled(True)
        else:
            self.sensor_dict2["PRD"].setEnabled(False)
        self.on_residual_sensor_changed()

    def update_graph_values(self):
        # convert 'nans' back to 9999s
        nan_ind = np.argwhere(np.isnan(self.browser.data))
        self.browser.data[nan_ind] = 9999
        # we want the sensor data object to point to self.browser.data and not self.browser.data.copy()
        # because when the self.browser.data is modified on the graph
        # the sensor data object will automatically be modified as well
        self.sens_objects[self.sens_str].data = self.browser.data

    def on_residual_sensor_changed(self):
        self._residual_ax.cla()
        self._residual_ax.figure.canvas.draw()

        checkedItems = [
            button for button in self.ui.buttonGroup_residual.buttons()
            if button.isChecked()
        ]
        if (checkedItems):
            for button in checkedItems:
                self.calculate_and_plot_residuals(self.sens_str, button.text(),
                                                  self.mode)
        else:
            self._residual_ax.cla()
            self._residual_ax.figure.canvas.draw()

    def plot(self, all=False):
        # Set the data browser object to NoneType on every file load
        self.browser = None
        self._static_ax.cla()
        self._residual_ax.cla()
        self._residual_ax.figure.canvas.draw()

        if all:
            lineEditText = 'No Header -- Plotting all sensors'
            sens_objects = self.sens_objects
            title = 'Relative levels = signal - average over selected period'
        else:
            lineEditText = self.sens_objects[self.sens_str].header[0]
            sens_objects = [self.sens_str]
            title = 'Tide Prediction'
        self.ui.lineEdit.setText(lineEditText)
        for sens in sens_objects:
            ## Set 9999s to NaN so they don't show up on the graph
            ## when initially plotted
            ## nans are converted back to 9999s when file is saved
            if sens == "ALL":
                pass
            else:
                data_flat = self.sens_objects[sens].get_flat_data()
                time = self.sens_objects[sens].get_time_vector()
                nines_ind = np.where(data_flat == 9999)
                data_flat[nines_ind] = float('nan')
                if all:
                    mean = np.nanmean(data_flat)
                else:
                    mean = 0
                # t = np.linspace(0, 10, 501)
                # t = np.arange(data.size)

                t = time
                y = data_flat - mean
                # self._static_ax.plot(t, np.tan(t), ".")
                line, = self._static_ax.plot(
                    t, y, '-', picker=5, lw=0.5,
                    markersize=3)  # 5 points tolerance
                # self._static_fig = self.static_canvas.figure
                if all:
                    line.set_label(sens)
                    self._static_ax.legend()
                self._static_ax.set_title(title)

                self._static_ax.autoscale(enable=True, axis='both', tight=True)
                self._static_ax.set_xlim([t[0], t[-1]])
                self._static_ax.margins(0.05, 0.05)

                self.ui.mplwidget_top.canvas.setFocusPolicy(
                    QtCore.Qt.ClickFocus)
                self.ui.mplwidget_top.canvas.setFocus()
                self.ui.mplwidget_top.canvas.figure.tight_layout()
                # self.toolbar1 = self._static_fig.canvas.toolbar #Get the toolbar handler
                self.toolbar1.update()  # Update the toolbar memory
                self.ui.mplwidget_top.canvas.draw()

    def calculate_and_plot_residuals(self, sens_str1, sens_str2, mode):
        # resample_freq = min(int(self.sens_objects[sens_str1].rate), int(self.sens_objects[sens_str2].rate))
        # _freq = str(resample_freq)+'min'
        #
        # # Get a date range to create pandas time Series
        # # using the sampling frequency of the sensor
        # rng1 = date_range(self.sens_objects[sens_str1].date, periods = self.sens_objects[sens_str1].data.size, freq=_freq)
        # ts1 = Series(self.sens_objects[sens_str1].data, rng1)
        #
        # rng2 = date_range(self.sens_objects[sens_str2].date, periods = self.sens_objects[sens_str2].data.size, freq=_freq)
        # ts2 = Series(self.sens_objects[sens_str2].data, rng2)

        # resample the data and linearly interpolate the missing values
        # upsampled = ts.resample(_freq)
        # interp = upsampled.interpolate()
        if mode == "Hourly":
            data_obj = {}
            # data_obj["prd"]={'time':filt.datenum2(self.sens_objects["PRD"].get_time_vector()), 'station':'014', 'sealevel':self.sens_objects["PRD"].get_flat_data().copy()}
            # for key in self.sens_objects.keys():
            #     print("KEY", key)
            sl_data = self.sens_objects[sens_str1].get_flat_data().copy()
            sl_data = self.remove_9s(sl_data)
            sl_data = sl_data - int(self.sens_objects[sens_str1].height)
            data_obj[sens_str1.lower()] = {
                'time':
                filt.datenum2(self.sens_objects[sens_str1].get_time_vector()),
                'station':
                '014',
                'sealevel':
                sl_data
            }

            sl_data2 = self.sens_objects[sens_str2].get_flat_data().copy()
            sl_data2 = self.remove_9s(sl_data2)
            sl_data2 = sl_data2 - int(self.sens_objects[sens_str2].height)
            data_obj[sens_str2.lower()] = {
                'time':
                filt.datenum2(self.sens_objects[sens_str2].get_time_vector()),
                'station':
                '014',
                'sealevel':
                sl_data2
            }

            year = self.sens_objects[sens_str2].date.astype(object).year
            month = self.sens_objects[sens_str2].date.astype(object).month
            data_hr = filt.hr_process_2(
                data_obj, filt.datetime(year, month, 1, 0, 0, 0),
                filt.datetime(year, month + 1, 1, 0, 0, 0))

            if sens_str1 != "PRD":
                hr_resid = data_hr[sens_str1.lower()]["sealevel"] - data_hr[
                    sens_str2.lower()]["sealevel"]
                time = [
                    filt.matlab2datetime(tval[0])
                    for tval in data_hr[list(data_hr.keys())[0]]['time']
                ]
                self.generic_plot(self.ui.mplwidget_bottom.canvas,
                                  time,
                                  hr_resid,
                                  sens_str1,
                                  sens_str2,
                                  "Hourly Residual",
                                  is_interactive=False)
            else:
                self.show_custom_message(
                    "Warning",
                    "For hourly residual an actual channel needs to be selected in the top plot."
                )
                self.generic_plot(
                    self.ui.mplwidget_bottom.canvas, [0], [0],
                    sens_str1,
                    sens_str2,
                    "Choose a channel in the top plot other than PRD",
                    is_interactive=False)

        else:
            newd1 = self.resample2(sens_str1)
            newd2 = self.resample2(sens_str2)
            # newd1 = ts1.resample(_freq).interpolate()
            # newd2 = ts2.resample(_freq).interpolate()
            if (newd1.size > newd2.size):
                resid = newd2 - newd1[:newd2.size]
            else:
                resid = newd1 - newd2[:newd1.size]

            # time = np.array([self.sens_objects[sens_str1].date + np.timedelta64(i*int(1), 'm') for i in range(resid.size)])
            # time = np.arange(resid.size)
            time = date_range(self.sens_objects[sens_str1].date,
                              periods=resid.size,
                              freq='1min')
            self.generic_plot(self.ui.mplwidget_bottom.canvas,
                              time,
                              resid,
                              sens_str1,
                              sens_str2,
                              "Residual",
                              is_interactive=False)

    def generic_plot(self, canvas, x, y, sens1, sens2, title, is_interactive):
        print("GENERIC PLOT CALLED")
        # self._residual_ax = canvas.figure.subplots()

        line, = self._residual_ax.plot(x,
                                       y,
                                       '-',
                                       picker=5,
                                       lw=0.5,
                                       markersize=3)  # 5 points tolerance
        line.set_gid(sens2)
        self._residual_fig = canvas.figure
        self._residual_ax.set_title(title)
        line.set_label(title + ": " + sens1 + " - " + sens2)
        self._residual_ax.autoscale(enable=True, axis='both', tight=True)
        self._residual_ax.set_xlim([x[0], x[-1]])
        self._residual_ax.margins(0.05, 0.05)
        self._residual_ax.legend()

        if (is_interactive):
            self.browser = PointBrowser(x, y, self._residual_ax, line,
                                        self._residual_fig,
                                        self.find_outliers(x, y, sens1))
            self.browser.onDataEnd += self.show_message
            canvas.mpl_connect('pick_event', self.browser.onpick)
            canvas.mpl_connect('key_press_event', self.browser.onpress)
            ## need to activate focus onto the mpl canvas so that the keyboard can be used
            canvas.setFocusPolicy(QtCore.Qt.ClickFocus)
            canvas.setFocus()

        self._residual_ax.figure.tight_layout()
        self.toolbar2 = self._residual_fig.canvas.toolbar  # Get the toolbar handler
        self.toolbar2.update()  # Update the toolbar memory

        self._residual_ax.figure.canvas.draw()

    def _update_top_canvas(self, sens):
        data_flat = self.sens_objects[sens].get_flat_data()
        nines_ind = np.where(data_flat == 9999)
        # nonines_data = data_flat.copy()
        # nonines_data[nines_ind] = float('nan')
        # data_flat = nonines_data
        # data_flat =data_flat - np.nanmean(data_flat)
        if (len(nines_ind[0]) < data_flat.size):
            # data_flat[nines_ind] = float('nan')
            pass
        else:
            self.show_custom_message("Warning",
                                     "The following sensor has no data")
        self._static_ax.clear()
        # disconnect canvas pick and press events when a new sensor is selected
        # to eliminate multiple callbacks on sensor change
        # self.static_canvas.mpl_disconnect(self.pidP)
        # self.static_canvas.mpl_disconnect(self.cidP)
        self.ui.mplwidget_top.canvas.mpl_disconnect(self.pid)
        self.ui.mplwidget_top.canvas.mpl_disconnect(self.cid)
        if self.browser:
            self.browser.onDataEnd -= self.show_message
            self.browser.disconnect()
        # time = np.arange(data_flat.size)
        time = self.sens_objects[sens].get_time_vector()
        self.line, = self._static_ax.plot(time,
                                          data_flat,
                                          '-',
                                          picker=5,
                                          lw=0.5,
                                          markersize=3)

        self._static_ax.set_title(
            'select a point you would like to remove and press "D"')
        self.browser = PointBrowser(time, data_flat, self._static_ax,
                                    self.line, self._static_fig,
                                    self.find_outliers(time, data_flat, sens))
        self.browser.onDataEnd += self.show_message
        self.browser.on_sensor_change_update()
        # update event ids so that they can be disconnect on next sensor change
        self.pid = self.ui.mplwidget_top.canvas.mpl_connect(
            'pick_event', self.browser.onpick)
        self.cid = self.ui.mplwidget_top.canvas.mpl_connect(
            'key_press_event', self.browser.onpress)
        ## need to activate focus onto the mpl canvas so that the keyboard can be used
        self.toolbar1.update()
        self.ui.mplwidget_top.canvas.setFocusPolicy(QtCore.Qt.ClickFocus)
        self.ui.mplwidget_top.canvas.setFocus()

    def find_outliers(self, t, data, sens):

        channel_freq = self.sens_objects[sens].rate
        _freq = channel_freq + 'min'

        nines_ind = np.where(data == 9999)
        nonines_data = data.copy()
        nonines_data[nines_ind] = float('nan')
        # Get a date range to create pandas time Series
        # using the sampling frequency of the sensor
        rng = date_range(t[0], t[-1], freq=_freq)
        ts = Series(nonines_data, rng)

        # resample the data and linearly interpolate the missing values
        upsampled = ts.resample(_freq)
        interp = upsampled.interpolate()

        # calculate a window size for moving average routine so the window
        # size is always 60 minutes long
        window_size = 60 // int(channel_freq)

        # calculate moving average including the interolated data
        # moving_average removes big outliers before calculating moving average
        y_av = self.moving_average(np.asarray(interp.tolist()), window_size)
        # y_av = self.moving_average(data, 30)
        # missing=np.argwhere(np.isnan(y_av))
        # y_av[missing] = np.nanmean(y_av)

        # calculate the residual between the actual data and the moving average
        # and then find the data that lies outside of sigma*std
        residual = nonines_data - y_av
        std = np.nanstd(residual)
        sigma = 3.0

        itemindex = np.where((nonines_data > y_av + (sigma * std))
                             | (nonines_data < y_av - (sigma * std)))
        return itemindex

    def moving_average(self, data, window_size):
        """ Computes moving average using discrete linear convolution of two one dimensional sequences.
        Args:
        -----
                data (pandas.Series): independent variable
                window_size (int): rolling window size

        Returns:
        --------
                ndarray of linear convolution

        References:
        ------------
        [1] Wikipedia, "Convolution", http://en.wikipedia.org/wiki/Convolution.
        [2] API Reference: https://docs.scipy.org/doc/numpy/reference/generated/numpy.convolve.html

        """
        # REMOVE GLOBAL OUTLIERS FROM MOVING AVERAGE CALCULATION nk
        filtered_data = data.copy()
        # my_mad=np.nanmedian(np.abs(filtered_data-np.nanmedian(filtered_data)))
        # my_mean=np.nanmean(filtered_data)

        my_mean = np.nanmean(filtered_data)
        my_std = np.nanstd(filtered_data)

        # itemindex = np.where(((filtered_data>my_mean+4*my_mad )  | (filtered_data<my_mean-4*my_mad)))
        itemindex = np.where(((filtered_data > my_mean + 3 * my_std) |
                              (filtered_data < my_mean - 3 * my_std)))
        filtered_data[itemindex] = np.nanmean(filtered_data)
        # Fix boundary effects by adding prepending and appending values to the data
        filtered_data = np.insert(
            filtered_data, 0,
            np.ones(window_size) *
            np.nanmean(filtered_data[:window_size // 2]))
        filtered_data = np.insert(
            filtered_data, filtered_data.size,
            np.ones(window_size) *
            np.nanmean(filtered_data[-window_size // 2:]))
        window = np.ones(int(window_size)) / float(window_size)
        # return (np.convolve(filtered_data, window, 'same')[window_size:-window_size],itemindex)
        return np.convolve(filtered_data, window,
                           'same')[window_size:-window_size]

    def resample2(self, sens_str):
        data = self.sens_objects[sens_str].data.copy()
        nines_ind = np.where(data == 9999)
        data[nines_ind] = float('nan')
        ave = np.nanmean(data)
        datas = data[0:-1] - ave  # int(self.sens_objects[sens_str].height)
        datae = data[1:] - ave  # int(self.sens_objects[sens_str].height)
        yc = (datae - datas) / int(self.sens_objects[sens_str].rate)

        min_data = []
        for j in range(0, len(datas)):
            for i in range(0, int(self.sens_objects[sens_str].rate)):
                min_data.append(float(datas[j] + yc[j]))
        # nan_ind = np.argwhere(np.isnan(min_data))
        # min_data[nan_ind] = 9999
        return np.asarray(min_data)

    def show_message(self, *args):
        print("SHOW MESSAGE", *args)
        # choice = QtWidgets.QMessageBox.information(self, 'The end of data has been reached',  'The end of data has been reached', QtWidgets.QMessageBox.Ok)
        self.show_custom_message(*args, *args)

    def show_ref_dialog(self):
        try:
            self.browser
        except AttributeError:
            self.show_custom_message("Error!",
                                     "Data needs to be loaded first.")
            return
        else:
            if (self.is_digit(str(self.ui.refLevelEdit.text()))):
                # text, result = QtWidgets.QInputDialog.getText(self, 'My Input Dialog', 'Enter start date and time:')
                date, time, result = DateDialog.getDateTime(self)
                ISOstring = date.toString('yyyy-MM-dd') + 'T' + time.toString(
                    "HH:mm")
                if result:
                    REF_diff = int(str(self.ui.refLevelEdit.text())) - int(
                        self.sens_objects[self.sens_str].height)
                    new_REF = REF_diff + int(
                        self.sens_objects[self.sens_str].height)
                    # offset the data
                    self.browser.offset_data(ISOstring, REF_diff)
                    # format the new reference to a 4 character string (i.e add leading zeros if necessary)
                    # update the header
                    new_header = self.sens_objects[self.sens_str].header[0][:60] + '{:04d}'.format(new_REF) + \
                                 self.sens_objects[self.sens_str].header[0][64:]
                    self.sens_objects[self.sens_str].header[0] = new_header
                    self.ui.lineEdit.setText(
                        self.sens_objects[self.sens_str].header[0])
                    print("Succesfully changed to: ",
                          str(self.ui.refLevelEdit.text()))

            else:
                self.show_custom_message("Error!",
                                         "The value entered is not a number.")
                return

    def is_digit(self, n):
        try:
            int(n)
            return True
        except ValueError:
            return False

    def remove_9s(self, data):
        nines_ind = np.where(data == 9999)
        data[nines_ind] = float('nan')
        return data

    def show_custom_message(self, title, descrip):
        choice = QtWidgets.QMessageBox.information(self, title, descrip,
                                                   QtWidgets.QMessageBox.Ok)

    def save_to_ts_files(self):
        # Deleting tkey "ALL" from the list of sensors
        if "ALL" in self.sens_objects:
            del self.sens_objects["ALL"]
        if (self.sens_objects):
            months = len(
                self.sens_objects["PRD"].line_num)  # amount of months loaded
            # print("Amount of months loaded", months)
            assem_data = [
                [] for j in range(months)
            ]  # initial an empty list of lists with the number of months
            nan_ind = np.argwhere(np.isnan(self.browser.data))
            # print("NAN INDICES",nan_ind)
            # self.browser.data[nan_ind] = 9999
            # self.sens_objects[self.sens_str].data = self.browser.data
            # separate PRD from the rest because it has to be saved on the top file
            # Because dictionaries are unordered
            prd_list = [[] for j in range(months)]

            # Cycle through each month loaded
            for m in range(months):
                # Cycle through each month loaded, where key is the sensor name
                # Use value instead of self.sens_objects[key]?
                for key, value in self.sens_objects.items():
                    # Add header
                    # separate PRD from the rest because it has to be saved on the top file
                    if (key == "PRD"):
                        prd_list[m].append(
                            self.sens_objects[key].header[m].strip("\n"))
                    else:
                        assem_data[m].append(
                            self.sens_objects[key].header[m].strip("\n"))
                    # The ugly range is calculating start and end line numbers for each month that was Loaded
                    # so that the data can be saved to separate, monthly files
                    for i in range(
                            sum(self.sens_objects[key].line_num[:]) -
                            sum(self.sens_objects[key].line_num[m:]),
                            sum(self.sens_objects[key].line_num[:]) -
                            sum(self.sens_objects[key].line_num[m:]) +
                            self.sens_objects[key].line_num[m]):
                        # File formatting is differs based on the sampling rate of a sensor
                        if (int(self.sens_objects[key].rate) >= 5):
                            # Get only sealevel reading, without anything else (no time/date etc)
                            data = ''.join(
                                '{:5.0f}'.format(e)
                                for e in self.sens_objects[key].data.flatten()
                                [i * 12:12 + i * 12].tolist())
                            # The columns/rows containing only time/data and no sealevel measurements
                            it_col_formatted = '  ' + self.sens_objects[key].type + '  ' + \
                                               self.sens_objects[key].time_info[i][8:12].strip()[-2:] + \
                                               self.sens_objects[key].time_info[i][12:20]
                            # assem_data.append(info_time_col[i][0:]+data)
                            if (key == "PRD"):
                                prd_list[m].append(''.join(it_col_formatted) +
                                                   data)
                            else:
                                assem_data[m].append(
                                    ''.join(it_col_formatted) + data)
                        else:
                            data = ''.join(
                                '{:4.0f}'.format(e)
                                for e in self.sens_objects[key].data.flatten()
                                [i * 15:15 + i * 15].tolist())
                            it_col_formatted = '  ' + self.sens_objects[key].type + '  ' + \
                                               self.sens_objects[key].time_info[i][8:12].strip()[-2:] + \
                                               self.sens_objects[key].time_info[i][12:20]
                            # assem_data.append(info_time_col[i][0:]+data)
                            assem_data[m].append(''.join(it_col_formatted) +
                                                 data)
                    if (key == "PRD"):
                        prd_list[m].append('9' * 80)
                    else:
                        assem_data[m].append('9' * 80)
                del data
                # find the start date lines of each monp file that was loaded
                date_str = self.sens_objects[key].time_info[
                    sum(self.sens_objects[key].line_num[:]) -
                    sum(self.sens_objects[key].line_num[m:])]
                month_int = int(date_str[12:14][-2:])
                month_str = "{:02}".format(month_int)
                year_str = date_str[8:12][-2:]
                station_num = self.sens_objects[key].type[0:-3]
                file_name = 't' + station_num + year_str + month_str
                file_extension = '.dat'
                try:
                    with open(
                            st.get_path(st.SAVE_KEY) + '/' + file_name +
                            file_extension, 'w') as the_file:
                        for lin in prd_list[m]:
                            the_file.write(lin + "\n")
                        for line in assem_data[m]:
                            the_file.write(line + "\n")
                        # Each file ends with two lines of 80 9s that's why adding an additional one
                        the_file.write('9' * 80 + "\n")
                    self.show_custom_message(
                        "Success", "Success \n" + file_name + file_extension +
                        " Saved to " + st.get_path(st.SAVE_KEY) + "\n")
                except IOError as e:
                    self.show_custom_message(
                        "Error", "Cannot Save to " + st.get_path(st.SAVE_KEY) +
                        "\n" + str(e) +
                        "\n Please select a different path to save to")
                self.save_fast_delivery(self.sens_objects)
                self.save_mat_high_fq(file_name)
            # if result == True:
            #     print("Succesfully changed to: ", str(self.refLevelEdit.text()))
        else:
            self.show_custom_message("Warning", "You haven't loaded any data.")

    # this function is called for every month of data loaded
    def save_mat_high_fq(self, file_name):
        import scipy.io as sio
        if st.get_path(st.HF_PATH):
            save_path = st.get_path(st.HF_PATH)
        else:
            self.show_custom_message(
                "Warning",
                "Please select a location where you would like your high "
                "frequency matlab data "
                "to be saved. Click save again once selected.")
            return

        for key, value in self.sens_objects.items():
            sl_data = self.sens_objects[key].get_flat_data().copy()
            sl_data = self.remove_9s(sl_data)
            sl_data = sl_data - int(self.sens_objects[key].height)
            time = filt.datenum2(self.sens_objects[key].get_time_vector())
            data_obj = [time, sl_data]
            # transposing the data so that it matches the shape of the UHSLC matlab format
            matlab_obj = {
                'NNNN': file_name + key.lower(),
                file_name + key.lower(): np.transpose(data_obj, (1, 0))
            }
            try:
                sio.savemat(save_path + '/' + file_name + key.lower() + '.mat',
                            matlab_obj)
                self.show_custom_message(
                    "Success", "Success \n" + file_name + key.lower() +
                    '.mat' + " Saved to " + st.get_path(st.HF_PATH) + "\n")
            except IOError as e:
                self.show_custom_message(
                    "Error", "Cannot Save to high frequency (.mat) data to" +
                    st.get_path(st.HF_PATH) + "\n" + str(e) +
                    "\n Please select a different path to save to")

    def save_fast_delivery(self, _data):
        import scipy.io as sio
        # 1. Check if the .din file was added and that it still exist at that path
        #       b) also check that a save folder is set up
        # 2. If it does. load in the primary channel for our station
        # 3. If it does not exist, display a warning message on how to add it and that the FD data won't be saved
        # 4. Perform filtering and save
        din_path = None
        save_path = None
        if st.get_path(st.DIN_PATH):
            din_path = st.get_path(st.DIN_PATH)
        else:
            self.show_custom_message(
                "Warning",
                "The fast delivery data cannot be processed because you haven't selected"
                "the .din file location. Press F1 to access the menu to select it. And "
                "then click the save button again.")
            return

        if st.get_path(st.FD_PATH):
            save_path = st.get_path(st.FD_PATH)
        else:
            self.show_custom_message(
                "Warning",
                "Please select a location where you would like your hourly and daily data"
                "to be saved. Click save again once selected.")
            return

        data_obj = {}

        station_num = _data["PRD"].type[0:-3]
        primary_sensor = filt.get_channel_priority(
            din_path, station_num)[0].upper(
            )  # returns multiple sensor in order of importance
        if primary_sensor not in _data:
            self.show_custom_message(
                "Error", f"Your .din file says that {primary_sensor} "
                f"is the primary sensor but the file you have loaded does "
                f"not contain that sensor. Hourly and daily data will not be saved."
            )
            return
        sl_data = _data[primary_sensor].get_flat_data().copy()
        sl_data = self.remove_9s(sl_data)
        sl_data = sl_data - int(_data[primary_sensor].height)
        data_obj[primary_sensor.lower()] = {
            'time': filt.datenum2(_data[primary_sensor].get_time_vector()),
            'station': station_num,
            'sealevel': sl_data
        }

        year = _data[primary_sensor].date.astype(object).year
        month = _data[primary_sensor].date.astype(object).month

        #  Filter to hourly
        data_hr = filt.hr_process_2(data_obj,
                                    filt.datetime(year, month, 1, 0, 0, 0),
                                    filt.datetime(year, month + 1, 1, 0, 0, 0))

        # for channel parameters see filt.channel_merge function
        # We are not actually merging channels here (not needed for fast delivery)
        # But we still need to run the data through the merge function, even though we are only using one channel
        # in order to get the correct output data format suitable for the daily filter
        ch_params = [{primary_sensor.lower(): 0}]
        hourly_merged = filt.channel_merge(data_hr, ch_params)

        # Note that hourly merged returns a channel attribute which is an array of integers representing channel type.
        # used for a particular day of data. In Fast delivery, all the number should be the same because no merge
        # int -> channel name mapping is inside of filtering.py var_flag function
        data_day = filt.day_119filt(hourly_merged,
                                    _data[primary_sensor].location[0])

        month_str = "{:02}".format(month)
        hourly_filename = save_path + '/' + 'th' + str(station_num) + str(
            year)[-2:] + month_str + '.mat'
        daily_filename = save_path + '/' + 'da' + str(station_num) + str(
            year)[-2:] + month_str + '.mat'
        sio.savemat(hourly_filename, data_hr)
        sio.savemat(daily_filename, data_day)
        self.show_custom_message(
            "Success", "Success \n Hourly and Daily Date Saved to " +
            st.get_path(st.FD_PATH) + "\n")

        monthly_mean = np.round(np.nanmean(data_day['sealevel'])).astype(int)
        # Remove nans, replace with 9999 to match the legacy files
        nan_ind = np.argwhere(np.isnan(data_day['sealevel']))
        data_day['sealevel'][nan_ind] = 9999
        sl_round_up = np.round(data_day['sealevel']).astype(
            int)  # round up sealevel data and convert to int

        # right justify with 5 spaces
        sl_str = [str(x).rjust(5, ' ')
                  for x in sl_round_up]  # convert data to string

        daily_filename = save_path + '/' + 'da' + str(station_num) + str(
            year)[-2:] + month_str + '.dat'

        # format the date and name strings to match the legacy .dat format
        month_str = str(month).rjust(2, ' ')
        station_name = _data[primary_sensor].name.ljust(7)
        line_begin_str = f'{station_name}WOC {year}{month_str}'
        counter = 1
        try:
            with open(daily_filename, 'w') as the_file:
                for i, sl in enumerate(sl_str):
                    if i % 11 == 0:
                        line_str = line_begin_str + str(
                            counter) + " " + ''.join(sl_str[i:i + 11])
                        if counter == 3:
                            line_str = line_str.ljust(75)
                            final_str = line_str[:-5] + str(monthly_mean)
                            line_str = final_str
                        the_file.write(line_str + "\n")
                        counter += 1
        except IOError as e:
            self.show_custom_message(
                "Error", "Cannot Save to " + daily_filename + "\n" + str(e) +
                "\n Please select a different path to save to")
Пример #6
0
class Variable(QtCore.QObject):
    '''
    Class that holds a value, using change() emits a signal.

    There is no mandatory naming for instances of this Class, however ARTview
    components name variable according to the following table, using it is
    recommended:

    .. _shared_variable:
    .. table:: Shared Variables Table

        +-----------+-------------------+------------------------------------+
        | Name      | Function          | Valid values                       |
        +===========+===================+====================================+
        | Vradar    | Hold radar open   | :py:class:`pyart.core.Radar`       |
        |           | with pyart        | instance                           |
        +-----------+-------------------+------------------------------------+
        | Vgrid     | Hold grid open or | :py:class:`pyart.core.Grid`        |
        |           | generated with    | instance                           |
        |           | pyart             |                                    |
        +-----------+-------------------+------------------------------------+
        | Vcontainer| Alias to Vradar or| Radar, Grid or None                |
        |           | Vgrid             |                                    |
        +-----------+-------------------+------------------------------------+
        | Vfield    | Name of a Field   | string, preferentially in          |
        |           | in radar file     | radar.fields.keys(), but there is  |
        |           |                   | no guarranty                       |
        +-----------+-------------------+------------------------------------+
        | Vtilt     | Tilt (sweep) of   | int between 0 and                  |
        |           | a radar file      | (number of sweeps) - 1             |
        +-----------+-------------------+------------------------------------+
        | VlevelZ   | Vertical level of | int between 0 and                  |
        |           | a grid file       | nz - 1                             |
        +-----------+-------------------+------------------------------------+
        | VlevelY   | Latitudinal level | int between 0 and                  |
        |           | of a grid file    | ny - 1                             |
        +-----------+-------------------+------------------------------------+
        | VlevelX   | Longitudinal level| int between 0 and                  |
        |           | of a grid file    | nx - 1                             |
        +-----------+-------------------+------------------------------------+
        | Vlevel    | Alias to Vtilt,   | positive integer                   |
        |           | VlevelZ, VlevelY  |                                    |
        |           | or VlevelX        |                                    |
        +-----------+-------------------+------------------------------------+
        | Vlimits   | Limits of display | dict containing keys: 'xmin',      |
        |           |                   | 'xmax', 'ymin', 'ymax' and holding |
        |           |                   | float values                       |
        +-----------+-------------------+------------------------------------+
        | Vcolormap | Colormap          | dict containing keys: 'vmin' and   |
        |           |                   | 'vmax' holding float values and key|
        |           |                   | 'cmap' holding colormap string name|
        +-----------+-------------------+------------------------------------+
        |Vgatefilter| Hold pyart        |:py:class:`pyart.filters.GateFilter`|
        |           | GateFilter        |instance or None                    |
        +-----------+-------------------+------------------------------------+
        |VplotAxes  | Hold axes of      |:py:class:`matplotlib.axes.Axes`    |
        |           | Matplotlib figure |instance or None                    |
        +-----------+-------------------+------------------------------------+
        |VPathInte\ | Hold auxillary    |function like :py:func:`~artview.\  |
        |riorFunc   | function          |components.RadarDisplay.\           |
        |           |                   |getPathInteriorValues` or None      |
        +-----------+-------------------+------------------------------------+
        |Vfilelist  | Hold filenames in |list containing paths to files      |
        |           | current working   |(strings)                           |
        |           | directory         |                                    |
        +-----------+-------------------+------------------------------------+
        |VRadarCol\ | Cache radars      |list of :py:class:`pyart.core.Radar`|
        |lection    |                   |instances                           |
        |           |                   |                                    |
        +-----------+-------------------+------------------------------------+
        |VpyartDis\ | Hold pyart display| instance of one of the classes     |
        |play       | instances         | in :py:mod:`pyart.graph`           |
        |           |                   |                                    |
        +-----------+-------------------+------------------------------------+
    '''

    value = None  #: Value of the Variable
    valueChanged = QtCore.pyqtSignal(QtCore.QObject, bool, name='ValueChanged')

    def __init__(self, value=None):
        QtCore.QObject.__init__(self)
        self.value = value

    def change(self, value, strong=True):
        '''
        Change the Variable value and emit 'ValueChanged' signal.

        Parameters
        ----------
        value :
            New Value to be assigned to the variable.
        [Optional]
        strong : bool, optional
            Define if this is a strong or weak change. This is a somewhat
            subjective decision: strong is default.

            A weak change should be used to indicate to the slot that the
            change should not trigger any costly computation.
            Reasons for this are: When initializing a variable, it is likely
            to change again shortly, or another more important variable is
            being changed as well etc.

            .. note::
                Defining how to respond to strong/weak changes is the
                responsibility of the slot, most can just ignore the
                difference, but the costly ones should be aware.

        Notes
        -----
        The arguments of the emitted signal are (self, value, strong).
        '''
        self.value = value
        self.valueChanged.emit(self, strong)

    def update(self, strong=True):
        '''
        Emits the 'ValueChanged' signal without changings value. This is
        useful when value is changed in place.

        Parameters
        ----------
        strong : bool, optional
            Define if this is a strong or weak change.
        '''
        self.valueChanged.emit(self, strong)