Пример #1
0
    def __init__(self, parent=QWidget.find(rt.windows.getMAXHWND())):
        super(PyMaxDialog, self).__init__(parent)
        self.setWindowTitle('Progress')

        main_layout = QVBoxLayout()
        label = QLabel("Progress so far")
        main_layout.addWidget(label)

        # progress bar
        progb = QProgressBar()
        progb.minimum = MINRANGE
        progb.maximum = MAXRANGE
        main_layout.addWidget(progb)

        # abort button
        btn = QPushButton("abort")
        main_layout.addWidget(btn)

        self.setLayout(main_layout)
        self.resize(250, 100)

        # start the worker
        self.worker = Worker()
        self.worker.progress.connect(progb.setValue)
        self.worker.start()

        # connect abort button
        btn.clicked.connect(self.worker.abort)
Пример #2
0
class MainWindow(QMainWindow):

    def __init__(self):
        QMainWindow.__init__(self)
        self.setWindowTitle("A Company Login Windows")
        self.setGeometry(300,250,400,300)
        
        #self.CreateStatusBar()
        self.CreateProgressBar()
        #self.showProgress()


    def CreateStatusBar(self):
        self.myStatusBar = QStatusBar()

        self.myStatusBar.showMessage('Ready',0)
        
        self.setStatusBar(self.myStatusBar)

    def CreateProgressBar(self):
        self.myStatusBar = QStatusBar()
        
        self.progressBar = QProgressBar()
        self.statusLabel = QLabel("Showing Progress")
        #self.finishLabel = QLabel("Finish Progress")
        self.progressBar.setMinimum(0)
        self.progressBar.setMaximum(100)
        self.progressBar.setValue(0)
        self.myStatusBar.addWidget(self.statusLabel,1)
        self.myStatusBar.addWidget(self.progressBar,2)
        #self.myStatusBar.addWidget(self.finishLabel,3)

        self.setStatusBar(self.myStatusBar)

    def showProgress(self):
        while(self.progressBar.value() < self.progressBar.maximum()):
            self.progressBar.setValue(self.progressBar.value() + 10)
            time.sleep(1)
        self.statusLabel.setText('Ready') 
