Ejemplo n.º 1
0
class PlottrMain(QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.setWindowTitle(getAppTitle())
        self.setWindowIcon(QIcon('./plottr_icon.png'))
        self.activateWindow()

        # layout of basic widgets
        self.logger = Logger()
        self.frame = QFrame()
        layout = QVBoxLayout(self.frame)
        layout.addWidget(self.logger)

        # self.setLayout(layout)
        self.setCentralWidget(self.frame)
        self.frame.setFocus()

        # basic setup of the data handling
        self.dataHandlers = {}

        # setting up the Listening thread
        self.listeningThread = QThread()
        self.listener = DataReceiver()
        self.listener.moveToThread(self.listeningThread)

        # communication with the ZMQ thread
        self.listeningThread.started.connect(self.listener.loop)
        self.listener.sendInfo.connect(self.logger.addMessage)
        self.listener.sendData.connect(self.processData)

        # go!
        self.listeningThread.start()

    @pyqtSlot(dict)
    def processData(self, data):
        dataId = data['id']
        if dataId not in self.dataHandlers:
            self.dataHandlers[dataId] = DataWindow(dataId=dataId)
            self.dataHandlers[dataId].show()
            self.logger.addMessage(f'Started new data window for {dataId}')
            self.dataHandlers[dataId].windowClosed.connect(
                self.dataWindowClosed)

        w = self.dataHandlers[dataId]
        w.addData(data)

    def closeEvent(self, event):
        self.listener.running = False
        self.listeningThread.quit()

        hs = [h for d, h in self.dataHandlers.items()]
        for h in hs:
            h.close()

    @pyqtSlot(str)
    def dataWindowClosed(self, dataId):
        self.dataHandlers[dataId].close()
        del self.dataHandlers[dataId]
Ejemplo n.º 2
0
class App(QWidget):
    """ GUI """
    def __init__(self):
        super().__init__()
        self.title = 'Lung areas scoring'
        self.left = 50
        self.top = 50
        self.top_row_height = 600
        self.bottom_row_height = 250
        self.left_column_width = 250
        self.right_column_width = 800
        self.width = self.left_column_width + self.right_column_width
        self.height = self.top_row_height + self.bottom_row_height
        self.video_label_width = 600  # height computed automatically to preserve aspect ratio

        self.tmp_dir = os.getcwd() + "/tmp"
        self.lungs_template_page_name = "image_webView.html"
        self.lungs_customized_page_name = "image_customized.html"
        self.lungs_template_page = os.getcwd(
        ) + "/resources/" + self.lungs_template_page_name
        self.lungs_customized_page = os.getcwd(
        ) + "/resources/" + self.lungs_customized_page_name
        # Mac
        self.ffmpeg_bin = os.getcwd() + "/bin/ffmpeg"
        # Windows
        #self.ffmpeg_bin = os.getcwd()+"/bin/ffmpeg.exe"

        self.init_ui()

        self.threadpool = QThreadPool()
        self._dialogs = []
        self.html = ""
        self.task = ""

        self.create_directory(self.tmp_dir)
        # used when user wants to reset everything. Needed to choose the image file page to load
        self.startNewSession = True
        self.clickedAreaName = ""

    def init_ui(self):
        """
        Initialize the window and add content
        """
        self.setFixedSize(self.width, self.height)
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)

        # Set background color for Mac compatibility
        p = self.palette()
        p.setColor(self.backgroundRole(), Qt.white)
        self.setPalette(p)

        # Style
        white = "#fff"
        yellow = "#ff0"
        orange = "#fa0"
        red = "#f00"
        grey = "#7c7c7c"
        #button_style          = "background-color: #D7D7D7; border-style: solid; border-width: 2px; border-color: black; border-radius: 5px; font: bold; font-size: 17px"
        # Mac compatibility
        button_style = "color: black; background-color: #D7D7D7; border-style: solid; border-width: 2px; border-color: black; border-radius: 5px; font: bold; font-size: 17px"
        #text_style            = "font-size: 17px; color: black"
        # Mac compatibility
        text_style = "font-size: 17px; color: black; background-color: rgb(255, 255, 255);"
        text_bold_style = "font-size: 17px; color: black; font-weight: bold"
        text_totals_style = "font-size: 17px; color: black; font-weight: bold; margin-left: 10px"
        header_style = "font-size: 17px; color: black; font-weight: bold"
        clickable_label_style = "font-size: 17px; color: black; background-color:#ffffff; padding-left: 20px"
        button_height = 40

        self.int_to_color_map = {
            0: "white",
            1: "yellow",
            2: "orange",
            3: "red",
            4: "grey"
        }

        layout = QGridLayout()

        panel_top_left = QVBoxLayout()

        self.reset_btn = QPushButton(text="RESTART")
        self.reset_btn.setFixedHeight(button_height)
        self.reset_btn.setStyleSheet(button_style)
        self.reset_btn.clicked.connect(self.reset_session)

        panel_top_left.addWidget(self.reset_btn)

        registry = QVBoxLayout()

        name_label = QLabel("Name")
        name_label.setStyleSheet(text_style)
        registry.addWidget(name_label)
        self.name = QLineEdit()
        self.name.setStyleSheet(text_style)
        registry.addWidget(self.name)

        surname_label = QLabel("Last name")
        surname_label.setStyleSheet(text_style)
        registry.addWidget(surname_label)
        self.surname = QLineEdit()
        self.surname.setStyleSheet(text_style)
        registry.addWidget(self.surname)

        dob_label = QLabel("Date of birth")
        dob_label.setStyleSheet(text_style)
        registry.addWidget(dob_label)
        dob_grid = QGridLayout()
        dob_btn = QPushButton(icon=QIcon("resources/calendar_icon.png"))
        dob_btn.clicked.connect(self.set_date_of_birth)
        dob_btn.setFixedWidth(button_height)
        dob_btn.setFixedHeight(button_height)
        dob_btn.setIconSize(QSize(button_height, button_height))
        dob_btn.setStyleSheet("border:none")
        dob_grid.addWidget(dob_btn, 0, 0)
        self.date_of_birth = ClickableQLabel(self)
        self.date_of_birth.clicked.connect(self.set_date_of_birth)
        self.date_of_birth.setFixedHeight(self.surname.sizeHint().height())
        self.date_of_birth.setStyleSheet(clickable_label_style)
        dob_grid.addWidget(self.date_of_birth, 0, 1)
        dob_frame = QFrame()
        dob_frame.setLayout(dob_grid)
        registry.addWidget(dob_frame)

        doa_label = QLabel("Date of acquisition")
        doa_label.setStyleSheet(text_style)
        registry.addWidget(doa_label)
        doa_grid = QGridLayout()
        doa_btn = QPushButton(icon=QIcon("resources/calendar_icon.png"))
        doa_btn.clicked.connect(self.set_date_of_acquisition)
        doa_btn.setFixedWidth(button_height)
        doa_btn.setFixedHeight(button_height)
        doa_btn.setIconSize(QSize(button_height, button_height))
        doa_btn.setStyleSheet("border:none")

        doa_grid.addWidget(doa_btn, 0, 0)
        self.date_of_acquisition = ClickableQLabel(self)
        self.date_of_acquisition.clicked.connect(self.set_date_of_acquisition)
        self.date_of_acquisition.setFixedHeight(
            self.surname.sizeHint().height())
        self.date_of_acquisition.setStyleSheet(clickable_label_style)
        doa_grid.addWidget(self.date_of_acquisition, 0, 1)
        doa_frame = QFrame()
        doa_frame.setLayout(doa_grid)
        registry.addWidget(doa_frame)

        registry_frame = QFrame()
        registry_frame.setFrameShape(QFrame.StyledPanel)
        registry_frame.setLineWidth(0.6)
        registry_frame.setLayout(registry)
        panel_top_left.addWidget(registry_frame)

        legend = QVBoxLayout()
        header_legend = QLabel("Legend")
        header_legend.setStyleSheet(header_style)
        legend.addWidget(header_legend)

        legend_grid = QGridLayout()
        legend_grid.addWidget(header_legend)
        whitelabel = self.get_label(white)
        legend_grid.addWidget(whitelabel, 0, 0)
        score0_label = QLabel("Score 0")
        score0_label.setStyleSheet(text_style)
        legend_grid.addWidget(score0_label, 0, 1)
        yellowlabel = self.get_label(yellow)
        legend_grid.addWidget(yellowlabel, 1, 0)
        score1_label = QLabel("Score 1")
        score1_label.setStyleSheet(text_style)
        legend_grid.addWidget(score1_label, 1, 1)
        orangelabel = self.get_label(orange)
        legend_grid.addWidget(orangelabel, 2, 0)
        score2_label = QLabel("Score 2")
        score2_label.setStyleSheet(text_style)
        legend_grid.addWidget(score2_label, 2, 1)
        redlabel = self.get_label(red)
        legend_grid.addWidget(redlabel, 3, 0)
        score3_label = QLabel("Score 3")
        score3_label.setStyleSheet(text_style)
        legend_grid.addWidget(score3_label, 3, 1)
        greylabel = self.get_label(grey)
        legend_grid.addWidget(greylabel, 4, 0)
        score_nm_label = QLabel("Not measured")
        score_nm_label.setStyleSheet(text_style)
        legend_grid.addWidget(score_nm_label, 4, 1)
        legend.addLayout(legend_grid)

        legend_frame = QFrame()
        legend_frame.setFrameShape(QFrame.StyledPanel)
        legend_frame.setLineWidth(0.6)
        legend_frame.setLayout(legend)
        panel_top_left.addWidget(legend_frame)

        panel_bottom_left = QVBoxLayout()

        totals = QVBoxLayout()
        header_totals = QLabel("Totals")
        header_totals.setStyleSheet(header_style)

        self.pathological_areas = QLabel("Pathological areas: ")
        self.pathological_areas.setStyleSheet(text_style)
        totals.addWidget(header_totals)
        totals.addWidget(self.pathological_areas)

        totals_grid = QGridLayout()
        totals_grid.addWidget(self.get_label(white), 0, 0)
        self.number_whites = QLabel("")
        self.number_whites.setStyleSheet(text_totals_style)
        totals_grid.addWidget(self.number_whites, 0, 1)
        totals_grid.addWidget(self.get_label(yellow), 1, 0)
        self.number_yellow = QLabel("")
        self.number_yellow.setStyleSheet(text_totals_style)
        totals_grid.addWidget(self.number_yellow, 1, 1)
        totals_grid.addWidget(self.get_label(orange), 2, 0)
        self.number_orange = QLabel("")
        self.number_orange.setStyleSheet(text_totals_style)
        totals_grid.addWidget(self.number_orange, 2, 1)
        totals_grid.addWidget(self.get_label(red), 3, 0)
        self.number_red = QLabel("")
        self.number_red.setStyleSheet(text_totals_style)
        totals_grid.addWidget(self.number_red, 3, 1)
        totals.addLayout(totals_grid)

        self.number_grey = QLabel("")

        totals_frame = QFrame()
        totals_frame.setFrameShape(QFrame.StyledPanel)
        totals_frame.setLineWidth(0.6)
        totals_frame.setLayout(totals)
        panel_bottom_left.addWidget(totals_frame)

        self.generate_report_btn = QPushButton(text="GENERATE REPORT")
        self.generate_report_btn.setFixedHeight(button_height)
        self.generate_report_btn.setStyleSheet(button_style)
        self.generate_report_btn.clicked.connect(self.generate_report)
        panel_bottom_left.addWidget(self.generate_report_btn)

        panel_top_left_frame = QFrame()
        panel_top_left_frame.setLayout(panel_top_left)
        panel_bottom_left_frame = QFrame()
        panel_bottom_left_frame.setLayout(panel_bottom_left)

        layout.addWidget(panel_top_left_frame, 0, 0)
        layout.addWidget(panel_bottom_left_frame, 1, 0)

        self.webview = QWebEngineView(None)
        self.webpage = ClickableWebPage()
        self.webview.setPage(self.webpage)

        self.webpage.load(
            QUrl(Path(self.lungs_template_page).absolute().as_uri()))
        self.webpage.signals.id.connect(self.choose_video_file)

        layout.addWidget(self.webview, 0, 1)
        self.webview.show()

        self.video_crop_window = VideoCropWindow(self)
        self.video_crop_window.layout = QVBoxLayout()

        # show video frame for cropping
        panel_video = QVBoxLayout()
        self.panel_video_frame = QFrame()
        self.panel_video_frame.setFrameStyle(QFrame.NoFrame)
        self.panel_video_frame.setLayout(panel_video)

        self.video_crop_window.layout.addWidget(self.panel_video_frame)
        self.video_crop_window.setCentralWidget(self.panel_video_frame)
        self.video_crop_window.setLayout(self.video_crop_window.layout)

        self.video_label = VideoView()

        self.video_label.setScaledContents(True)
        self.video_label.setMinimumSize(1, 1)
        panel_video.addWidget(self.video_label)

        self.crop_btn = QPushButton(text="CROP VIDEO")
        self.crop_btn.setFixedHeight(button_height)
        self.crop_btn.setStyleSheet(button_style)
        self.crop_btn.clicked.connect(self.crop_video)

        panel_video.addWidget(self.crop_btn)

        self.gray_label = QLabel(self.video_label)
        self.gray_label.setStyleSheet(
            "border: 0px; background-color: rgba(124, 124, 124, 160);")
        self.gray_label.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
        self.gray_label.setScaledContents(True)
        self.gray_label.hide()

        self.m_label_gif = QLabel()
        self.m_movie_gif = QMovie("resources/loadingGif.gif")
        self.m_label_gif.setMovie(self.m_movie_gif)
        #self.m_label_gif.setScaledContents(True)
        self.m_label_gif.setSizePolicy(QSizePolicy.Expanding,
                                       QSizePolicy.Expanding)
        self.m_label_gif.setAlignment(Qt.AlignCenter)
        self.m_movie_gif.setScaledSize(QSize(75, 75))
        gifLayout = QVBoxLayout()
        self.video_label.setLayout(gifLayout)
        gifLayout.addWidget(self.m_label_gif, alignment=Qt.AlignCenter)
        self.m_movie_gif.stop()
        self.m_label_gif.hide()

        note_layout = QVBoxLayout()
        n_header = QLabel("Notes of the clinician")
        n_header.setStyleSheet(header_style)
        self.clinician_notes = QTextEdit()
        self.clinician_notes.setPlainText("")
        self.clinician_notes.setStyleSheet(text_style)

        note_layout.addWidget(n_header)
        note_layout.addWidget(self.clinician_notes)
        note_frame = QFrame()
        note_frame.setLayout(note_layout)
        layout.addWidget(note_frame, 1, 1)

        layout.setRowStretch

        layout.setRowMinimumHeight(0, self.top_row_height)
        layout.setRowMinimumHeight(1, self.bottom_row_height)
        layout.setColumnMinimumWidth(0, self.left_column_width)
        layout.setColumnMinimumWidth(1, self.right_column_width)
        layout.setRowStretch(0, 5)
        layout.setRowStretch(1, 3)
        self.setLayout(layout)
        self.show()

    def select_date(self, label):
        widget = Calendar()
        widget.setWindowModality(Qt.ApplicationModal)
        widget.exec_()
        result = widget.selectedDate
        label.setText(result)

    def set_date_of_birth(self):
        self.select_date(self.date_of_birth)

    def set_date_of_acquisition(self):
        self.select_date(self.date_of_acquisition)

    @pyqtSlot()
    def choose_file(self):
        """ Opens the file chooser and starts processing in a separate thread """
        data_path = QFileDialog.getExistingDirectory(self, "Select folder")

        if len(data_path) == 0:
            return
        worker = Worker(data_path, TFClassifier)
        worker.signals.result.connect(self.process_result)
        self.threadpool.start(worker)

    def choose_video_file(self, areaID):
        """ Opens the file chooser and starts processing in a separate thread """

        dialogResult = QFileDialog.getOpenFileName(
            self, "Open Video", "", "Video Files (*.MOV *.mov *.AVI *.avi)")

        if len(dialogResult[0]) == 0:
            return

        self.clickedAreaName = areaID  # to be used later in HTML substitution
        self.video_file_path = dialogResult[0]
        self.frame_path = ""
        self.extract_video_frame(self.video_file_path)

    @pyqtSlot()
    def reset_session(self):
        """ In case user wants to start from scracth, template image webview page has to be loaded"""
        self.startNewSession = True
        # set the template page in the webView
        self.webpage.load(
            QUrl(Path(self.lungs_template_page).absolute().as_uri()))
        # delete the customized page if present
        if (os.path.exists(self.lungs_customized_page)):
            os.remove(self.lungs_customized_page)

    def process_result(self, task):
        """ Retrieves the output of a task """

        task_dict = json.loads(task)
        self.task = task
        #html = customize_report("resources/image.html", task)

        html = ""

        if (self.startNewSession):
            shutil.copy(self.lungs_template_page, self.lungs_customized_page)
            self.startNewSession = False

        with open(self.lungs_customized_page) as file:
            html = "".join(file.readlines())

        if len(html) == 0:
            raise RuntimeError("The HTML template must not be empty")

        if (self.clickedAreaName == ""):
            raise RuntimeError("Clicked area name must not be empty")

        newColorInt = task_dict[
            self.clickedAreaName[1:]]  # strip initial _ character
        newColorClass = self.int_to_color_map[
            newColorInt]  # must be same as in html CSS color class name

        # find id attribute in HTML path tag to change color specifically for one area
        matches = re.findall(
            r'<path id=\"' + self.clickedAreaName + '\" class=\"(.+?)\"', html)

        oldPath = ""
        newPath = ""
        paths = re.findall(r'<path.+?</path>', html)
        for path in paths:
            if (self.clickedAreaName in path):
                oldPath = path
                matches = re.findall(r'class=\"(.+?)\"', path)
                if (len(matches) == 1):
                    newPath = path.replace(matches[0], newColorClass)

        if (oldPath != "" and newPath != ""):
            html = html.replace(oldPath, newPath)

            # replace in the template also the page name in the links
            # TODO: do it only if it is start of new session
            html = html.replace(self.lungs_template_page_name,
                                self.lungs_customized_page_name)

            self.html = html

            export_html(html, self.lungs_customized_page)

            self.webpage.load(
                QUrl(Path(self.lungs_customized_page).absolute().as_uri()))

            self.pathological_areas.setText(
                "Pathological areas: <b>{}/14</b>".format(
                    task_dict['pathological_areas']))
            self.number_whites.setText(str(task_dict['n_score_0']))
            self.number_yellow.setText(str(task_dict['n_score_1']))
            self.number_orange.setText(str(task_dict['n_score_2']))
            self.number_red.setText(str(task_dict['n_score_3']))
            self.number_grey.setText(str(task_dict['n_not_measured']))

            self.video_crop_window.hide()
            self.gray_label.hide()
            self.m_movie_gif.stop()
            self.m_label_gif.hide()

        else:
            raise RuntimeError("Error while customizing html template")

    def show_alert(self, task_dict, fields):
        """ Shows a generic alert. task_dict is a dictionary containing at least the "name" field and the one contained in fields """
        new_window = QDialog()
        new_window.setWindowTitle("{}".format(task_dict['name']))

        layout = QVBoxLayout()

        for field in fields:
            label = QLabel("{}: {}".format(field.capitalize(),
                                           task_dict[field]))
            layout.addWidget(label)

        new_window.setLayout(layout)
        self._dialogs.append(new_window)
        new_window.show()

        # Remove unused dialogs
        to_remove = []
        for d in self._dialogs:
            # Check if some dialogs have been closed
            if not d.isVisible():
                to_remove.append(d)

        for tr in to_remove:
            self._dialogs.remove(tr)

    def get_label(self, color):
        """ Returns a label of the color specified (in HTML) """
        tmp = QLabel()
        tmp.setStyleSheet(
            "border: 1px solid #444;background-color:{};".format(color))
        tmp.setFixedWidth(50)
        tmp.setFixedHeight(20)
        return tmp

    def generate_report(self):
        output_dir = QFileDialog.getExistingDirectory(self, "Select folder")
        if len(output_dir) == 0:
            return
        html = customize_report("resources/report.html", self.task)
        totals = [
            k.text() for k in [
                self.number_whites, self.number_yellow, self.number_orange,
                self.number_red, self.number_grey
            ]
        ]

        name = self.name.text()
        surname = self.surname.text()
        dob = self.date_of_birth.text()
        doa = self.date_of_acquisition.text()

        html = generate_output_html(html, name, surname, dob, doa,
                                    self.pathological_areas.text(), totals,
                                    self.clinician_notes.toPlainText())

        export_html(html, os.path.join(output_dir, "Report.html"))
        export_pdf(
            html,
            os.path.join(output_dir,
                         "{}{}_{}_{}.pdf".format(surname, name, dob, doa)))

    def extract_video_frame(self, file_name):
        """ Open video with ffmpeg and get file_number frame. Not efficient for multiple calls"""
        self.delete_folder_contents(self.tmp_dir)

        commandStringList = [
            self.ffmpeg_bin, '-i', file_name, '-vframes', '1', '-f', 'image2',
            self.tmp_dir + '/frame%03d.jpeg'
        ]

        videoworker = VideoWorker(commandStringList, "success", "fail")
        videoworker.signals.result.connect(self.process_video_frame_result)
        self.threadpool.start(videoworker)

    def process_video_frame_result(self, task):
        """ Async call to process frame extraction result """

        task_dict = json.loads(task)

        if (task_dict["value"] == "success"):
            frame_basenames = sorted(
                list(
                    filter(
                        re.compile(r'frame').search,
                        os.listdir(self.tmp_dir))))

            self.frame_path = os.path.join(self.tmp_dir, frame_basenames[0])
        else:
            self.frame_path = ""
            QMessageBox.about(self, "Extraction Result",
                              "Could not extract frame from video")

        if (self.frame_path != ""):

            pixmap = QPixmap(self.frame_path)
            # calculate a view that preserves aspect ratio of video frame
            video_aspect_ratio = pixmap.height() / pixmap.width()
            video_label_height = int(
                (video_aspect_ratio * self.video_label_width))

            self.video_label.setFixedWidth(pixmap.width())
            self.video_label.setFixedHeight(pixmap.height())
            self.crop_btn.setFixedWidth(pixmap.width())

            self.gray_label.setGeometry(0, 0, pixmap.width(), pixmap.height())

            self.video_label.setPixmap(pixmap)

            # center crop square only at beginning of each session
            if (self.startNewSession):
                self.video_label.rubberBand.setGeometry(
                    (pixmap.width() / 2) -
                    (self.video_label.minSquareSide / 2),
                    (pixmap.height() / 2) -
                    (self.video_label.minSquareSide / 2),
                    self.video_label.minSquareSide,
                    self.video_label.minSquareSide)

            self.panel_video_frame.setFocus()
            self.video_crop_window.show()

    def crop_video(self):
        """Crop from ffmpeg using scaled pixels of rubber band"""

        self.gray_label.show()
        self.m_movie_gif.start()
        self.m_label_gif.show()

        labelX = self.video_label.rubberBand.geometry().x()
        labelY = self.video_label.rubberBand.geometry().y()
        labelRight = self.video_label.rubberBand.geometry().x(
        ) + self.video_label.rubberBand.geometry().width()
        labelBottom = self.video_label.rubberBand.geometry().y(
        ) + self.video_label.rubberBand.geometry().height()
        labelWidth = self.video_label.width
        labelHeight = self.video_label.height
        # need to compensate in case video view has aspect ratio different than the original video, for UI reasons
        # a scale factor has to be applied from label space to video space, asuming
        # that the aspect ratio of the label was the same of the video
        labelToVideoScaleWidth = self.video_label.pixmap().width(
        ) / self.video_label.width()
        labelToVideoScaleHeight = self.video_label.pixmap().height(
        ) / self.video_label.height()
        videoWidth = self.video_label.pixmap().width()
        videoHeight = self.video_label.pixmap().height()
        videoFrameSpaceX = int((labelToVideoScaleWidth * labelX))
        videoFrameSpaceY = int((labelToVideoScaleHeight * labelY))
        videoFrameSpaceRight = int((labelToVideoScaleWidth * labelRight))
        videoFrameSpaceBottom = int((labelToVideoScaleHeight * labelBottom))

        pre, ext = os.path.splitext(self.video_file_path)
        new_video_name = pre + "_cropped" + ext

        #note: -crf 15 is the quality of exported video. See ffmpeg doc
        commandStringList = [
            self.ffmpeg_bin, '-y', '-i', self.video_file_path, '-filter:v',
            'crop=' + str(videoFrameSpaceRight - videoFrameSpaceX) + ':' +
            str(videoFrameSpaceBottom - videoFrameSpaceY) + ':' +
            str(videoFrameSpaceX) + ':' + str(videoFrameSpaceY) + '', '-crf',
            '15', new_video_name
        ]

        videoworker = VideoWorker(commandStringList, "success",
                                  "Error exporting cropped video")
        videoworker.signals.result.connect(self.process_video_crop_result)
        self.threadpool.start(videoworker)

    def process_video_crop_result(self, task):
        """ Retrieves the output of a task """
        task_dict = json.loads(task)
        if (task_dict["value"] != "success"):
            QMessageBox.about(self, "Crop Result", task_dict["value"])

        # clean tmp folder
        self.delete_folder_contents(self.tmp_dir)

        dir = os.path.dirname(self.video_file_path)
        worker = Worker(dir, TFClassifier)
        worker.signals.result.connect(self.process_result)
        self.threadpool.start(worker)

    #TODO put in utils
    def create_directory(self, directory):
        if not os.path.exists(directory):
            os.makedirs(directory)

    def delete_folder_contents(self, directory):
        for filename in os.listdir(directory):
            file_path = os.path.join(directory, filename)
            try:
                if os.path.isfile(file_path) or os.path.islink(file_path):
                    os.unlink(file_path)
                elif os.path.isdir(file_path):
                    shutil.rmtree(file_path)
            except Exception as e:
                print('Failed to delete %s. Reason: %s' % (file_path, e))
