コード例 #1
0
class CandClassifier(QWidget):

    def __init__(self, directory, output, extension):

        super().__init__()

        self._directory = directory
        self._output_file_name = output
        self._cand_plots = sorted(glob(path.join(directory, "*" + extension)))
        self._total_cands = len(self._cand_plots)

        self._cands_params = [self._splitter(cand) for cand in self._cand_plots]
        self._current_cand = 0
        self._rfi_data = []
        self._known_data = []
        self._cand_data = []
        self._auto_enabled = False
        self._auto_speed_value = 2

        self._stats_window = StatsWindow()
        self._stats_window.update_dist_plot([cand["dm"] for cand in self._cands_params])
        self._stats_window.apply_limits_button.clicked.connect(self._get_limits)
        self._stats_window.limits_choice.currentTextChanged.connect(self._change_source)

        self._help_window = HelpWindow()
        self._examples_window = ExamplesWindow()

        main_box = QVBoxLayout()
        main_box.setContentsMargins(10, 0, 10, 0)
        main_box.setSpacing(0)

        # Because adding QPixmap to layout directly is not a thing
        self._plot_label = QLabel()
        main_box.addWidget(self._plot_label)

        stats_box = QHBoxLayout()
        stats_box.setContentsMargins(0, 0, 0, 0)

        self._rfi_count_label = QLabel("RFI: 0")
        self._rfi_count_label.setStyleSheet("font-weight: bold;\
                                        font-size: 20px")

        self._known_count_label = QLabel("Known: 0")
        self._known_count_label.setStyleSheet("font-weight: bold;\
                                        font-size: 20px")

        self._cand_count_label = QLabel("Candidates: 0")
        self._cand_count_label.setStyleSheet("font-weight: bold;\
                                            font-size: 20px")
        
        stats_box.addWidget(self._rfi_count_label)
        stats_box.addWidget(self._known_count_label)
        stats_box.addWidget(self._cand_count_label)
        main_box.addLayout(stats_box)

        current_box = QHBoxLayout()

        self._current_cand_select = QLineEdit()
        self._current_cand_select.setFixedSize(100, 25)
        self._current_cand_select.setText(str(1))
        self._current_cand_select.setStyleSheet("font-weight: bold;\
                                        font-size: 20px")
        self._current_cand_select.returnPressed.connect(self._set_cand)
        current_box.addWidget(self._current_cand_select)
        self._cand_label = QLabel()
        self._cand_label.setStyleSheet("font-weight: bold;\
                                        font-size: 20px")
        self._cand_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
        current_box.addWidget(self._cand_label)
        main_box.addLayout(current_box)

        buttons_box = QHBoxLayout()
        cand_box = QHBoxLayout()
        self._rfi_button = QPushButton()
        self._rfi_button.setText("RFI")
        self._rfi_button.clicked.connect(self._rfi_press)
        self._rfi_button.setFixedSize(150, 50)
        self._rfi_button.setStyleSheet("background-color: red;\
                                        font-weight: bold;\
                                        font-variant: small-caps;\
                                        font-size: 25px")
        cand_box.addWidget(self._rfi_button)

        self._known_button = QPushButton()
        self._known_button.setText("Known")
        self._known_button.clicked.connect(self._known_press)
        self._known_button.setFixedSize(150, 50)
        self._known_button.setStyleSheet("background-color: orange;\
                                        font-weight: bold;\
                                        font-variant: small-caps;\
                                        font-size: 25px")
        cand_box.addWidget(self._known_button)

        self._cand_button = QPushButton()
        self._cand_button.setText("Candidate")
        self._cand_button.clicked.connect(self._cand_press)
        self._cand_button.setFixedSize(150, 50)
        self._cand_button.setStyleSheet("background-color: green;\
                                        font-weight: bold;\
                                        font-variant: small-caps;\
                                        font-size: 25px")
        cand_box.addWidget(self._cand_button)
        cand_box.setContentsMargins(0, 0, 30, 0)

        view_box = QVBoxLayout()

        nav_box = QHBoxLayout()
        self._skip_start_button = QPushButton()
        self._skip_start_button.setText("|<")
        self._skip_start_button.setFixedWidth(25)
        self._skip_start_button.clicked.connect(self._skip_start_press)
        nav_box.addWidget(self._skip_start_button)
        self._prev_skip_button = QPushButton()
        self._prev_skip_button.setText("<<")
        self._prev_skip_button.setFixedWidth(40)
        self._prev_skip_button.clicked.connect(self._previous_skip_press)
        nav_box.addWidget(self._prev_skip_button)
        self._prev_button = QPushButton()
        self._prev_button.setText("<")
        self._prev_button.clicked.connect(self._previous_press)
        nav_box.addWidget(self._prev_button)
        self._next_button = QPushButton()
        self._next_button.setText(">")
        self._next_button.clicked.connect(self._next_press)
        nav_box.addWidget(self._next_button)
        self._next_skip_button = QPushButton()
        self._next_skip_button.setText(">>")
        self._next_skip_button.setFixedWidth(40)
        self._next_skip_button.clicked.connect(self._next_skip_press)
        nav_box.addWidget(self._next_skip_button)
        self._skip_end_button = QPushButton()
        self._skip_end_button.setText(">|")
        self._skip_end_button.setFixedWidth(25)
        self._skip_end_button.clicked.connect(self._skip_end_press)
        nav_box.addWidget(self._skip_end_button)
        nav_box.setContentsMargins(0, 0, 30, 0)
        view_box.addLayout(nav_box)
        
        auto_box = QHBoxLayout()
        self._auto_timer = QTimer()
        self._auto_timer.timeout.connect(self._next_press)
        self._auto_label = QLabel("Enable auto view")
        auto_box.addWidget(self._auto_label)
        self._auto_enable = QCheckBox()
        self._auto_enable.stateChanged.connect(self._enable_auto)
        auto_box.addWidget(self._auto_enable)
        self._auto_speed = QSpinBox()
        self._auto_speed.setMinimum(1)
        self._auto_speed.setMaximum(10)
        self._auto_speed.setValue(self._auto_speed_value)
        self._auto_speed.valueChanged.connect(self._change_auto_speed)
        auto_box.addWidget(self._auto_speed)
        self._speed_label = QLabel(" cands per second")
        auto_box.addWidget(self._speed_label)
        auto_box.setContentsMargins(0, 0, 30, 0)
        view_box.addLayout(auto_box)

        buttons_box.addLayout(cand_box)
        buttons_box.addLayout(view_box)

        extra_buttons = QVBoxLayout()
        self._stats_button = QPushButton()
        self._stats_button.clicked.connect(self._open_stats)
        self._stats_button.setText("Open Statistics")
        extra_buttons.addWidget(self._stats_button)
        self._examples_button = QPushButton()
        self._examples_button.clicked.connect(self._open_examples)
        self._examples_button.setText("Examples")
        extra_buttons.addWidget(self._examples_button)
        self._help_button = QPushButton()
        self._help_button.clicked.connect(self._open_help)
        self._help_button.setText("Help")
        extra_buttons.addWidget(self._help_button)
        buttons_box.addLayout(extra_buttons)
        main_box.addLayout(buttons_box)

        self.setLayout(main_box)
        self.setGeometry(150, 150, 1024, 768)
        self.setWindowTitle("MeerTRAP candidate classifier")
        self.show()

        if len(self._cand_plots) > 0:
            self._show_cand()
        else:
            self._cand_label.setText("No candidates to view")
    
    """
        if isfile(path.join(self._directory, self._output_file_name)):
            done = 0
            with open(path.join(self._directory, self._output_file_name)) as df:
                done = sum(1 for line in df)

            if done > 0:
                self._resume_dialog(done, self._output_file_name)

    def _resume_dialog(self, done, file_name):

        done_box = QMessageBox()
        done_box.setIcon(QMessageBox.Information)
        done_box.setText(f"File {file_name} already exists with"
                         + f"{done} candidates.\n"
                         + "Would you like to load it?")
        done_box.setStandardButtons(QMessageBox.Yes | QMessageBox.No)

        done_value = done_box.exec()
        
        if done_value == QMessageBox.Yes:
            self._reload_csv()
            self._show_cand(min(done, self._total_cands - 1))

    def _reload_csv():

        with open(path.join(self._directory, self._output_file_name)) as df:
            done_reader = reader(df, delimiter=",")

            for done in done_reader:
                filename = [0]
                done = [1]
    
    """

    def _splitter(self, cand):

        """
        
        Extract DM and MJD from the candidate plot file name.

        Check for different naming conventions we currently use and
        take them into account when getting that information

        Parameters:

            cand: str
                Candidate plot name

        Returns:

            cand_dict: dict
                Dictionary with correctly extracted MJD and DM values
        
        """

        cand = basename(cand)

        split_cand = cand.split("_")
        cand_dict = {}
        mjd_off = cand.startswith("mjd_")
        cand_dict["mjd"] = float(split_cand[0 + mjd_off])
        cand_dict["dm"] = float(split_cand[2 + mjd_off])

        return cand_dict

    def _enable_auto(self, state=None):

        self._auto_enabled = not self._auto_enabled
        if self._auto_enabled:
            self._auto_speed_value = self._auto_speed.value()
            self._auto_timer.start(1000 / self._auto_speed_value)
        else:
            self._auto_timer.stop()

    def _change_auto_speed(self, state):
        self._auto_speed_value = state

    def _change_source(self, source):
        self._stats_window.update_dist_plot([cand[source.lower()] for cand in self._cands_params], source == "MJD")

    def _get_limits(self):

        limit_type = self._stats_window.limits_choice.currentText()
        lower_limit = float(self._stats_window.start_limit.text())
        upper_limit = float(self._stats_window.end_limit.text())

        idx = 0
        if limit_type == "DM":
            idx = 2

        remaining_plots = self._cand_plots[self._current_cand:]
        passed_remaining_plots = [cand for cand in remaining_plots if not ((float(basename(cand).split("_")[idx + cand.startswith("mjd_")]) >= lower_limit) and (float(basename(cand).split("_")[idx + cand.startswith("mjd_")]) < upper_limit))]

        removed = len(remaining_plots) - len(passed_remaining_plots)
        self._stats_window.remove_label.setText(f"Removed {removed} candidates")

        del self._cand_plots[self._current_cand:]

        self._cand_plots.extend(passed_remaining_plots)
        self._total_cands = len(self._cand_plots)
        #self._cands_params = [{"mjd": float(basename(cand).split("_")[0]), "dm": float(basename(cand).split("_")[2])} for cand in self._cand_plots]
        self._cands_params = [self._splitter(cand) for cand in self._cand_plots]

        self._stats_window.update_dist_plot([cand[limit_type.lower()] for cand in self._cands_params], limit_type == "MJD")
        self._show_cand(self._current_cand)

    def _set_cand(self):

        self._plot_label.setFocus()

        state = self._current_cand_select.text()

        if not state:
            self._show_cand(0)
        else:
            self._show_cand(int(state) - 1)

    def _show_cand(self, idx = 0):

        if (idx == 0):

            cand_map = QPixmap(self._cand_plots[idx])
            img_width = cand_map.width()
            img_height = cand_map.height()

            window_width = max(img_width, 1024)
            window_height = max(img_height + 150, 620)
            self.setFixedSize(QSize(window_width, window_height))

        if (idx < self._total_cands) and (idx >= 0):
            cand_map = QPixmap(self._cand_plots[idx])
            self._plot_label.setPixmap(cand_map)
            self._current_cand = idx
            self._current_cand_select.setText(str(self._current_cand + 1))
            self._cand_label.setText(f" out of {self._total_cands}:"
                                    + f" {basename(self._cand_plots[idx])}")

    def _open_stats(self):

        if not self._stats_window.isVisible():
            self._stats_window.show()
            self._stats_button.setText("Close Statistics")
        else:
            self._stats_window.hide()
            self._stats_button.setText("Open Statistics")

    def _open_help(self):

        if not self._help_window.isVisible():
            self._help_window.show()
        else:
            self._help_window.hide()

    def _open_examples(self):

        if not self._examples_window.isVisible():
            self._examples_window.show()
        else:
            self._examples_window.hide()

    def keyPressEvent(self, event):



        route = {
            Qt.Key_A: self._rfi_press,
            Qt.Key_S: self._known_press,
            Qt.Key_D: self._cand_press,
            Qt.Key_Z: self._previous_press,
            Qt.Key_X: self._next_press,
            Qt.Key_V: self._auto_enable.nextCheckState,
            Qt.Key_PageDown: self._previous_skip_press,
            Qt.Key_PageUp: self._next_skip_press,
            Qt.Key_Home: self._skip_start_press,
            Qt.Key_End: self._skip_end_press
        }

        pressed = event.key()
        function = route.get(pressed)
        if function:
            if pressed != Qt.Key_V:
                if self._auto_enabled:
                    self._auto_enable.nextCheckState()
                return function(event)
            else:
                return function()

    def _update_list(self, idx, class_type):

        cand_name = basename(self._cand_plots[idx])
        split_cand = cand_name.split("_")
        cand_dm = float(split_cand[2 + cand_name.startswith("mjd_")])
        
        cand = (idx, cand_dm)

        if class_type == "rfi":
            if cand not in self._rfi_data:
                self._rfi_data.append(cand)
                if cand in self._cand_data:
                    self._cand_data.remove(cand)
                    self._replace_csv(cand_name, 0)
                elif cand in self._known_data:
                    self._known_data.remove(cand)
                    self._replace_csv(cand_name, 0)
                else:
                    self._add_csv(cand_name, 0)
        
        elif class_type == "known":
            if cand not in self._known_data:
                self._known_data.append(cand)
                if cand in self._rfi_data:
                    self._rfi_data.remove(cand)
                    self._replace_csv(cand_name, 2)
                elif cand in self._cand_data:
                    self._cand_data.remove(cand)
                    self._replace_csv(cand_name, 2)
                else:
                    self._add_csv(cand_name, 2)

        elif class_type == "cand":
            if cand not in self._cand_data:
                self._cand_data.append(cand)
                if cand in self._rfi_data:
                    self._rfi_data.remove(cand)
                    self._replace_csv(cand_name, 1)
                elif cand in self._known_data:
                    self._known_data.remove(cand)
                    self._replace_csv(cand_name, 1)
                else:
                    self._add_csv(cand_name, 1)

        self._rfi_count_label.setText(f"RFI: {len(self._rfi_data)}")
        self._known_count_label.setText(f"Known: {len(self._known_data)}")
        self._cand_count_label.setText(f"Candidates: {len(self._cand_data)}")

        self._stats_window._update(self._rfi_data, self._cand_data)

    def _replace_csv(self, cand_name, new_label):

        with open(path.join(self._directory, self._output_file_name),
                            "r", buffering=1) as of,\
             open(path.join(self._directory, self._output_file_name + ".tmp"),
                            "a", buffering=1) as nf:
            
            old_csv = reader(of, delimiter=",")
            new_csv = writer(nf, delimiter=",")

            for cand in old_csv:
                if cand[0] != cand_name:
                    new_csv.writerow([cand[0], cand[1]])
                else:
                    new_csv.writerow([cand[0], new_label])

        move(path.join(self._directory, self._output_file_name + ".tmp"),
             path.join(self._directory, self._output_file_name))

    def _add_csv(self, cand_name, label):

        with open(path.join(self._directory, self._output_file_name),
                            "a", buffering=1) as cf:
            cand_csv = writer(cf, delimiter=",")
            cand_csv.writerow([cand_name, label])

    def _rfi_press(self, event):
        self._update_list(self._current_cand, "rfi")
        self._show_cand(self._current_cand + 1)

    def _known_press(self, event):
        self._update_list(self._current_cand, "known")
        self._show_cand(self._current_cand + 1)

    def _cand_press(self, event):
        self._update_list(self._current_cand, "cand")
        self._show_cand(self._current_cand + 1)

    def _next_press(self, event=None):
        self._show_cand(self._current_cand + 1)

    def _previous_press(self, event):
        self._show_cand(self._current_cand - 1)

    def _previous_skip_press(self, event):
         idx = max(self._current_cand - 5, 0)
         self._show_cand(idx)

    def _next_skip_press(self, event):
        idx = min(self._current_cand + 5, self._total_cands - 1)
        self._show_cand(idx)

    def _skip_start_press(self, event):
        self._show_cand(0)

    def _skip_end_press(self, event):
        self._show_cand(self._total_cands - 1)