class TrainingConsoleWidget(QWidget):
    """The TrainingConsoleWidget provides a widget for controlling the training status
    of a model, in a simple way. (Analog of the console output in Keras, but with a few
    more options).

    It also can save and show an history of the last trained models.
    """

    training_started = Signal()
    training_stopped = Signal()

    class TrainingStatus(Enum):
        Running = 1
        Stopped = 2
        Not_Compiled = 3

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

        # Components
        self._pretrained_model: Optional["keras.models.Model"] = None
        self._ttv: Optional["TTVSets"] = None
        self._hyperparameters: Optional[Dict] = None
        self._callbacks: List[Callback] = []

        self._trained_model: Optional["keras.models.Model"] = None

        # Widgets
        self._start_training_button = QPushButton("Start training")
        self._stop_training_button = QPushButton("Stop training")

        self._buttons_layout = QHBoxLayout()
        self._buttons_layout.addWidget(self._start_training_button)
        self._buttons_layout.addWidget(self._stop_training_button)

        self._status_label = QLabel()

        self._batch_progress_bar = QProgressBar()
        self._epoch_progress_bar = QProgressBar()

        self.training_output_textbox = QPlainTextEdit()
        self.training_output_textbox.setReadOnly(True)

        console_output_group = QGroupBox("Console output")
        console_output_layout = QVBoxLayout()
        console_output_layout.setContentsMargins(0, 0, 0, 0)
        console_output_layout.addWidget(self.training_output_textbox)
        console_output_group.setLayout(console_output_layout)

        self._main_layout = QVBoxLayout()
        self._main_layout.addLayout(self._buttons_layout)
        self._main_layout.addWidget(self._status_label, Qt.AlignRight)
        self._main_layout.addWidget(console_output_group)
        self._main_layout.addWidget(self._batch_progress_bar)
        self._main_layout.addWidget(self._epoch_progress_bar)
        self.setLayout(self._main_layout)

        # Connections
        self._start_training_button.clicked.connect(self.start_training)
        self._stop_training_button.clicked.connect(self.stop_training)

        # Inner workings
        self.training_status = self.TrainingStatus.Not_Compiled
        self._training_thread = None

    @property
    def training_status(self):
        """Returns the current status of the training (Running, Stopped...)"""
        return self._training_status

    @training_status.setter
    def training_status(self, new_status):
        """Changes the training status.

        Doing so will update the interface accordingly.
        """
        self._training_status = new_status

        if self._training_status == self.TrainingStatus.Running:
            self._start_training_button.setEnabled(False)
            self._stop_training_button.setEnabled(True)
            self._status_label.setText("Running")
            self.start_training

        elif self._training_status == self.TrainingStatus.Stopped:
            self._start_training_button.setEnabled(True)
            self._stop_training_button.setEnabled(False)
            self._status_label.setText("Stopped")

        elif self._training_status == self.TrainingStatus.Not_Compiled:
            self._start_training_button.setEnabled(True)
            self._stop_training_button.setEnabled(False)
            self._status_label.setText("Not Compiled")

    def set_ttv(self, ttv: "TTVSets"):
        """Sets the Train/Test/Validation models used for training."""
        self._ttv = ttv

    def set_pretrained_model(self, pretrained_model: "Model"):
        """Sets a new pretrained model for training."""
        self._pretrained_model = pretrained_model

        self.training_status = self.TrainingStatus.Not_Compiled

    def set_hyperparameters(self, hyperparameters: Dict):
        """Sets new hyperparameters for training."""
        self._hyperparameters = hyperparameters

        self.training_status = self.TrainingStatus.Not_Compiled

    def set_callbacks(self, callbacks: List[Callback]):
        self._callbacks = callbacks

    def get_trained_model(self):
        """Returns the model after it has been trained."""
        return self._trained_model

    def compile_model(self):
        """Compile the model with the passed hyperparameters. The dataset is needed for
        the input shape."""
        LOGGER.info("Starting to compile the model...")

        if not self._is_input_ready():
            return False

        # Create a new model based on the pretrained one, but with a new InputLayer
        # compatible with the dataset
        if self._pretrained_model.layers[0].__class__.__name__ != "InputLayer":
            input_layer = Input(self._ttv.train.input_shape)

            output = self._pretrained_model(input_layer)
            self._trained_model = Model(input_layer, output)
        else:
            self._trained_model = self._pretrained_model

        try:
            self._trained_model.compile(
                optimizer=self._hyperparameters["optimizer"],
                loss=self._hyperparameters["loss_function"],
                metrics=["accuracy"],
            )

            self._trained_model.summary()

            LOGGER.info("Model compiled successfully!!")

            self.training_status = self.TrainingStatus.Stopped

            return True

        except Exception as err:
            LOGGER.exception("Model Compiling error: ", err)

            self.training_output_textbox.setPlainText(
                "> Error while compiling the model:\n", str(err))

        return False

    def start_training(self):
        """Starts the training on a new thread."""
        if self.training_status == self.TrainingStatus.Not_Compiled:
            successfully_compiled = self.compile_model()

            if not successfully_compiled:
                LOGGER.info("Couldn't compile model. Training not started.")
                return

        total_train_batches = len(self._ttv.train)
        total_train_epochs = self._hyperparameters["epochs"]

        self._batch_progress_bar.setMaximum(total_train_batches)
        self._epoch_progress_bar.setMaximum(total_train_epochs)
        self._epoch_progress_bar.setValue(0)
        self.training_output_textbox.clear()

        def epoch_begin_update(epoch: int, logs):
            message = f"==== Epoch {epoch + 1}/{total_train_epochs} ===="

            LOGGER.info(message)
            self.training_output_textbox.appendPlainText(message)
            self._epoch_progress_bar.setValue(epoch)

        def batch_end_update(batch: int, logs):
            # Update progress
            self._batch_progress_bar.setValue(batch)

            # Log metrics on console
            message = f"{batch}/{total_train_batches}"

            for (k, v) in list(logs.items()):
                message += f" - {k}: {v:.4f}"

            LOGGER.info(message)
            self.training_output_textbox.appendPlainText(message)

        def train_end_update(logs):
            # Put the progress bar at 100% when the training ends
            self._batch_progress_bar.setValue(
                self._batch_progress_bar.maximum())
            self._epoch_progress_bar.setValue(
                self._epoch_progress_bar.maximum())

            # Stop the training
            self.stop_training()

        # Connect callbacks
        signals_callback = SignalsCallback()
        signals_callback.epoch_begin.connect(epoch_begin_update)
        signals_callback.train_batch_end.connect(batch_end_update)
        signals_callback.train_end.connect(train_end_update)

        self.training_stopped.connect(signals_callback.stop_model)

        print(self._callbacks)
        # log_dir = "logs/fit/" + datetime.datetime.now().strftime("%Y_%m_%d-%H_%M_%S")

        # tb = program.TensorBoard()
        # tb.configure(argv=[None, "--logdir", log_dir])
        # url = tb.launch()
        # print("Launched Tensorboard instance in:", url)

        # tf_callback=tf.keras.callbacks.TensorBoard(log_dir=log_dir, histogram_freq=1)
        # tf_callback.set_model(self._trained_model)

        # Start training
        self._fit_worker = FitWorker(
            self._trained_model,
            self._ttv.train,
            self._hyperparameters,
            callbacks=[signals_callback] + self._callbacks,
        )

        self.training_status = self.TrainingStatus.Running
        self._fit_worker.start()

        self.training_started.emit()

    def stop_training(self):
        """Stops the training."""
        self.training_status = self.TrainingStatus.Stopped

        self.training_stopped.emit()

    def _is_input_ready(self) -> bool:
        """Checks if the input values used for training (model, dataset,
        hyperparameters...) are valid."""
        message = ""

        if not self._ttv.train:
            message += "> Training dataset not specified\n"

        if not self._pretrained_model:
            message += "> Model not specified.\n"

        if not self._hyperparameters:
            message += "> Hyperparameters not specified.\n"

        if message:
            self.training_output_textbox.setPlainText(message)
            LOGGER.info(message)
            return False

        return True

    def sizeHint(self) -> "QSize":
        """Returns the expected size of the widget."""
        return QSize(500, 300)

    def __reduce__(self):
        return (TrainingConsoleWidget, ())