Ejemplo n.º 3
0
class DataWindow(QMainWindow):

    dataAdded = pyqtSignal(dict)
    dataActivated = pyqtSignal(dict)
    windowClosed = pyqtSignal(str)

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

        self.dataId = dataId
        self.setWindowTitle(getAppTitle() + f" ({dataId})")
        self.data = {}

        self.addingQueue = {}
        self.currentlyProcessingPlotData = False
        self.pendingPlotData = False

        # plot settings
        setMplDefaults()

        # data chosing widgets
        self.structureWidget = DataStructure()
        self.plotChoice = PlotChoice()
        chooserLayout = QVBoxLayout()
        chooserLayout.addWidget(self.structureWidget)
        chooserLayout.addWidget(self.plotChoice)

        # plot control widgets
        self.plot = MPLPlot()
        plotLayout = QVBoxLayout()
        plotLayout.addWidget(self.plot)
        plotLayout.addWidget(NavBar(self.plot, self))

        # Main layout
        self.frame = QFrame()
        mainLayout = QHBoxLayout(self.frame)
        mainLayout.addLayout(chooserLayout)
        mainLayout.addLayout(plotLayout)

        # data processing threads
        self.dataAdder = DataAdder()
        self.dataAdderThread = QThread()
        self.dataAdder.moveToThread(self.dataAdderThread)
        self.dataAdder.dataUpdated.connect(self.dataFromAdder)
        self.dataAdder.dataUpdated.connect(self.dataAdderThread.quit)
        self.dataAdderThread.started.connect(self.dataAdder.run)

        self.plotData = PlotData()
        self.plotDataThread = QThread()
        self.plotData.moveToThread(self.plotDataThread)
        self.plotData.dataProcessed.connect(self.updatePlot)
        self.plotData.dataProcessed.connect(self.plotDataThread.quit)
        self.plotDataThread.started.connect(self.plotData.processData)

        # signals/slots for data selection etc.
        self.dataAdded.connect(self.structureWidget.update)
        self.dataAdded.connect(self.updatePlotChoices)
        self.dataAdded.connect(self.updatePlotData)

        self.structureWidget.itemSelectionChanged.connect(self.activateData)
        self.dataActivated.connect(self.plotChoice.setOptions)

        self.plotChoice.choiceUpdated.connect(self.updatePlotData)

        # activate window
        self.frame.setFocus()
        self.setCentralWidget(self.frame)
        self.activateWindow()

    @pyqtSlot()
    def activateData(self):
        item = self.structureWidget.selectedItems()[0]
        self.activeDataSet = item.text(0)
        self.dataActivated.emit(self.dataStructure[self.activeDataSet])

    def updatePlotChoices(self, dataStructure):
        n = self.structureWidget.getSelected()
        if n is not None:
            self.plotChoice.updateData(dataStructure[n])

    @pyqtSlot()
    def updatePlotData(self):
        if self.plotDataThread.isRunning():
            self.pendingPlotData = True
        else:
            self.currentPlotChoiceInfo = self.plotChoice.choiceInfo
            self.pendingPlotData = False
            self.plotData.setData(self.data[self.activeDataSet],
                                  self.currentPlotChoiceInfo)
            self.plotDataThread.start()

    def _plot1D(self, x, data):
        self.plot.axes.plot(x, data, 'o')
        self.plot.axes.set_xlabel(self.currentPlotChoiceInfo['xAxis']['name'])
        self.plot.axes.set_ylabel(self.activeDataSet)

    def _plot2D(self, x, y, data):
        xx, yy = pcolorgrid(x, y)
        if self.currentPlotChoiceInfo['xAxis'][
                'idx'] < self.currentPlotChoiceInfo['yAxis']['idx']:
            im = self.plot.axes.pcolormesh(xx, yy, data.T)
        else:
            im = self.plot.axes.pcolormesh(xx, yy, data)
        cb = self.plot.fig.colorbar(im)
        self.plot.axes.set_xlabel(self.currentPlotChoiceInfo['xAxis']['name'])
        self.plot.axes.set_ylabel(self.currentPlotChoiceInfo['yAxis']['name'])
        cb.set_label(self.activeDataSet)

    @pyqtSlot(object, object, object)
    def updatePlot(self, data, x, y):
        self.plot.clearFig()

        if x is None and y is None:
            self.plot.draw()
        elif x is not None and y is None:
            self._plot1D(x, data)
        elif y.size < 2:
            self._plot1D(x, data)
        elif x is not None and y is not None and x.size > 1:
            self._plot2D(x, y, data)

        self.plot.axes.set_title("{} [{}]".format(self.dataId,
                                                  self.activeDataSet),
                                 size='x-small')
        self.plot.fig.tight_layout()
        self.plot.draw()

        if self.pendingPlotData:
            self.updatePlotData()

    #
    # Data adding
    #
    def addData(self, dataDict):
        """
        Here we receive new data from the listener.
        We'll use a separate thread for processing and combining (numerics might be costly).
        If the thread is already running, we'll put the new data into a queue that will
        be resolved during the next call of addData (i.e, queue will grow until current
        adding thread is done.)
        """
        doUpdate = dataDict.get('update', False) and self.data != {}
        dataDict = dataDict.get('datasets', {})

        if self.dataAdderThread.isRunning():
            if self.addingQueue == {}:
                self.addingQueue = dataDict
            else:
                self.addingQueue = combineDicts(self.addingQueue, dataDict)
        else:
            if self.addingQueue != {}:
                dataDict = combineDicts(self.addingQueue, dataDict)

            if dataDict != {}:
                self.dataAdder.setData(self.data, dataDict, doUpdate)
                self.dataAdderThread.start()
                self.addingQueue = {}

    @pyqtSlot(object, dict)
    def dataFromAdder(self, data, dataStructure):
        self.data = data
        self.dataStructure = dataStructure
        self.dataAdded.emit(self.dataStructure)

    # clean-up
    def closeEvent(self, event):
        self.windowClosed.emit(self.dataId)