Пример #4
0
class TabDisplays(QTabWidget):
    def __init__(self, parent=None):
        super(TabDisplays, self).__init__(parent)

        # Initialize logging
        logging_conf_file = os.path.join(os.path.dirname(__file__),
                                         'cfg/aecgviewer_aecg_logging.conf')
        logging.config.fileConfig(logging_conf_file)
        self.logger = logging.getLogger(__name__)

        self.studyindex_info = aecg.tools.indexer.StudyInfo()

        self.validator = QWidget()

        self.studyinfo = QWidget()

        self.statistics = QWidget()

        self.waveforms = QWidget()

        self.waveforms.setAccessibleName("Waveforms")

        self.scatterplot = QWidget()

        self.histogram = QWidget()

        self.trends = QWidget()

        self.xmlviewer = QWidget()
        self.xml_display = QTextEdit(self.xmlviewer)

        self.options = QWidget()

        self.aecg_display_area = QScrollArea()
        self.cbECGLayout = QComboBox()
        self.aecg_display = EcgDisplayWidget(self.aecg_display_area)
        self.aecg_display_area.setWidget(self.aecg_display)

        self.addTab(self.validator, "Study information")
        self.addTab(self.waveforms, "Waveforms")
        self.addTab(self.xmlviewer, "XML")
        self.addTab(self.options, "Options")

        self.setup_validator()
        self.setup_waveforms()
        self.setup_xmlviewer()
        self.setup_options()

        size = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        size.setHeightForWidth(False)
        self.setSizePolicy(size)

        # Initialized a threpool with 2 threads 1 for the GUI, 1 for long
        # tasks, so GUI remains responsive
        self.threadpool = QThreadPool()
        self.threadpool.setMaxThreadCount(2)
        self.validator_worker = None
        self.indexing_timer = QElapsedTimer()

    def setup_validator(self):
        self.directory_indexer = None  # aecg.indexing.DirectoryIndexer()
        self.validator_layout_container = QWidget()
        self.validator_layout = QFormLayout()
        self.validator_form_layout = QFormLayout(
            self.validator_layout_container)
        self.validator_grid_layout = QGridLayout()
        self.study_info_file = QLineEdit()
        self.study_info_file.setToolTip("Study index file")
        self.study_info_description = QLineEdit()
        self.study_info_description.setToolTip("Description")
        self.app_type = QLineEdit()
        self.app_type.setToolTip("Application type (e.g., NDA, IND, BLA, IDE)")
        self.app_num = QLineEdit()
        self.app_num.setToolTip("Six-digit application number")
        self.app_num.setValidator(QIntValidator(self.app_num))
        self.study_id = QLineEdit()
        self.study_id.setToolTip("Study identifier")
        self.study_sponsor = QLineEdit()
        self.study_sponsor.setToolTip("Sponsor of the study")

        self.study_annotation_aecg_cb = QComboBox()
        self.study_annotation_aecg_cb.addItems(
            ["Rhythm", "Derived beat", "Holter-rhythm", "Holter-derived"])
        self.study_annotation_aecg_cb.setToolTip(
            "Waveforms used to perform the ECG measurements (i.e., "
            "annotations)\n"
            "\tRhythm: annotations in a rhythm strip or discrete ECG "
            "extraction (e.g., 10-s strips)\n"
            "\tDerived beat: annotations in a representative beat derived "
            "from a rhythm strip\n"
            "\tHolter-rhythm: annotations in a the analysis window of a "
            "continuous recording\n"
            "\tHolter-derived: annotations in a representative beat derived "
            "from analysis window of a continuous recording\n")
        self.study_annotation_lead_cb = QComboBox()
        self.ui_leads = ["GLOBAL"] + aecg.STD_LEADS[0:12] +\
            [aecg.KNOWN_NON_STD_LEADS[1]] + aecg.STD_LEADS[12:15] + ["Other"]
        self.study_annotation_lead_cb.addItems(self.ui_leads)
        self.study_annotation_lead_cb.setToolTip(
            "Primary analysis lead annotated per protocol. There could be "
            "annotations in other leads also, but only the primary lead should"
            " be selected here.\n"
            "Select global if all leads were used at the "
            "same time (e.g., superimposed on screen).\n"
            "Select other if the primary lead used is not in the list.")

        self.study_numsubjects = QLineEdit()
        self.study_numsubjects.setToolTip(
            "Number of subjects with ECGs in the study")
        self.study_numsubjects.setValidator(
            QIntValidator(self.study_numsubjects))

        self.study_aecgpersubject = QLineEdit()
        self.study_aecgpersubject.setToolTip(
            "Number of scheduled ECGs (or analysis windows) per subject as "
            "specified in the study protocol.\n"
            "Enter average number of ECGs "
            "per subject if the protocol does not specify a fixed number of "
            "ECGs per subject.")
        self.study_aecgpersubject.setValidator(
            QIntValidator(self.study_aecgpersubject))

        self.study_numaecg = QLineEdit()
        self.study_numaecg.setToolTip(
            "Total number of aECG XML files in the study")
        self.study_numaecg.setValidator(QIntValidator(self.study_numaecg))

        self.study_annotation_numbeats = QLineEdit()
        self.study_annotation_numbeats.setToolTip(
            "Minimum number of beats annotated in each ECG or analysis window"
            ".\nEnter 1 if annotations were done in the derived beat.")
        self.study_annotation_numbeats.setValidator(
            QIntValidator(self.study_annotation_numbeats))

        self.aecg_numsubjects = QLineEdit()
        self.aecg_numsubjects.setToolTip(
            "Number of subjects found across the provided aECG XML files")
        self.aecg_numsubjects.setReadOnly(True)

        self.aecg_aecgpersubject = QLineEdit()
        self.aecg_aecgpersubject.setToolTip(
            "Average number of ECGs per subject found across the provided "
            "aECG XML files")
        self.aecg_aecgpersubject.setReadOnly(True)

        self.aecg_numaecg = QLineEdit()
        self.aecg_numaecg.setToolTip(
            "Number of aECG XML files found in the study aECG directory")
        self.aecg_numaecg.setReadOnly(True)

        self.subjects_less_aecgs = QLineEdit()
        self.subjects_less_aecgs.setToolTip(
            "Percentage of subjects with less aECGs than specified per "
            "protocol")
        self.subjects_less_aecgs.setReadOnly(True)

        self.subjects_more_aecgs = QLineEdit()
        self.subjects_more_aecgs.setToolTip(
            "Percentage of subjects with more aECGs than specified per "
            "protocol")
        self.subjects_more_aecgs.setReadOnly(True)

        self.aecgs_no_annotations = QLineEdit()
        self.aecgs_no_annotations.setToolTip(
            "Percentage of aECGs with no annotations")
        self.aecgs_no_annotations.setReadOnly(True)

        self.aecgs_less_qt_in_primary_lead = QLineEdit()
        self.aecgs_less_qt_in_primary_lead.setToolTip(
            "Percentage of aECGs with less QT intervals in the primary lead "
            "than specified per protocol")
        self.aecgs_less_qt_in_primary_lead.setReadOnly(True)

        self.aecgs_less_qts = QLineEdit()
        self.aecgs_less_qts.setToolTip(
            "Percentage of aECGs with less QT intervals than specified per "
            "protocol")
        self.aecgs_less_qts.setReadOnly(True)

        self.aecgs_annotations_multiple_leads = QLineEdit()
        self.aecgs_annotations_multiple_leads.setToolTip(
            "Percentage of aECGs with QT annotations in multiple leads")
        self.aecgs_annotations_multiple_leads.setReadOnly(True)

        self.aecgs_annotations_no_primary_lead = QLineEdit()
        self.aecgs_annotations_no_primary_lead.setToolTip(
            "Percentage of aECGs with QT annotations not in the primary lead")
        self.aecgs_annotations_no_primary_lead.setReadOnly(True)

        self.aecgs_with_errors = QLineEdit()
        self.aecgs_with_errors.setToolTip("Number of aECG files with errors")
        self.aecgs_with_errors.setReadOnly(True)

        self.aecgs_potentially_digitized = QLineEdit()
        self.aecgs_potentially_digitized.setToolTip(
            "Number of aECG files potentially digitized (i.e., with more than "
            "5% of samples missing)")
        self.aecgs_potentially_digitized.setReadOnly(True)

        self.study_dir = QLineEdit()
        self.study_dir.setToolTip("Directory containing the aECG files")
        self.study_dir_button = QPushButton("...")
        self.study_dir_button.clicked.connect(self.select_study_dir)
        self.study_dir_button.setToolTip("Open select directory dialog")

        self.validator_form_layout.addRow("Application Type", self.app_type)
        self.validator_form_layout.addRow("Application Number", self.app_num)
        self.validator_form_layout.addRow("Study name/ID", self.study_id)
        self.validator_form_layout.addRow("Sponsor", self.study_sponsor)
        self.validator_form_layout.addRow("Study description",
                                          self.study_info_description)

        self.validator_form_layout.addRow("Annotations in",
                                          self.study_annotation_aecg_cb)
        self.validator_form_layout.addRow("Annotations primary lead",
                                          self.study_annotation_lead_cb)

        self.validator_grid_layout.addWidget(QLabel(""), 0, 0)
        self.validator_grid_layout.addWidget(
            QLabel("Per study protocol or report"), 0, 1)
        self.validator_grid_layout.addWidget(QLabel("Found in aECG files"), 0,
                                             2)

        self.validator_grid_layout.addWidget(QLabel("Number of subjects"), 1,
                                             0)
        self.validator_grid_layout.addWidget(self.study_numsubjects, 1, 1)
        self.validator_grid_layout.addWidget(self.aecg_numsubjects, 1, 2)

        self.validator_grid_layout.addWidget(
            QLabel("Number of aECG per subject"), 2, 0)
        self.validator_grid_layout.addWidget(self.study_aecgpersubject, 2, 1)
        self.validator_grid_layout.addWidget(self.aecg_aecgpersubject, 2, 2)

        self.validator_grid_layout.addWidget(QLabel("Total number of aECG"), 3,
                                             0)
        self.validator_grid_layout.addWidget(self.study_numaecg, 3, 1)
        self.validator_grid_layout.addWidget(self.aecg_numaecg, 3, 2)

        self.validator_grid_layout.addWidget(
            QLabel("Number of beats per aECG"), 4, 0)
        self.validator_grid_layout.addWidget(self.study_annotation_numbeats, 4,
                                             1)

        self.validator_grid_layout.addWidget(
            QLabel("Subjects with fewer ECGs"), 5, 1)
        self.validator_grid_layout.addWidget(self.subjects_less_aecgs, 5, 2)
        self.validator_grid_layout.addWidget(QLabel("Subjects with more ECGs"),
                                             6, 1)
        self.validator_grid_layout.addWidget(self.subjects_more_aecgs, 6, 2)
        self.validator_grid_layout.addWidget(
            QLabel("aECGs without annotations"), 7, 1)
        self.validator_grid_layout.addWidget(self.aecgs_no_annotations, 7, 2)
        self.validator_grid_layout.addWidget(
            QLabel("aECGs without expected number of QTs in primary lead"), 8,
            1)
        self.validator_grid_layout.addWidget(
            self.aecgs_less_qt_in_primary_lead, 8, 2)
        self.validator_grid_layout.addWidget(
            QLabel("aECGs without expected number of QTs"), 9, 1)
        self.validator_grid_layout.addWidget(self.aecgs_less_qts, 9, 2)

        self.validator_grid_layout.addWidget(
            QLabel("aECGs annotated in multiple leads"), 10, 1)
        self.validator_grid_layout.addWidget(
            self.aecgs_annotations_multiple_leads, 10, 2)
        self.validator_grid_layout.addWidget(
            QLabel("aECGs with annotations not in primary lead"), 11, 1)
        self.validator_grid_layout.addWidget(
            self.aecgs_annotations_no_primary_lead, 11, 2)
        self.validator_grid_layout.addWidget(QLabel("aECGs with errors"), 12,
                                             1)
        self.validator_grid_layout.addWidget(self.aecgs_with_errors, 12, 2)

        self.validator_grid_layout.addWidget(
            QLabel("Potentially digitized aECGs"), 13, 1)
        self.validator_grid_layout.addWidget(self.aecgs_potentially_digitized,
                                             13, 2)

        self.validator_form_layout.addRow(self.validator_grid_layout)

        tmp = QHBoxLayout()
        tmp.addWidget(self.study_dir)
        tmp.addWidget(self.study_dir_button)
        self.validator_form_layout.addRow("Study aECGs directory", tmp)

        self.validator_form_layout.addRow("Study index file",
                                          self.study_info_file)

        self.validator_layout.addWidget(self.validator_layout_container)
        self.validator_effective_dirs = QLabel("")
        self.validator_effective_dirs.setWordWrap(True)
        self.validator_layout.addWidget(self.validator_effective_dirs)

        self.val_button = QPushButton("Generate/update study index")
        self.val_button.clicked.connect(self.importstudy_dialog)
        self.validator_layout.addWidget(self.val_button)
        self.cancel_val_button = QPushButton("Cancel study index generation")
        self.cancel_val_button.clicked.connect(self.cancel_validator)
        self.cancel_val_button.setEnabled(False)
        self.validator_layout.addWidget(self.cancel_val_button)
        self.validator_pl = QLabel("")
        self.validator_layout.addWidget(self.validator_pl)
        self.validator_pb = QProgressBar()
        self.validator_layout.addWidget(self.validator_pb)
        self.validator.setLayout(self.validator_layout)
        self.stop_indexing = False

        self.lastindexing_starttime = None

        self.update_validator_effective_dirs()

    def effective_aecgs_dir(self, navwidget, silent=False):
        aecgs_effective_dir = self.study_dir.text()
        if navwidget.project_loaded != '':
            # Path specified in the GUI
            potential_aecgs_dirs = [self.study_dir.text()]
            # StudyDir path from current working directory
            potential_aecgs_dirs += [self.studyindex_info.StudyDir]
            # StudyDir path from directory where the index is located
            potential_aecgs_dirs += [
                os.path.join(os.path.dirname(navwidget.project_loaded),
                             self.studyindex_info.StudyDir)
            ]
            # StudyDir replaced with the directory where the index is located
            potential_aecgs_dirs += [os.path.dirname(navwidget.project_loaded)]
            dir_found = False
            # Get xml and zip filenames from first element in the index
            aecg_xml_file = navwidget.data_index["AECGXML"][0]
            zipfile = ""
            if aecg_xml_file != "":
                zipfile = navwidget.data_index["ZIPFILE"][0]
            for p in potential_aecgs_dirs:
                testfn = os.path.join(p, aecg_xml_file)
                if zipfile != "":
                    testfn = os.path.join(p, zipfile)
                if os.path.isfile(testfn):
                    dir_found = True
                    aecgs_effective_dir = p
                    break
            if not silent:
                if not dir_found:
                    QMessageBox.warning(
                        self, f"Study aECGs directory not found",
                        f"The following paths were checked:"
                        f"{[','.join(p) for p in potential_aecgs_dirs]} and "
                        f"none of them is valid")
                elif p != self.study_dir.text():
                    QMessageBox.warning(
                        self, f"Study aECGs directory not found",
                        f"The path specified in the study aECGs directory is "
                        f"not valid and {p} is being used instead. Check and "
                        f"update the path in Study aECGs directory textbox if "
                        f"the suggested path is not the adequate path")
        return aecgs_effective_dir

    def update_validator_effective_dirs(self):
        msg = f"Working directory: {os.getcwd()}"
        if self.parent() is not None:
            if isinstance(self.parent(), QSplitter):
                navwidget = self.parent().parent()
            else:  # Tabs widget has not been allocated the QSplitter yet
                navwidget = self.parent()
            project_loaded = navwidget.project_loaded
            if project_loaded != '':
                msg = f"{msg}\nLoaded project index: "\
                      f"{navwidget.project_loaded}"
                effective_aecgs_path = self.effective_aecgs_dir(navwidget)
                msg = f"{msg}\nEffective study aECGs directory: "\
                      f"{effective_aecgs_path}"
            else:
                msg = f"{msg}\nLoaded project index: None"
        else:
            msg = f"{msg}\nLoaded project index: None"
        self.validator_effective_dirs.setText(msg)

    def load_study_info(self, fileName):
        self.study_info_file.setText(fileName)
        try:
            study_info = pd.read_excel(fileName, sheet_name="Info")
            self.studyindex_info = aecg.tools.indexer.StudyInfo()
            self.studyindex_info.__dict__.update(
                study_info.set_index("Property").transpose().reset_index(
                    drop=True).to_dict('index')[0])
            sponsor = ""
            description = ""
            if self.studyindex_info.Sponsor is not None and\
                    isinstance(self.studyindex_info.Sponsor, str):
                sponsor = self.studyindex_info.Sponsor
            if self.studyindex_info.Description is not None and\
                    isinstance(self.studyindex_info.Description, str):
                description = self.studyindex_info.Description
            self.study_sponsor.setText(sponsor)
            self.study_info_description.setText(description)
            self.app_type.setText(self.studyindex_info.AppType)
            self.app_num.setText(f"{int(self.studyindex_info.AppNum):06d}")
            self.study_id.setText(self.studyindex_info.StudyID)
            self.study_numsubjects.setText(str(self.studyindex_info.NumSubj))
            self.study_aecgpersubject.setText(
                str(self.studyindex_info.NECGSubj))
            self.study_numaecg.setText(str(self.studyindex_info.TotalECGs))

            anns_in = self.studyindex_info.AnMethod.upper()
            idx = 0
            if anns_in == "RHYTHM":
                idx = 0
            elif anns_in == "DERIVED":
                idx = 1
            elif anns_in == "HOLTER_RHYTHM":
                idx = 2
            elif anns_in == "HOLTER_MEDIAN_BEAT":
                idx = 3
            else:
                idx = int(anns_in) - 1
            self.study_annotation_aecg_cb.setCurrentIndex(idx)

            the_lead = self.studyindex_info.AnLead
            idx = self.study_annotation_lead_cb.findText(str(the_lead))
            if idx == -1:
                idx = self.study_annotation_lead_cb.findText("MDC_ECG_LEAD_" +
                                                             str(the_lead))
            if idx == -1:
                idx = int(the_lead)
            self.study_annotation_lead_cb.setCurrentIndex(idx)

            self.study_annotation_numbeats.setText(
                str(self.studyindex_info.AnNbeats))
            if self.studyindex_info.StudyDir == "":
                self.studyindex_info.StudyDir = os.path.dirname(fileName)
            self.study_dir.setText(self.studyindex_info.StudyDir)

            self.update_validator_effective_dirs()
            self.setCurrentWidget(self.validator)

        except Exception as ex:
            QMessageBox.critical(
                self, "Import study error",
                "Error reading the study information file: '" + fileName + "'")

    def setup_waveforms(self):
        wflayout = QVBoxLayout()

        # ECG plot layout selection box
        self.cbECGLayout.addItems(
            ['12-lead stacked', '3x4 + lead II rhythm', 'Superimposed'])
        self.cbECGLayout.currentIndexChanged.connect(
            self.ecgplotlayout_changed)

        # Zoom buttons
        blayout = QHBoxLayout()

        pb_zoomin = QPushButton()
        pb_zoomin.setText("Zoom +")
        pb_zoomin.clicked.connect(self.zoom_in)

        pb_zoomreset = QPushButton()
        pb_zoomreset.setText("Zoom 1:1")
        pb_zoomreset.clicked.connect(self.zoom_reset)

        pb_zoomout = QPushButton()
        pb_zoomout.setText("Zoom -")
        pb_zoomout.clicked.connect(self.zoom_out)

        blayout.addWidget(self.cbECGLayout)
        blayout.addWidget(pb_zoomout)
        blayout.addWidget(pb_zoomreset)
        blayout.addWidget(pb_zoomin)

        wflayout.addLayout(blayout)

        # Add QScrollArea to main layout of waveforms tab
        self.aecg_display_area.setWidgetResizable(False)
        wflayout.addWidget(self.aecg_display_area)
        self.waveforms.setLayout(wflayout)
        size = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
        size.setHeightForWidth(False)
        self.aecg_display_area.setSizePolicy(size)
        self.waveforms.setSizePolicy(size)

    def setup_xmlviewer(self):
        wf_layout = QHBoxLayout()
        wf_layout.addWidget(self.xml_display)
        self.xmlviewer.setLayout(wf_layout)

    def setup_options(self):
        self.options_layout = QFormLayout()
        self.aecg_schema_filename = QLineEdit(aecg.get_aecg_schema_location())
        self.options_layout.addRow("aECG XML schema path",
                                   self.aecg_schema_filename)

        self.save_index_every_n_aecgs = QSpinBox()
        self.save_index_every_n_aecgs.setMinimum(0)
        self.save_index_every_n_aecgs.setMaximum(50000)
        self.save_index_every_n_aecgs.setValue(0)
        self.save_index_every_n_aecgs.setSingleStep(100)
        self.save_index_every_n_aecgs.setSuffix(" aECGs")
        self.save_index_every_n_aecgs.setToolTip(
            "Set o 0 to save the study index file only after its generation "
            "is completed.\nOtherwise, the file is saved everytime the "
            " specified number of ECGs have been appended to the index.")
        self.options_layout.addRow("Save index every ",
                                   self.save_index_every_n_aecgs)

        self.save_all_intervals_cb = QCheckBox("")
        self.save_all_intervals_cb.setChecked(False)
        self.options_layout.addRow("Save individual beat intervals",
                                   self.save_all_intervals_cb)

        self.parallel_processing_cb = QCheckBox("")
        self.parallel_processing_cb.setChecked(True)
        self.options_layout.addRow("Parallel processing of files",
                                   self.parallel_processing_cb)

        self.options.setLayout(self.options_layout)

    def zoom_in(self):
        self.aecg_display.apply_zoom(self.aecg_display.zoom_factor + 0.1)

    def zoom_out(self):
        self.aecg_display.apply_zoom(self.aecg_display.zoom_factor - 0.1)

    def zoom_reset(self):
        self.aecg_display.apply_zoom(1.0)

    def ecgplotlayout_changed(self, i):
        self.aecg_display.update_aecg_plot(
            ecg_layout=aecg.utils.ECG_plot_layout(i + 1))

    def update_search_progress(self, i, n):
        self.validator_pl.setText(
            f"Searching aECGs in directory ({n} XML files found)")

    def update_progress(self, i, n):
        j = i
        m = n
        if i <= 1:
            j = 1
            if self.validator_pb.value() > 0:
                j = self.validator_pb.value() + 1
            m = self.validator_pb.maximum()
        running_time = self.indexing_timer.elapsed() * 1e-3  # in seconds
        time_per_item = running_time / j
        # reamining = seconds per item so far * total pending items to process
        remaining_time = time_per_item * (m - j)
        eta = datetime.datetime.now() +\
            datetime.timedelta(seconds=round(remaining_time, 0))
        self.validator_pl.setText(
            f"Validating aECG {j}/{m} | "
            f"Execution time: "
            f"{str(datetime.timedelta(0,seconds=round(running_time)))} | "
            f"{round(1/time_per_item,2)} aECGs per second | "
            f"ETA: {eta.isoformat(timespec='seconds')}")
        self.validator_pb.setValue(j)
        if self.save_index_every_n_aecgs.value() > 0 and\
                len(self.directory_indexer.studyindex) % \
                self.save_index_every_n_aecgs.value() == 0:
            self.save_validator_results(
                pd.concat(self.directory_indexer.studyindex,
                          ignore_index=True))

    def save_validator_results(self, res):
        if res.shape[0] > 0:
            self.studyindex_info = aecg.tools.indexer.StudyInfo()
            self.studyindex_info.StudyDir = self.study_dir.text()
            self.studyindex_info.IndexFile = self.study_info_file.text()
            self.studyindex_info.Sponsor = self.study_sponsor.text()
            self.studyindex_info.Description =\
                self.study_info_description.text()
            self.studyindex_info.Date = self.lastindexing_starttime.isoformat()
            self.studyindex_info.End_date = datetime.datetime.now().isoformat()
            self.studyindex_info.Version = aecg.__version__
            self.studyindex_info.AppType = self.app_type.text()
            self.studyindex_info.AppNum = f"{int(self.app_num.text()):06d}"
            self.studyindex_info.StudyID = self.study_id.text()
            self.studyindex_info.NumSubj = int(self.study_numsubjects.text())
            self.studyindex_info.NECGSubj = int(
                self.study_aecgpersubject.text())
            self.studyindex_info.TotalECGs = int(self.study_numaecg.text())
            anmethod = aecg.tools.indexer.AnnotationMethod(
                self.study_annotation_aecg_cb.currentIndex())
            self.studyindex_info.AnMethod = anmethod.name
            self.studyindex_info.AnLead =\
                self.study_annotation_lead_cb.currentText()
            self.studyindex_info.AnNbeats = int(
                self.study_annotation_numbeats.text())

            # Calculate stats
            study_stats = aecg.tools.indexer.StudyStats(
                self.studyindex_info, res)

            # Save to file
            aecg.tools.indexer.save_study_index(self.studyindex_info, res,
                                                study_stats)

    validator_data_ready = Signal()

    def save_validator_results_and_load_index(self, res):
        self.save_validator_results(res)
        self.validator_data_ready.emit()

    def indexer_validator_results(self, res):
        self.studyindex_df = pd.concat([self.studyindex_df, res],
                                       ignore_index=True)

    def subindex_thread_complete(self):
        return

    def index_directory_thread_complete(self):
        tmp = self.validator_pl.text().replace("ETA:", "Completed: ").replace(
            "Validating", "Validated")
        self.validator_pl.setText(tmp)
        self.val_button.setEnabled(True)
        self.cancel_val_button.setEnabled(False)
        self.validator_layout_container.setEnabled(True)

    def index_directory(self, progress_callback):
        self.lastindexing_starttime = datetime.datetime.now()
        self.indexing_timer.start()

        studyindex_df = []
        n_cores = os.cpu_count()
        aecg_schema = None
        if self.aecg_schema_filename.text() != "":
            aecg_schema = self.aecg_schema_filename.text()
        if self.parallel_processing_cb.isChecked():
            studyindex_df = self.directory_indexer.index_directory(
                self.save_all_intervals_cb.isChecked(), aecg_schema, n_cores,
                progress_callback)
        else:
            studyindex_df = self.directory_indexer.index_directory(
                self.save_all_intervals_cb.isChecked(), aecg_schema, 1,
                progress_callback)

        return studyindex_df

    def importstudy_dialog(self):
        dirName = os.path.normpath(self.study_dir.text())
        if dirName != "":
            if os.path.exists(dirName):
                self.directory_indexer = aecg.indexing.DirectoryIndexer()
                self.directory_indexer.set_aecg_dir(
                    dirName, self.update_search_progress)
                self.validator_pb.setMaximum(self.directory_indexer.num_files)
                self.validator_pb.reset()
                self.stop_indexing = False
                self.validator_layout_container.setEnabled(False)
                self.val_button.setEnabled(False)
                self.cancel_val_button.setEnabled(True)
                self.validator_worker = Worker(self.index_directory)
                self.validator_worker.signals.result.connect(
                    self.save_validator_results_and_load_index)
                self.validator_worker.signals.finished.connect(
                    self.index_directory_thread_complete)
                self.validator_worker.signals.progress.connect(
                    self.update_progress)

                # Execute
                self.threadpool.start(self.validator_worker)
            else:
                QMessageBox.critical(
                    self, "Directory not found",
                    f"Specified study directory not found:\n{dirName}")
        else:
            QMessageBox.critical(self, "Import study error",
                                 "Study directory cannot be empty")

    def cancel_validator(self):
        self.cancel_val_button.setEnabled(False)
        self.stop_indexing = True
        self.directory_indexer.cancel_indexing = True
        self.threadpool.waitForDone(3000)
        self.val_button.setEnabled(True)

    def select_study_dir(self):
        cd = self.study_dir.text()
        if cd == "":
            cd = "."
        dir = QFileDialog.getExistingDirectory(
            self, "Open Directory", cd,
            QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks)
        if dir != "":
            self.study_dir.setText(dir)
class Window(QWidget):
    def __init__(self, *args, **kwargs):
        super(Window, self).__init__(*args, **kwargs)
        self.resize(400, 200)
        self._value = 0

        self.setWindowTitle('Adobe Version Changer')
        self.setWindowIcon(QIcon('favicon.ico'))
        self.progressbar = QProgressBar(self)
        self.progressbar.setRange(0, 99)

        self.widget = QWidget(self)
        self.layout = QGridLayout(self.widget)
        self.label = QLabel('|  Drop your file here  |')

        self.layout.addWidget(self.label, 1, 23, 30, 10)
        self.layout.addWidget(self.progressbar, 2, 0, 1, 58)

        QToolTip.setFont(QFont('Helvetica', 20))
        self.setToolTip(
            '''Drag and Drop into this window a <b>Premiere Project File or Motion Graphics File</b> 
            to be converted. You will find a file named as the Project or Motion Graphic file with 
            the extension <b>"_changed"</b> in that same location.''')

        self.setAcceptDrops(True)

    def progressStart(self):

        animation = QPropertyAnimation(self.progressbar, b'value', self)
        x = int(os.stat(self.userInput).st_size) / 20000
        # print(x)
        animation.setDuration(x)
        animation.setLoopCount(1)

        animation.setKeyValueAt(0, self.progressbar.minimum())
        animation.setKeyValueAt(0.1, 10)
        animation.setKeyValueAt(0.2, 30)
        animation.setKeyValueAt(0.5, 60)
        animation.setKeyValueAt(0.7, 80)
        animation.setKeyValueAt(1, self.progressbar.maximum())
        animation.start(animation.DeleteWhenStopped)

    def center(self):
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    def dragEnterEvent(self, e):
        if e.mimeData().hasUrls():
            e.acceptProposedAction()

    def dropEvent(self, e):
        for url in e.mimeData().urls():
            self.userInput = url.toLocalFile()
            self.progressStart()
            func(self.userInput)

            self.label.setText("| Another file? |")  # repeat
        return self.userInput

    def startprogressBar(self):
        self.progressBar.setVisible(True)
        if self.timer.isActive():
            self.timer.stop()
        else:
            self.timer.start(100, self)

    def resetBar(self):
        self.step = 0
        self.progressBar.setValue(0)

    def timerEvent(self, event):
        if self.step >= 100:
            self.timer.stop()
            return

        self.step += 1
        self.progressBar.setValue(self.step)