def _connect(self): success = False try: self._check_host_url() client = RemotingService(self._host_url) self.dlg = ProgressDialog("Connecting to Cluster...", None, 0, 0, self) func = lambda: client.getService('clustercontrol') self.dlg.exec_(func) self._service = self.dlg.getTargetResult() self._check_api_version() except ConnectionError as e: msg = ("%s\nDo you want to turn off the cluster support?") % str(e) ret = QMessageBox.question(self, "Error", msg) if ret == QMessageBox.Yes: self._turn_off_cluster_support() except Exception as e: QMessageBox.critical(self, "Error", str(e)) else: try: self.dlg.exec_(self._service.get_cecog_versions) cluster_versions = self.dlg.getTargetResult() except Exception as e: QMessageBox.critical(self, "Error", str(e)) else: if not version in set(cluster_versions): QMessageBox.warning( self, "Warning", ("Cluster does not support %s %s" "Available versions: %s" % (appname, version, ', '.join(cluster_versions)))) else: success = True return success
def _connect(self): self._check_host_url() success = False msg = 'Error on connecting to cluster control service on %s' % self._host_url try: client = RemotingService(self._host_url) self.dlg = ProgressDialog("connecting to cluster...", None, 0, 0, self) func = lambda: client.getService('clustercontrol') self.dlg.exec_(func) self._service = self.dlg.getTargetResult() except: exception(self, msg) else: try: self.dlg.exec_(self._service.get_cecog_versions) cluster_versions = self.dlg.getTargetResult() except Exception as e: exception(self, msg + '(%s)' %str(e)) else: if not version in set(cluster_versions): warning(self, 'Cecog version %s not supported by the cluster' % version, 'Valid versions are: %s' \ % ', '.join(cluster_versions)) else: success = True return success
def _on_submit_job(self): self._submit_settings.set_section(SECTION_NAME_GENERAL) if not self._submit_settings.get2('constrain_positions'): positions = [] for plate_id in self.imagecontainer.plates: self.imagecontainer.set_plate(plate_id) meta_data = self.imagecontainer.get_meta_data() positions += [ '%s___%s' % (plate_id, p) for p in meta_data.positions ] self._submit_settings.set2('positions', ','.join(positions)) nr_items = len(positions) else: positions = self._submit_settings.get2('positions') nr_items = len(positions.split(',')) settings_dummy = self._clusterframe.get_special_settings( self._settings) apc = AppPreferences() batch_size = apc.batch_size pathout = self._submit_settings.get2('pathout') if not self._submit_settings('General', 'skip_finished'): self.clear_output_directory(self._settings("General", "pathout")) try: self.dlg = ProgressDialog("Submitting Jobs...", None, 0, 0, self) settings_str = self._submit_settings.to_string() func = lambda: self._service.submit_job( 'cecog_batch', settings_str, pathout, nr_items, batch_size, version) self.dlg.exec_(func) jobid = self.dlg.getTargetResult() except Exception as e: QMessageBox.critical(self, "Error", 'Job submission failed (%s)' % str(e)) else: # FIXME: no idea how DRMAA 1.0 compatible this is if type(jobid) == types.ListType: self._jobid = ','.join(jobid) main_jobid = jobid[0].split('.')[0] else: self._jobid = str(jobid) main_jobid = jobid self._txt_jobid.setText(self._jobid) self._update_job_status() QMessageBox.information(self, "Information", ("Job(s) successfully submitted\n" "Job ID: %s, #jobs: %d" % (main_jobid, nr_items)))
def _on_terminate_job(self): try: self.dlg = ProgressDialog("terminating jobs...", None, 0, 0, self) func = lambda: self._service.control_job(self._jobid, JOB_CONTROL_TERMINATE) self.dlg.exec_(func) except Exception as e: exception(self, 'Error on job termination (%s)' %str(e)) else: self._btn_toogle.setChecked(False) self._toggle_state = JOB_CONTROL_SUSPEND self._btn_toogle.setText(self._toggle_state) self._update_job_status()
def _update_job_status(self): try: self.dlg = ProgressDialog("updating job status...", None, 0, 0, self) func = lambda: self._service.get_job_status(self._jobid) self.dlg.exec_(func) txt = self.dlg.getTargetResult() except Exception as e: exception(self, 'Error on retrieve job status (%s)' %str(e)) else: self._label_jobstatus.setText(txt) return txt
def _on_toggle_job(self): try: self.dlg = ProgressDialog("suspending jobs...", None, 0, 0, self) func = lambda: self._service.control_job(self._jobid, self._toggle_state) self.dlg.exec_(func) except Exception as e: self._toggle_state = JOB_CONTROL_SUSPEND self._btn_toogle.setChecked(False) exception(self, 'Error on toggle job status (%s)' %str(e)) else: if self._toggle_state == JOB_CONTROL_SUSPEND: self._toggle_state = JOB_CONTROL_RESUME else: self._toggle_state = JOB_CONTROL_SUSPEND self._update_job_status() self._btn_toogle.setText(self._toggle_state)
def _connect(self): self._check_host_url() success = False msg = 'Error on connecting to cluster control service on %s' % self._host_url try: client = RemotingService(self._host_url) self.dlg = ProgressDialog("connecting to cluster...", None, 0, 0, self) func = lambda: client.getService('clustercontrol') self.dlg.exec_(func) self._service = self.dlg.getTargetResult() except: exception(self, msg) else: try: self.dlg.exec_(self._service.get_cecog_versions) cluster_versions = self.dlg.getTargetResult() except Exception as e: exception(self, msg + '(%s)' %str(e)) else: if not VERSION in set(cluster_versions): warning(self, 'Cecog version %s not supported by the cluster' % VERSION, 'Valid versions are: %s' \ % ', '.join(cluster_versions)) else: success = True return success
def _connect(self): success = False try: self._check_host_url() client = RemotingService(self._host_url) self.dlg = ProgressDialog("Connecting to Cluster...", None, 0, 0, self) func = lambda: client.getService('clustercontrol') self.dlg.exec_(func) self._service = self.dlg.getTargetResult() self._check_api_version() except ConnectionError as e: msg = ("%s\nDo you want to turn off the cluster support?") %str(e) ret = QMessageBox.question( self, "Error", msg) if ret == QMessageBox.Yes: self._turn_off_cluster_support() except Exception as e: QMessageBox.critical(self, "Error", str(e)) else: try: self.dlg.exec_(self._service.get_cecog_versions) cluster_versions = self.dlg.getTargetResult() except Exception as e: QMessageBox.critical(self, "Error", str(e)) else: if not version in set(cluster_versions): QMessageBox.warning( self, "Warning", ("Cluster does not support %s %s" "Available versions: %s" %(appname, version, ', '.join(cluster_versions)))) else: success = True return success
def _update_job_status(self): if self.jobIds is None: return try: self.dlg = ProgressDialog("Updating Job Status...", None, 0, 0, self) func = lambda: self._service.get_job_status(self._jobid) self.dlg.exec_(func) txt = self.dlg.getTargetResult() except Exception as e: QMessageBox.critical(self, "Error", 'Could not retrieve job status (%s)' % str(e)) else: self._label_jobstatus.setText(txt) return txt
def _on_terminate_job(self): if self.jobIds is None: return try: self.dlg = ProgressDialog("Terminating Jobs...", None, 0, 0, self) func = lambda: self._service.control_job(self._jobid, JOB_CONTROL_TERMINATE) self.dlg.exec_(func) except Exception as e: QMessageBox.critical(self, "Error", "Job termination failed (%s)" % str(e)) else: self._btn_toogle.setChecked(False) self._toggle_state = JOB_CONTROL_SUSPEND self._btn_toogle.setText(self._toggle_state) self._update_job_status()
def _on_submit_job(self): self._submit_settings.set_section(SECTION_NAME_GENERAL) if not self._submit_settings.get2('constrain_positions'): positions = [] for plate_id in self.imagecontainer.plates: self.imagecontainer.set_plate(plate_id) meta_data = self.imagecontainer.get_meta_data() positions += ['%s___%s' % (plate_id, p) for p in meta_data.positions] self._submit_settings.set2('positions', ','.join(positions)) nr_items = len(positions) else: positions = self._submit_settings.get2('positions') nr_items = len(positions.split(',')) # FIXME: we need to get the current value for 'position_granularity' settings_dummy = self._clusterframe.get_special_settings(self._settings) position_granularity = settings_dummy.get('Cluster', 'position_granularity') path_out = self._submit_settings.get2('pathout') emails = str(self._txt_mail.text()).split(',') try: self.dlg = ProgressDialog("submitting jobs...", None, 0, 0, self) settings_str = self._submit_settings.to_string() func = lambda: self._service.submit_job('cecog_batch', settings_str, path_out, emails, nr_items, position_granularity, version) self.dlg.exec_(func) jobid = self.dlg.getTargetResult() except Exception as e: exception(self, 'Error on job submission (%s)' %str(e)) else: # FIXME: no idea how DRMAA 1.0 compatible this is if type(jobid) == types.ListType: self._jobid = ','.join(jobid) main_jobid = jobid[0].split('.')[0] else: self._jobid = str(jobid) main_jobid = jobid self._txt_jobid.setText(self._jobid) self._update_job_status() information(self, 'Job submitted successfully', "Job successfully submitted to the cluster.\nJob ID: %s, items: %d" % (main_jobid, nr_items))
def _set_plate(self, coordinate_new, set_current=False): coordinate_old = self.browser.get_coordinate() plate = coordinate_new.plate func = lambda: self._imagecontainer.set_plate(plate) self.dlg = ProgressDialog("Loading plate...", None, 0, 0, self) self.dlg.exec_(func) meta_data = self._imagecontainer.get_meta_data() if set_current: self._set_current_plate(plate) self._update_position_table(meta_data) self._get_closeby_position(coordinate_old, coordinate_new) self._set_current_position(coordinate_new.position) if self._imagecontainer.has_timelapse: self._update_time_table(meta_data, coordinate_new) self._get_closeby_time(coordinate_old, coordinate_new) self._set_current_time(coordinate_new.time) self._update_info_frame(meta_data) self.coordinate_changed.emit(coordinate_new)
def _on_toggle_job(self): if self.jobIds is None: return try: self.dlg = ProgressDialog("Suspending Jobs...", None, 0, 0, self) func = lambda: self._service.control_job(self._jobid, self. _toggle_state) self.dlg.exec_(func) except Exception as e: self._toggle_state = JOB_CONTROL_SUSPEND self._btn_toogle.setChecked(False) QMessageBox.critical(self, "Error", "Could not toggle job status (%s)" % str(e)) else: if self._toggle_state == JOB_CONTROL_SUSPEND: self._toggle_state = JOB_CONTROL_RESUME else: self._toggle_state = JOB_CONTROL_SUSPEND self._update_job_status() self._btn_toogle.setText(self._toggle_state)
def _on_submit_job(self): self._submit_settings.set_section(SECTION_NAME_GENERAL) if not self._submit_settings.get2('constrain_positions'): positions = [] for plate_id in self.imagecontainer.plates: self.imagecontainer.set_plate(plate_id) meta_data = self.imagecontainer.get_meta_data() positions += ['%s___%s' % (plate_id, p) for p in meta_data.positions] self._submit_settings.set2('positions', ','.join(positions)) nr_items = len(positions) else: positions = self._submit_settings.get2('positions') nr_items = len(positions.split(',')) settings_dummy = self._clusterframe.get_special_settings(self._settings) apc = AppPreferences() batch_size = apc.batch_size pathout = self._submit_settings.get2('pathout') if not self._submit_settings('General', 'skip_finished'): self.clear_output_directory(self._settings("General", "pathout")) try: self.dlg = ProgressDialog("Submitting Jobs...", None, 0, 0, self) settings_str = self._submit_settings.to_string() func = lambda: self._service.submit_job('cecog_batch', settings_str, pathout, nr_items, batch_size, version) self.dlg.exec_(func) jobid = self.dlg.getTargetResult() except Exception as e: QMessageBox.critical( self, "Error", 'Job submission failed (%s)' %str(e)) else: # FIXME: no idea how DRMAA 1.0 compatible this is if type(jobid) == types.ListType: self._jobid = ','.join(jobid) main_jobid = jobid[0].split('.')[0] else: self._jobid = str(jobid) main_jobid = jobid self._txt_jobid.setText(self._jobid) self._update_job_status() QMessageBox.information( self, "Information", ("Job(s) successfully submitted\n" "Job ID: %s, #jobs: %d" % (main_jobid, nr_items)))
def _on_terminate_job(self): if self.jobIds is None: return try: self.dlg = ProgressDialog("Terminating Jobs...", None, 0, 0, self) func = lambda: self._service.control_job(self._jobid, JOB_CONTROL_TERMINATE) self.dlg.exec_(func) except Exception as e: QMessageBox.critical( self, "Error", "Job termination failed (%s)" %str(e)) else: self._btn_toogle.setChecked(False) self._toggle_state = JOB_CONTROL_SUSPEND self._btn_toogle.setText(self._toggle_state) self._update_job_status()
def _update_job_status(self): if self.jobIds is None: return try: self.dlg = ProgressDialog("Updating Job Status...", None, 0, 0, self) func = lambda: self._service.get_job_status(self._jobid) self.dlg.exec_(func) txt = self.dlg.getTargetResult() except Exception as e: QMessageBox.critical( self, "Error", 'Could not retrieve job status (%s)' %str(e)) else: self._label_jobstatus.setText(txt) return txt
def _on_submit_job(self): self._submit_settings.set_section(SECTION_NAME_GENERAL) if not self._submit_settings.get2('constrain_positions'): positions = [] for plate_id in self.imagecontainer.plates: self.imagecontainer.set_plate(plate_id) meta_data = self.imagecontainer.get_meta_data() positions += ['%s___%s' % (plate_id, p) for p in meta_data.positions] self._submit_settings.set2('positions', ','.join(positions)) nr_items = len(positions) else: positions = self._submit_settings.get2('positions') nr_items = len(positions.split(',')) # FIXME: we need to get the current value for 'position_granularity' settings_dummy = self._clusterframe.get_special_settings(self._settings) position_granularity = settings_dummy.get('Cluster', 'position_granularity') path_out = self._submit_settings.get2('pathout') emails = str(self._txt_mail.text()).split(',') try: self.dlg = ProgressDialog("submitting jobs...", None, 0, 0, self) settings_str = self._submit_settings.to_string() func = lambda: self._service.submit_job('cecog_batch', settings_str, path_out, emails, nr_items, position_granularity, VERSION) self.dlg.exec_(func) jobid = self.dlg.getTargetResult() except Exception as e: exception(self, 'Error on job submission (%s)' %str(e)) else: # FIXME: no idea how DRMAA 1.0 compatible this is if type(jobid) == types.ListType: self._jobid = ','.join(jobid) main_jobid = jobid[0].split('.')[0] else: self._jobid = str(jobid) main_jobid = jobid self._txt_jobid.setText(self._jobid) self._update_job_status() information(self, 'Job submitted successfully', "Job successfully submitted to the cluster.\nJob ID: %s, items: %d" % (main_jobid, nr_items))
def _on_toggle_job(self): if self.jobIds is None: return try: self.dlg = ProgressDialog("Suspending Jobs...", None, 0, 0, self) func = lambda: self._service.control_job(self._jobid, self._toggle_state) self.dlg.exec_(func) except Exception as e: self._toggle_state = JOB_CONTROL_SUSPEND self._btn_toogle.setChecked(False) QMessageBox.critical( self, "Error", "Could not toggle job status (%s)" %str(e)) else: if self._toggle_state == JOB_CONTROL_SUSPEND: self._toggle_state = JOB_CONTROL_RESUME else: self._toggle_state = JOB_CONTROL_SUSPEND self._update_job_status() self._btn_toogle.setText(self._toggle_state)
def _abort_processing(self): self.setCursor(Qt.BusyCursor) self._is_abort = True self.dlg = ProgressDialog('terminating...', None, 0, 0, self) self.dlg.exec_(lambda: self._analyzer.abort(wait=True)) self.setCursor(Qt.ArrowCursor)
class BaseProcessorFrame(BaseFrame): def __init__(self, settings, parent, name): super(BaseProcessorFrame, self).__init__(settings, parent, name) self.idialog = parent.idialog self._is_running = False self._is_abort = False self._has_error = True self._current_process = None self._image_combo = None self._stage_infos = {} self._process_items = None self._control_buttons = OrderedDict() shortcut = QShortcut(QKeySequence(Qt.Key_Escape), self) shortcut.activated.connect(self._on_esc_pressed) def set_active(self, state): # set intern state and enable/disable control buttons super(BaseProcessorFrame, self).set_active(state) self.process_control.setButtonsEnabled(state) def _on_update_image(self, images, message): if self.process_control.showImages(): self.idialog.updateImages(images, message) if not self.idialog.isVisible(): self.idialog.raise_() def register_process(self, name): pass def register_control_button(self, name, cls, labels): self._control_buttons[name] = {'labels': labels, 'cls' : cls} def _init_control(self, has_images=True): if not has_images: self.process_control.hideImageCheckBox() for name in self._control_buttons: slot = lambda x: lambda : self._on_process_start(x) self.process_control.addControlButton(name, slot(name)) if not self.TABS is None: self._tab.currentChanged.connect(self._on_tab_changed) self._on_tab_changed(0) else: for name in self._control_buttons: self._set_control_button_text(name=name) def _set_control_button_text(self, name=None, idx=0): if name is None: name = self._current_process try: text = self._control_buttons[name]['labels'][idx] % self._tab_name except: text = self._control_buttons[name]['labels'][idx] self.process_control.buttonByName(name).setText(text) def _toggle_control_buttons(self, name=None): if name is None: name = self._current_process for name2 in self._control_buttons: if name != name2: btn = self.process_control.buttonByName(name) btn.setEnabled(not btn.isEnabled()) @classmethod def get_special_settings(cls, settings, has_timelapse=True): settings = settings.copy() return settings def _get_modified_settings(self, name, has_timelapse): return self.get_special_settings(self._settings, has_timelapse) def _on_tab_changed(self, idx): self._tab_name = CHANNEL_PREFIX[idx] for name in self._control_buttons: self._set_control_button_text(name=name) def _clear_image(self): """Pop up and clear the image display""" self.idialog.clearImage() def _on_process_start(self, name, start_again=False): if not self._is_running or start_again: is_valid = True self._is_abort = False self._has_error = False if self._process_items is None: cls = self._control_buttons[name]['cls'] if type(cls) == types.ListType: self._process_items = cls self._current_process_item = 0 cls = cls[self._current_process_item] else: self._process_items = None self._current_process_item = 0 else: cls = self._process_items[self._current_process_item] if self.name == SECTION_NAME_CLASSIFICATION: result_frame = self._get_result_frame(self._tab_name) result_frame.load_classifier(check=False) learner = result_frame._learner if name == self.PICKING: if not result_frame.classifier.is_annotated: is_valid = False result_frame.msg_pick_samples(self) elif result_frame.classifier.is_trained: if not question(self, 'Samples already picked', 'Do you want to pick samples again and ' 'overwrite previous ' 'results?'): is_valid = False elif name == self.TRAINING: if not result_frame.classifier.is_trained: is_valid = False result_frame.msg_train_classifier(self) elif result_frame.classifier.is_valid: if not question(self, 'Classifier already trained', 'Do you want to train the classifier ' 'again?'): is_valid = False elif name == self.TESTING and not result_frame.classifier.is_valid: is_valid = False result_frame.msg_apply_classifier(self) if cls is MultiAnalyzerThread: ncpu = cpu_count() (ncpu, ok) = QInputDialog.getInt(None, "On your machine are %d processers available." % ncpu, \ "Select the number of processors", \ ncpu, 1, ncpu*2) if not ok: self._process_items = None is_valid = False if is_valid: self._current_process = name if not start_again: self.parent().log_window.clear() self._is_running = True self._stage_infos = {} self._toggle_tabs(False) # disable all section button of the main widget self.toggle_tabs.emit(self.get_name()) self._set_control_button_text(idx=1) self.process_control.toggleButtons(self._current_process) imagecontainer = self.parent().main_window._imagecontainer if cls is PickerThread: self._current_settings = self._get_modified_settings(name, imagecontainer.has_timelapse) self._analyzer = cls(self, self._current_settings, imagecontainer) self._clear_image() elif cls is AnalyzerThread: self._current_settings = self._get_modified_settings(name, imagecontainer.has_timelapse) self._analyzer = cls(self, self._current_settings, imagecontainer) self._clear_image() elif cls is TrainingThread: self._current_settings = self._settings.copy() self._analyzer = cls(self, self._current_settings, result_frame._learner) self._analyzer.setTerminationEnabled(True) self._analyzer.conf_result.connect(result_frame.on_conf_result, Qt.QueuedConnection) result_frame.reset() elif cls is MultiAnalyzerThread: self._current_settings = self._get_modified_settings(name, imagecontainer.has_timelapse) self._analyzer = cls(self, self._current_settings, imagecontainer, ncpu) elif cls is ErrorCorrectionThread: self._current_settings = self._get_modified_settings(name, imagecontainer.has_timelapse) self._analyzer = cls(self, self._current_settings, self.parent().main_window._imagecontainer) self._analyzer.finished.connect(self._on_process_finished) self._analyzer.stage_info.connect(self._on_update_stage_info, Qt.QueuedConnection) self._analyzer.analyzer_error.connect(self._on_error, Qt.QueuedConnection) self._analyzer.image_ready.connect(self._on_update_image) self._analyzer.start(QThread.LowestPriority) if self._current_process_item == 0: self.status_message.emit('Process started...') else: self._abort_processing() def _toggle_tabs(self, state): if not self.TABS is None: self._tab.enable_non_active(state) def _abort_processing(self): self.setCursor(Qt.BusyCursor) self._is_abort = True self.dlg = ProgressDialog('terminating...', None, 0, 0, self) self.dlg.exec_(lambda: self._analyzer.abort(wait=True)) self.setCursor(Qt.ArrowCursor) def _on_error(self, msg, short='An error occurred during processing!'): self._has_error = True critical(self, short, detail=msg) def _on_process_finished(self): self._analyzer.image_ready.disconnect(self._on_update_image) if (not self._process_items is None and self._current_process_item+1 < len(self._process_items) and not self._is_abort and not self._has_error): self._current_process_item += 1 self._on_process_start(self._current_process, start_again=True) else: self._is_running = False self._set_control_button_text(idx=0) self.process_control.toggleButtons(self._current_process) self._toggle_tabs(True) # enable all section button of the main widget self.toggle_tabs.emit(self.get_name()) if not self._is_abort and not self._has_error: if self.name == SECTION_NAME_OBJECTDETECTION: msg = 'Object detection successfully finished.' elif self.name == SECTION_NAME_CLASSIFICATION: if self._current_process == self.PICKING: msg = 'Samples successfully picked.\n\n'\ 'Please train the classifier now based on the '\ 'newly picked samples.' result_frame = self._get_result_frame(self._tab_name) result_frame.load_classifier(check=False) nr_removed = len(result_frame._learner.nan_features) if nr_removed > 0: msg += '\n\n%d features contained NA values and will be removed from training.' % nr_removed elif self._current_process == self.TRAINING: msg = 'Classifier successfully trained.\n\n'\ 'You can test the classifier performance here'\ 'visually or apply the classifier in the '\ 'processing workflow.' elif self._current_process == self.TESTING: msg = 'Classifier testing successfully finished.' elif self.name == SECTION_NAME_TRACKING: msg = 'Tracking successfully finished.' elif self.name == SECTION_NAME_EVENT_SELECTION: msg = 'Event selection successfully finished.' elif self.name == SECTION_NAME_ERRORCORRECTION: msg = 'Error correction successfully finished.' elif self.name == SECTION_NAME_PROCESSING: msg = 'Processing successfully finished.' self.status_message.emit(msg) else: if self._is_abort: self.status_message.emit('Process aborted by user.') elif self._has_error: self.status_message.emit('Process aborted by error.') self._current_process = None self._process_items = None def _on_esc_pressed(self): if self._is_running: self._abort_processing() self._analyzer.image_ready.disconnect(self._on_update_image) def _on_update_stage_info(self, info): sep = ' | ' info = dict([(str(k), v) for k,v in info.iteritems()]) #print info if self.CONTROL == CONTROL_1: if info['stage'] == 0: self.process_control.setRange(info['min'], info['max']) if not info['progress'] is None: self.process_control.setProgress(info['progress']) msg = '' if 'meta' in info: msg += '%s' % info['meta'] if 'text' in info: msg += ' %s' % info['text'] if info['progress'] > info['min'] and 'interval' in info: interval = info['interval'] self._intervals.append(interval) avg = numpy.average(self._intervals) estimate = seconds2datetime(avg*float(info['max']-info['progress'])) msg += '%s~ %.1fs / %s%s%s remaining' % (sep, avg, info['item_name'], sep, estimate.strftime("%H:%M:%S")) else: self._intervals = [] self.status_message.emit(msg) else: self.process_control.clearText() else: self._stage_infos[info['stage']] = info if len(self._stage_infos) > 1: total = self._stage_infos[1]['max']*self._stage_infos[2]['max'] current = (self._stage_infos[1]['progress']-1)*self._stage_infos[2]['max']+self._stage_infos[2]['progress'] self.process_control.setRange(0, total) self.process_control.setProgress(current) sep = ' | ' msg = '%s %s%s%s' % (self._stage_infos[2]['meta'], self._stage_infos[1]['text'], sep, self._stage_infos[2]['text']) if current > 1 and ('interval' in info.keys()): interval = info['interval'] self._intervals.append(interval) estimate = seconds2datetime( numpy.average(self._intervals)*float(total-current)) msg += '%s%.1fs / %s%s%s remaining' % (sep, interval, self._stage_infos[2]['item_name'], sep, estimate.strftime("%H:%M:%S")) else: self._intervals = [] self.status_message.emit(msg) elif self.CONTROL == CONTROL_2: if info['stage'] == 1: if 'progress' in info: self._analyzer_progress1.setRange(info['min'], info['max']) self._analyzer_progress1.setValue(info['progress']) self._analyzer_label1.setText('%s (%d / %d)' % (info['text'], info['progress'], info['max'])) else: self._analyzer_label1.setText(info['text']) else: if 'progress' in info: self._analyzer_progress2.setRange(info['min'], info['max']) self._analyzer_progress2.setValue(info['progress']) self._analyzer_label2.setText('%s: %s (%d / %d)' % (info['text'], info['meta'], info['progress'], info['max'])) else: self._analyzer_label2.setText(info['text'])
class ClusterDisplay(QGroupBox): MIN_API_VERSION = 2 def __init__(self, parent, clusterframe, settings): super(ClusterDisplay, self).__init__(parent) self._settings = settings self._clusterframe = clusterframe self._imagecontainer = None self._jobid = None self._toggle_state = JOB_CONTROL_SUSPEND self._service = None self._host_url = None self.setTitle('ClusterControl') label1 = QLabel('Cluster URL:', self) fixed = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) expanding = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) label1.setSizePolicy(fixed) self._label_hosturl = QLabel('', self) self._label_hosturl.setSizePolicy(expanding) label3 = QLabel('Cluster status:', self) label3.setSizePolicy(fixed) self._label_status = QLabel('', self) self._label_status.setSizePolicy(expanding) label4 = QLabel('Path mappings:', self) label4.setSizePolicy(fixed) self._table_info = QTableWidget(self) self._table_info.setSelectionMode(QTableWidget.NoSelection) labels = ['Local', 'Remote'] self._table_info.setColumnCount(len(labels)) self._table_info.setHorizontalHeaderLabels(labels) self._table_info.setSizePolicy( QSizePolicy(QSizePolicy.Expanding|QSizePolicy.Maximum, QSizePolicy.MinimumExpanding)) layout = QGridLayout(self) layout.addWidget(label1, 0, 0, Qt.AlignRight) layout.addWidget(self._label_hosturl, 0, 1, 1, 4) layout.addWidget(label3, 1, 0, Qt.AlignRight) layout.addWidget(self._label_status, 1, 1, 1, 4) layout.addWidget(label4, 2, 0, Qt.AlignRight) layout.addWidget(self._table_info, 3, 0, 1, 5) self._btn_submit = QPushButton('Submit job', self) self._btn_submit.setEnabled(False) self._btn_submit.clicked.connect(self._on_submit_job) layout.addWidget(self._btn_submit, 5, 0, 1, 5) line = QFrame(self) line.setFrameShape(QFrame.HLine) layout.addWidget(line, 6, 0, 1, 5) label5 = QLabel('Current job ID:', self) layout.addWidget(label5, 7, 0, Qt.AlignRight) self._txt_jobid = QLineEdit(self._jobid or '', self) regexp = QRegExp('\d+') regexp.setPatternSyntax(QRegExp.RegExp2) self._txt_jobid.textEdited.connect(self._on_jobid_entered) self._txt_jobid.returnPressed.connect(self._on_update_job_status) layout.addWidget(self._txt_jobid, 7, 1) self._btn_update = QPushButton('Update', self) self._btn_update.clicked.connect(self._on_update_job_status) layout.addWidget(self._btn_update, 7, 2) self._btn_toogle = QPushButton(self._toggle_state, self) self._btn_toogle.clicked.connect(self._on_toggle_job) self._btn_toogle.setCheckable(True) layout.addWidget(self._btn_toogle, 7, 3) self._btn_terminate = QPushButton('Terminate', self) self._btn_terminate.clicked.connect(self._on_terminate_job) layout.addWidget(self._btn_terminate, 7, 4) label = QLabel('Job status:', self) layout.addWidget(label, 8, 0, Qt.AlignRight) self._label_jobstatus = QLabel('', self) layout.addWidget(self._label_jobstatus, 8, 1, 1, 4) layout.addItem(QSpacerItem(1, 1, QSizePolicy.MinimumExpanding, QSizePolicy.Expanding|QSizePolicy.Maximum), 10, 0, 1, 5) @property def jobIds(self): jids = self._txt_jobid.text() if not jids: return None else: return jids @jobIds.setter def jobIds(self, jobids): self._txt_jobid.setText(jobids) self._jobid = str(jobids) @property def imagecontainer(self): if self._imagecontainer is None: raise RuntimeError("Image container is not loaded yet") return self._imagecontainer @imagecontainer.deleter def imagecontainer(self): del self._imagecontainer @imagecontainer.setter def imagecontainer(self, imagecontainer): self._imagecontainer = imagecontainer def _on_jobid_entered(self, txt): self._jobid = str(txt) def clear_output_directory(self, directory): """Remove the content of the output directory except the structure file.""" for root, dirs, files in os.walk(directory, topdown=False): for name in files: if not name.endswith(".xml"): try: os.remove(os.path.join(root, name)) except OSError: pass for name in dirs: try: os.rmdir(os.path.join(root, name)) except OSError: pass @pyqtSlot() def _on_submit_job(self): self._submit_settings.set_section(SECTION_NAME_GENERAL) if not self._submit_settings.get2('constrain_positions'): positions = [] for plate_id in self.imagecontainer.plates: self.imagecontainer.set_plate(plate_id) meta_data = self.imagecontainer.get_meta_data() positions += ['%s___%s' % (plate_id, p) for p in meta_data.positions] self._submit_settings.set2('positions', ','.join(positions)) nr_items = len(positions) else: positions = self._submit_settings.get2('positions') nr_items = len(positions.split(',')) settings_dummy = self._clusterframe.get_special_settings(self._settings) apc = AppPreferences() batch_size = apc.batch_size pathout = self._submit_settings.get2('pathout') if not self._submit_settings('General', 'skip_finished'): self.clear_output_directory(self._settings("General", "pathout")) try: self.dlg = ProgressDialog("Submitting Jobs...", None, 0, 0, self) settings_str = self._submit_settings.to_string() func = lambda: self._service.submit_job('cecog_batch', settings_str, pathout, nr_items, batch_size, version) self.dlg.exec_(func) jobid = self.dlg.getTargetResult() except Exception as e: QMessageBox.critical( self, "Error", 'Job submission failed (%s)' %str(e)) else: # FIXME: no idea how DRMAA 1.0 compatible this is if type(jobid) == types.ListType: self._jobid = ','.join(jobid) main_jobid = jobid[0].split('.')[0] else: self._jobid = str(jobid) main_jobid = jobid self._txt_jobid.setText(self._jobid) self._update_job_status() QMessageBox.information( self, "Information", ("Job(s) successfully submitted\n" "Job ID: %s, #jobs: %d" % (main_jobid, nr_items))) @pyqtSlot() def _on_terminate_job(self): if self.jobIds is None: return try: self.dlg = ProgressDialog("Terminating Jobs...", None, 0, 0, self) func = lambda: self._service.control_job(self._jobid, JOB_CONTROL_TERMINATE) self.dlg.exec_(func) except Exception as e: QMessageBox.critical( self, "Error", "Job termination failed (%s)" %str(e)) else: self._btn_toogle.setChecked(False) self._toggle_state = JOB_CONTROL_SUSPEND self._btn_toogle.setText(self._toggle_state) self._update_job_status() @pyqtSlot() def _on_toggle_job(self): if self.jobIds is None: return try: self.dlg = ProgressDialog("Suspending Jobs...", None, 0, 0, self) func = lambda: self._service.control_job(self._jobid, self._toggle_state) self.dlg.exec_(func) except Exception as e: self._toggle_state = JOB_CONTROL_SUSPEND self._btn_toogle.setChecked(False) QMessageBox.critical( self, "Error", "Could not toggle job status (%s)" %str(e)) else: if self._toggle_state == JOB_CONTROL_SUSPEND: self._toggle_state = JOB_CONTROL_RESUME else: self._toggle_state = JOB_CONTROL_SUSPEND self._update_job_status() self._btn_toogle.setText(self._toggle_state) @pyqtSlot() def _on_update_job_status(self): txt = self._update_job_status() if txt is not None: QMessageBox.information(self, "Cluster update", "Message: '%s'" % txt) def _update_job_status(self): if self.jobIds is None: return try: self.dlg = ProgressDialog("Updating Job Status...", None, 0, 0, self) func = lambda: self._service.get_job_status(self._jobid) self.dlg.exec_(func) txt = self.dlg.getTargetResult() except Exception as e: QMessageBox.critical( self, "Error", 'Could not retrieve job status (%s)' %str(e)) else: self._label_jobstatus.setText(txt) return txt def _check_host_url(self): url = urlparse.urlparse(self._host_url) try: test_sock = socket.create_connection((url.hostname, url.port), timeout=1) test_sock.shutdown(2) test_sock.close() except: msg = "Connection failed (%s)" %self._host_url raise ConnectionError(msg) def _check_api_version(self): try: api_version = self._service.api_version() except RemotingError: # this call is not supported by older version of the # cluster service api_version = 1 if api_version < self.MIN_API_VERSION: msg = ("Api version of the cluster services is %d " "but version %d is required.") \ %(self.MIN_API_VERSION, api_version) raise RuntimeError(msg) def _turn_off_cluster_support(self): pref = AppPreferences() pref.cluster_support = False pref.saveSettings() def _connect(self): success = False try: self._check_host_url() client = RemotingService(self._host_url) self.dlg = ProgressDialog("Connecting to Cluster...", None, 0, 0, self) func = lambda: client.getService('clustercontrol') self.dlg.exec_(func) self._service = self.dlg.getTargetResult() self._check_api_version() except ConnectionError as e: msg = ("%s\nDo you want to turn off the cluster support?") %str(e) ret = QMessageBox.question( self, "Error", msg) if ret == QMessageBox.Yes: self._turn_off_cluster_support() except Exception as e: QMessageBox.critical(self, "Error", str(e)) else: try: self.dlg.exec_(self._service.get_cecog_versions) cluster_versions = self.dlg.getTargetResult() except Exception as e: QMessageBox.critical(self, "Error", str(e)) else: if not version in set(cluster_versions): QMessageBox.warning( self, "Warning", ("Cluster does not support %s %s" "Available versions: %s" %(appname, version, ', '.join(cluster_versions)))) else: success = True return success def _get_mappable_paths(self): """Get the paths/filenames which have to be mapped to run on a remote cluster. Whether an option is considered or not might depend on other values/switches, e.g. if classification is not needed there is no need to map the paths. """ # FIXME: should be done in a better way. results = [] targets = [(('General', 'pathin'), []), (('General', 'pathout'),[]), (('General', 'plate_layout'),[]), (('Classification', 'primary_classification_envpath'), [('Processing', 'primary_classification')]), (('Classification', 'secondary_classification_envpath'), [('General', 'process_secondary'), ('Processing', 'secondary_classification')]), (('Classification', 'tertiary_classification_envpath'), [('General', 'process_tertiary'), ('Processing', 'tertiary_classification')]), (('Classification', 'merged_classification_envpath'), [('General', 'process_merged'), ('Processing', 'merged_classification')]), ] targets.extend( [(('ObjectDetection', '%s_flat_field_correction_image_dir' % prefix), [('ObjectDetection', '%s_flat_field_correction' % prefix)]) for prefix in ['primary', 'secondary', 'tertiary']]) for info, const in targets: passed = reduce(lambda x,y: x and y, map(lambda z: self._settings.get(*z), const), True) if passed: results.append(info) return results def check_directories(self): """Check local and remote directories defined in the path mapping table for existence and setup the table view accordingly. """ ndirs = self._table_info.rowCount() remote_dirs = [self._table_info.item(i, 1).text() for i in xrange(ndirs)] remote_state = self._service.check_directory(remote_dirs) local_dirs = [self._table_info.item(i, 0).text() for i in xrange(ndirs)] local_state = [isdir(d) for d in local_dirs] states = [local_state, remote_state] for i in xrange(ndirs): for j in xrange(2): item = self._table_info.item(i, j) item.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled) if states[j][i]: item.setBackground(QBrush(QColor("green"))) else: if not item.text(): item.setText("Not available") item.setBackground(QBrush(QColor("red"))) def update_display(self, is_active): apc = AppPreferences() self._host_url = apc.url if self._connect(): can_submit = True try: self._submit_settings = self._clusterframe.get_special_settings( \ self._settings, self.imagecontainer.has_timelapse) except: self._submit_settings = self._clusterframe.get_special_settings(self._settings) self._label_hosturl.setText(self._host_url) self._label_status.setText(self._service.get_service_info()) mappable_paths = self._get_mappable_paths() self._table_info.clearContents() self._table_info.setRowCount(len(mappable_paths)) for i, info in enumerate(mappable_paths): value = self._settings.get(*info) mapped = apc.map2platform(value) self._submit_settings.set(info[0], info[1], mapped) if mapped is None: can_submit = False self._table_info.setItem(i, 0, QTableWidgetItem(value)) self._table_info.setItem(i, 1, QTableWidgetItem(mapped)) self.check_directories() self._table_info.resizeColumnsToContents() self._table_info.resizeRowsToContents() self._btn_submit.setEnabled(can_submit and is_active) else: self._btn_submit.setEnabled(False)
class ClusterDisplay(QGroupBox): MIN_API_VERSION = 2 def __init__(self, parent, clusterframe, settings): super(ClusterDisplay, self).__init__(parent) self._settings = settings self._clusterframe = clusterframe self._imagecontainer = None self._jobid = None self._toggle_state = JOB_CONTROL_SUSPEND self._service = None self._host_url = None self.setTitle('ClusterControl') label1 = QLabel('Cluster URL:', self) fixed = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) expanding = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) label1.setSizePolicy(fixed) self._label_hosturl = QLabel('', self) self._label_hosturl.setSizePolicy(expanding) label3 = QLabel('Cluster status:', self) label3.setSizePolicy(fixed) self._label_status = QLabel('', self) self._label_status.setSizePolicy(expanding) label4 = QLabel('Path mappings:', self) label4.setSizePolicy(fixed) self._table_info = QTableWidget(self) self._table_info.setSelectionMode(QTableWidget.NoSelection) labels = ['Local', 'Remote'] self._table_info.setColumnCount(len(labels)) self._table_info.setHorizontalHeaderLabels(labels) self._table_info.setSizePolicy( QSizePolicy(QSizePolicy.Expanding | QSizePolicy.Maximum, QSizePolicy.MinimumExpanding)) layout = QGridLayout(self) layout.addWidget(label1, 0, 0, Qt.AlignRight) layout.addWidget(self._label_hosturl, 0, 1, 1, 4) layout.addWidget(label3, 1, 0, Qt.AlignRight) layout.addWidget(self._label_status, 1, 1, 1, 4) layout.addWidget(label4, 2, 0, Qt.AlignRight) layout.addWidget(self._table_info, 3, 0, 1, 5) self._btn_submit = QPushButton('Submit job', self) self._btn_submit.setEnabled(False) self._btn_submit.clicked.connect(self._on_submit_job) layout.addWidget(self._btn_submit, 5, 0, 1, 5) line = QFrame(self) line.setFrameShape(QFrame.HLine) layout.addWidget(line, 6, 0, 1, 5) label5 = QLabel('Current job ID:', self) layout.addWidget(label5, 7, 0, Qt.AlignRight) self._txt_jobid = QLineEdit(self._jobid or '', self) regexp = QRegExp('\d+') regexp.setPatternSyntax(QRegExp.RegExp2) self._txt_jobid.textEdited.connect(self._on_jobid_entered) self._txt_jobid.returnPressed.connect(self._on_update_job_status) layout.addWidget(self._txt_jobid, 7, 1) self._btn_update = QPushButton('Update', self) self._btn_update.clicked.connect(self._on_update_job_status) layout.addWidget(self._btn_update, 7, 2) self._btn_toogle = QPushButton(self._toggle_state, self) self._btn_toogle.clicked.connect(self._on_toggle_job) self._btn_toogle.setCheckable(True) layout.addWidget(self._btn_toogle, 7, 3) self._btn_terminate = QPushButton('Terminate', self) self._btn_terminate.clicked.connect(self._on_terminate_job) layout.addWidget(self._btn_terminate, 7, 4) label = QLabel('Job status:', self) layout.addWidget(label, 8, 0, Qt.AlignRight) self._label_jobstatus = QLabel('', self) layout.addWidget(self._label_jobstatus, 8, 1, 1, 4) layout.addItem( QSpacerItem(1, 1, QSizePolicy.MinimumExpanding, QSizePolicy.Expanding | QSizePolicy.Maximum), 10, 0, 1, 5) @property def jobIds(self): jids = self._txt_jobid.text() if not jids: return None else: return jids @jobIds.setter def jobIds(self, jobids): self._txt_jobid.setText(jobids) self._jobid = str(jobids) @property def imagecontainer(self): if self._imagecontainer is None: raise RuntimeError("Image container is not loaded yet") return self._imagecontainer @imagecontainer.deleter def imagecontainer(self): del self._imagecontainer @imagecontainer.setter def imagecontainer(self, imagecontainer): self._imagecontainer = imagecontainer def _on_jobid_entered(self, txt): self._jobid = str(txt) @pyqtSlot() def _on_submit_job(self): self._submit_settings.set_section(SECTION_NAME_GENERAL) if not self._submit_settings.get2('constrain_positions'): positions = [] for plate_id in self.imagecontainer.plates: self.imagecontainer.set_plate(plate_id) meta_data = self.imagecontainer.get_meta_data() positions += [ '%s___%s' % (plate_id, p) for p in meta_data.positions ] self._submit_settings.set2('positions', ','.join(positions)) nr_items = len(positions) else: positions = self._submit_settings.get2('positions') nr_items = len(positions.split(',')) settings_dummy = self._clusterframe.get_special_settings( self._settings) apc = AppPreferences() batch_size = apc.batch_size pathout = self._submit_settings.get2('pathout') try: self.dlg = ProgressDialog("Submitting Jobs...", None, 0, 0, self) settings_str = self._submit_settings.to_string() func = lambda: self._service.submit_job( 'cecog_batch', settings_str, pathout, nr_items, batch_size, version) self.dlg.exec_(func) jobid = self.dlg.getTargetResult() except Exception as e: QMessageBox.critical(self, "Error", 'Job submission failed (%s)' % str(e)) else: # FIXME: no idea how DRMAA 1.0 compatible this is if type(jobid) == types.ListType: self._jobid = ','.join(jobid) main_jobid = jobid[0].split('.')[0] else: self._jobid = str(jobid) main_jobid = jobid self._txt_jobid.setText(self._jobid) self._update_job_status() QMessageBox.information(self, "Information", ("Job(s) successfully submitted\n" "Job ID: %s, #jobs: %d" % (main_jobid, nr_items))) @pyqtSlot() def _on_terminate_job(self): if self.jobIds is None: return try: self.dlg = ProgressDialog("Terminating Jobs...", None, 0, 0, self) func = lambda: self._service.control_job(self._jobid, JOB_CONTROL_TERMINATE) self.dlg.exec_(func) except Exception as e: QMessageBox.critical(self, "Error", "Job termination failed (%s)" % str(e)) else: self._btn_toogle.setChecked(False) self._toggle_state = JOB_CONTROL_SUSPEND self._btn_toogle.setText(self._toggle_state) self._update_job_status() @pyqtSlot() def _on_toggle_job(self): if self.jobIds is None: return try: self.dlg = ProgressDialog("Suspending Jobs...", None, 0, 0, self) func = lambda: self._service.control_job(self._jobid, self. _toggle_state) self.dlg.exec_(func) except Exception as e: self._toggle_state = JOB_CONTROL_SUSPEND self._btn_toogle.setChecked(False) QMessageBox.critical(self, "Error", "Could not toggle job status (%s)" % str(e)) else: if self._toggle_state == JOB_CONTROL_SUSPEND: self._toggle_state = JOB_CONTROL_RESUME else: self._toggle_state = JOB_CONTROL_SUSPEND self._update_job_status() self._btn_toogle.setText(self._toggle_state) @pyqtSlot() def _on_update_job_status(self): txt = self._update_job_status() if txt is not None: QMessageBox.information(self, "Cluster update", "Message: '%s'" % txt) def _update_job_status(self): if self.jobIds is None: return try: self.dlg = ProgressDialog("Updating Job Status...", None, 0, 0, self) func = lambda: self._service.get_job_status(self._jobid) self.dlg.exec_(func) txt = self.dlg.getTargetResult() except Exception as e: QMessageBox.critical(self, "Error", 'Could not retrieve job status (%s)' % str(e)) else: self._label_jobstatus.setText(txt) return txt def _check_host_url(self): url = urlparse.urlparse(self._host_url) try: test_sock = socket.create_connection((url.hostname, url.port), timeout=1) test_sock.shutdown(2) test_sock.close() except: msg = "Connection failed (%s)" % self._host_url raise ConnectionError(msg) def _check_api_version(self): try: api_version = self._service.api_version() except RemotingError: # this call is not supported by older version of the # cluster service api_version = 1 if api_version < self.MIN_API_VERSION: msg = ("Api version of the cluster services is %d " "but version %d is required.") \ %(self.MIN_API_VERSION, api_version) raise RuntimeError(msg) def _turn_off_cluster_support(self): pref = AppPreferences() pref.cluster_support = False pref.saveSettings() def _connect(self): success = False try: self._check_host_url() client = RemotingService(self._host_url) self.dlg = ProgressDialog("Connecting to Cluster...", None, 0, 0, self) func = lambda: client.getService('clustercontrol') self.dlg.exec_(func) self._service = self.dlg.getTargetResult() self._check_api_version() except ConnectionError as e: msg = ("%s\nDo you want to turn off the cluster support?") % str(e) ret = QMessageBox.question(self, "Error", msg) if ret == QMessageBox.Yes: self._turn_off_cluster_support() except Exception as e: QMessageBox.critical(self, "Error", str(e)) else: try: self.dlg.exec_(self._service.get_cecog_versions) cluster_versions = self.dlg.getTargetResult() except Exception as e: QMessageBox.critical(self, "Error", str(e)) else: if not version in set(cluster_versions): QMessageBox.warning( self, "Warning", ("Cluster does not support %s %s" "Available versions: %s" % (appname, version, ', '.join(cluster_versions)))) else: success = True return success def _get_mappable_paths(self): """Get the paths/filenames which have to be mapped to run on a remote cluster. Whether an option is considered or not might depend on other values/switches, e.g. if classification is not needed there is no need to map the paths. """ #FIXME: should be done in a better way. results = [] targets = [ (('General', 'pathin'), []), (('General', 'pathout'), []), (('General', 'structure_file_extra_path_name'), [('General', 'structure_file_extra_path')]), (('Classification', 'primary_classification_envpath'), [('Processing', 'primary_classification')]), (('Classification', 'secondary_classification_envpath'), [('General', 'process_secondary'), ('Processing', 'secondary_classification')]), (('Classification', 'tertiary_classification_envpath'), [('General', 'process_tertiary'), ('Processing', 'tertiary_classification')]), (('Classification', 'merged_classification_envpath'), [('General', 'process_merged'), ('Processing', 'merged_classification')]), ] targets.extend([(('ObjectDetection', '%s_flat_field_correction_image_dir' % prefix), [('ObjectDetection', '%s_flat_field_correction' % prefix)]) for prefix in ['primary', 'secondary', 'tertiary']]) for info, const in targets: passed = reduce(lambda x, y: x and y, map(lambda z: self._settings.get(*z), const), True) if passed: results.append(info) return results def check_directories(self): """Check local and remote directories defined in the path mapping table for existence and setup the table view accordingly. """ ndirs = self._table_info.rowCount() remote_dirs = [ self._table_info.item(i, 1).text() for i in xrange(ndirs) ] remote_state = self._service.check_directory(remote_dirs) local_dirs = [ self._table_info.item(i, 0).text() for i in xrange(ndirs) ] local_state = [isdir(d) for d in local_dirs] states = [local_state, remote_state] for i in xrange(ndirs): for j in xrange(2): item = self._table_info.item(i, j) item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) if states[j][i]: item.setBackground(QBrush(QColor("green"))) else: if not item.text(): item.setText("Not available") item.setBackground(QBrush(QColor("red"))) def update_display(self, is_active): apc = AppPreferences() self._host_url = apc.url if self._connect(): can_submit = True try: self._submit_settings = self._clusterframe.get_special_settings( \ self._settings, self.imagecontainer.has_timelapse) except: self._submit_settings = self._clusterframe.get_special_settings( self._settings) self._label_hosturl.setText(self._host_url) self._label_status.setText(self._service.get_service_info()) mappable_paths = self._get_mappable_paths() self._table_info.clearContents() self._table_info.setRowCount(len(mappable_paths)) for i, info in enumerate(mappable_paths): value = self._settings.get(*info) mapped = apc.map2platform(value) self._submit_settings.set(info[0], info[1], mapped) if mapped is None: can_submit = False self._table_info.setItem(i, 0, QTableWidgetItem(value)) self._table_info.setItem(i, 1, QTableWidgetItem(mapped)) self.check_directories() self._table_info.resizeColumnsToContents() self._table_info.resizeRowsToContents() self._btn_submit.setEnabled(can_submit and is_active) else: self._btn_submit.setEnabled(False)
class CecogAnalyzer(QtWidgets.QMainWindow): NAME_FILTERS = ['Settings files (*.conf)', 'All files (*.*)'] modified = QtCore.pyqtSignal('bool') def __init__(self, appname, version, redirect, settings=None, debug=False, *args, **kw): super(CecogAnalyzer, self).__init__(*args, **kw) self.setWindowTitle("%s-%s" % (appname, version) + '[*]') self.setAcceptDrops(True) self.setCentralWidget(QtWidgets.QFrame(self)) self.setObjectName(appname) self.version = version self.appname = appname self.debug = debug self.environ = CecogEnvironment(version=version, redirect=redirect, debug=debug) if debug: self.environ.pprint() self._is_initialized = False self._imagecontainer = None self._meta_data = None self._browser = None action_quit = self.create_action('&Quit', slot=self.close) action_pref = self.create_action('&Preferences', slot=self.open_preferences) action_load = self.create_action('&Load Settings...', shortcut=QtGui.QKeySequence.Open, slot=self._on_file_open) action_save = self.create_action('&Save Settings', shortcut=QtGui.QKeySequence.Save, slot=self._on_file_save) self.action_save = action_save action_save_as = self.create_action('&Save Settings As...', shortcut=QtGui.QKeySequence.SaveAs, slot=self._on_file_save_as) menu_file = self.menuBar().addMenu('&File') self.add_actions(menu_file, (action_pref, None, action_load, None, action_save, action_save_as, None, action_quit)) action_log = self.create_action('&Log window', shortcut=QtGui.QKeySequence(Qt.CTRL + Qt.Key_L), slot=self._on_show_log_window) action_open = self.create_action('&Browser', shortcut=QtGui.QKeySequence('CTRL+B'), slot=self._on_browser_open) menu_view = self.menuBar().addMenu('&View') self.add_actions(menu_view, (action_log, )) self.add_actions(menu_view, (action_open, )) action_assistant = self.create_action( '&Help', shortcut=QtGui.QKeySequence.HelpContents, slot=self.show_assistant) action_about = self.create_action('&About', slot=self.on_about) action_aboutQt = self.create_action('&About Qt', slot=self.about_qt) menu_help = self.menuBar().addMenu('&Help') self.add_actions(menu_help, (action_assistant, action_about, action_aboutQt)) self.setStatusBar(QtWidgets.QStatusBar(self)) self._selection = QtWidgets.QListWidget(self.centralWidget()) self._selection.setViewMode(QtWidgets.QListView.IconMode) self._selection.setIconSize(QtCore.QSize(35, 35)) self._selection.setGridSize(QtCore.QSize(140, 60)) self._selection.setMovement(QtWidgets.QListView.Static) self._selection.setMaximumWidth(self._selection.gridSize().width() + 5) self._selection.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self._selection.setSizePolicy( QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Expanding)) self._pages = FrameStack(self) self._settings_filename = None self._settings = GuiConfigSettings(self) self._tab_lookup = OrderedDict() self._tabs = [ GeneralFrame(self._settings, self._pages, SECTION_NAME_GENERAL), ObjectDetectionFrame(self._settings, self._pages, SECTION_NAME_OBJECTDETECTION), FeatureExtractionFrame(self._settings, self._pages, SECTION_NAME_FEATURE_EXTRACTION), ClassificationFrame(self._settings, self._pages, SECTION_NAME_CLASSIFICATION), TrackingFrame(self._settings, self._pages, SECTION_NAME_TRACKING), EventSelectionFrame(self._settings, self._pages, SECTION_NAME_EVENT_SELECTION), ErrorCorrectionFrame(self._settings, self._pages, SECTION_NAME_ERRORCORRECTION), OutputFrame(self._settings, self._pages, SECTION_NAME_OUTPUT), ProcessingFrame(self._settings, self._pages, SECTION_NAME_PROCESSING) ] # connections for the section frames self._tabs[3].connect_browser_btn(self._on_browser_open) for frame in self._tabs: frame.status_message.connect(self.statusBar().showMessage) app = AppPreferences() if app.cluster_support: clusterframe = ClusterFrame(self._settings, self._pages, SECTION_NAME_CLUSTER) clusterframe.set_imagecontainer(self._imagecontainer) self._tabs.append(clusterframe) try: self.updateStyleSheet(loadStyle(app.stylesheet)) except Exception as e: # proceed with no stylesheet traceback.print_exc() widths = [] for tab in self._tabs: size = self._add_page(tab) widths.append(size.width()) self.set_modules_active(state=False) self._pages.setMinimumWidth(max(widths) + 45) self._selection.currentItemChanged.connect(self._on_change_page) self._selection.setCurrentRow(0) w_logo = QtWidgets.QLabel(self.centralWidget()) w_logo.setPixmap(QtGui.QPixmap(':cecog_logo_w145')) layout = QtWidgets.QGridLayout(self.centralWidget()) layout.addWidget(self._selection, 0, 0) layout.addWidget(w_logo, 1, 0, Qt.AlignBottom | Qt.AlignHCenter) layout.addWidget(self._pages, 0, 1, 2, 1) layout.setContentsMargins(1, 1, 1, 1) self.setGeometry(0, 0, 1250, 800) self.setMinimumSize(QtCore.QSize(700, 600)) self._is_initialized = True self._restore_geometry() self.show() # finally load (demo) - settings if settings is None: self.load_settings(self.environ.demo_settings) elif os.path.isfile(settings): self.load_settings(settings) else: QMessageBox.warning(self, "Warning", "File (%s) does not exist" % settings) def dragEnterEvent(self, event): event.acceptProposedAction() def dragMoveEvent(self, event): event.acceptProposedAction() def dropEvent(self, event): mimeData = event.mimeData() if mimeData.hasUrls(): if len(mimeData.urls()) == 1: self.load_settings(fix_path(mimeData.urls()[0].path())) # self._on_load_input() event.acceptProposedAction() def dragLeaveEvent(self, event): event.accept() def _save_geometry(self): settings = QtCore.QSettings(version.organisation, version.appname) settings.beginGroup('Gui') settings.setValue('state', self.saveState()) settings.setValue('geometry', self.saveGeometry()) try: settings.setValue( 'clusterjobs', self._pages.widgetByType(ClusterFrame).get_jobids()) except KeyError: pass settings.endGroup() def _restore_geometry(self): settings = QtCore.QSettings(version.organisation, version.appname) settings.beginGroup('Gui') if settings.contains('geometry'): self.restoreGeometry(settings.value('geometry')) if settings.contains('state'): self.restoreState(settings.value('state')) if settings.contains('clusterjobs'): jobids = settings.value('clusterjobs') if AppPreferences().cluster_support and jobids: self._pages.widgetByType(ClusterFrame).restore_jobids(jobids) settings.endGroup() def closeEvent(self, event): self._pages.close() # Quit dialog only if not debuging flag is not set self._save_geometry() if self.debug: QtWidgets.QApplication.exit() ret = QMessageBox.question(self, "Quit %s" % self.appname, "Do you really want to quit?", QMessageBox.Yes | QMessageBox.No) if ret == QMessageBox.No: event.ignore() else: self._check_settings_saved(QMessageBox.Yes | QMessageBox.No) QtWidgets.QApplication.exit() def settings_changed(self, changed): if self._is_initialized: self.setWindowModified(changed) self.action_save.setEnabled(changed) self.modified.emit(changed) def _add_page(self, widget): button = QtWidgets.QListWidgetItem(self._selection) button.setIcon(QtGui.QIcon(widget.ICON)) button.setText(widget.get_name()) button.setTextAlignment(Qt.AlignHCenter) self._pages.addWidget(widget) widget.toggle_tabs.connect(self._on_toggle_tabs) self._tab_lookup[widget.get_name()] = (button, widget) return widget.size() def _on_toggle_tabs(self, name): """Toggle ItemIsEnabled flag for all list items but name.""" for name2 in self._tab_lookup: if name2 != name: item, _ = self._tab_lookup[name2] flags = item.flags() # check flag (and) if flags & Qt.ItemIsEnabled: # remove flag (nand) item.setFlags(flags & ~Qt.ItemIsEnabled) else: # set flag (or) item.setFlags(flags | Qt.ItemIsEnabled) def _on_change_page(self, current, previous): if not current: current = previous index = self._selection.row(current) self._pages.setCurrentIndex(index) widget = self._pages.widget(index) widget.page_changed() def _check_settings_saved(self, buttons=QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel): if self.isWindowModified(): result = QMessageBox.question(self, "Settings have been modified", "Do you want to save the settings?", buttons) if result == QMessageBox.Yes: self.save_settings() else: result = QMessageBox.No return result def add_actions(self, target, actions): for action in actions: if action is None: target.addSeparator() else: target.addAction(action) def create_action(self, text, slot=None, shortcut=None, icon=None, tooltip=None, checkable=None, signal='triggered', checked=False): action = QtWidgets.QAction(text, self) if icon is not None: action.setIcon(QtGui.QIcon(':/%s.png' % icon)) if shortcut is not None: action.setShortcut(shortcut) if tooltip is not None: action.setToolTip(tooltip) action.setStatusTip(tooltip) if slot is not None: getattr(action, signal).connect(slot) if checkable is not None: action.setCheckable(True) action.setChecked(checked) return action def save_settings(self, save_as=False): filename = self._settings_filename if filename is None or save_as: filename = self._get_save_as_filename() if not filename is None: self._write_settings(filename) def load_settings(self, filename): try: self._settings.read(filename) except Exception as e: QMessageBox.critical(self, "Error", ("Error loading settings file\n" "Could not load settings file '%s'.\n%s" % (filename, str(e)))) self.statusBar().showMessage('Error loading settings files.') else: self._settings_filename = filename title = self.windowTitle().split(' - ')[0] self.setWindowTitle('%s - %s[*]' % (title, filename)) try: # reset naming scheme to load config file completely nst = self._settings.get_trait("General", "namingscheme") namingscheme_file = self._settings("General", "namingscheme") if not namingscheme_file in nst.list_data: self._settings.set("General", "namingscheme", nst.default_value) QMessageBox.warning( self, "Unkown naming scheme", ("%s-%s can not use the naming scheme '%s'." " Resetting to default '%s'" % (version.appname, version.version, namingscheme_file, nst.default_value))) for widget in self._tabs: widget.update_input() except Exception as e: msg = "Could not load settings file (%s)\n.%s" \ %(filename, traceback.format_exc()) QMessageBox.critical(self, "Error", msg) else: # set settings to not-changed (assume no changed since loaded from file) self.settings_changed(False) # notify tabs about new settings loaded for tab in self._tabs: tab.settings_loaded() self.statusBar().showMessage('Settings successfully loaded.') def _write_settings(self, filename): try: f = file(filename, 'w') # create a new version (copy) of the current # settings which add the needed rendering information pframe = self._tab_lookup[SECTION_NAME_PROCESSING][1] settings_dummy = pframe.get_export_settings(self._settings) settings_dummy.write(f) f.close() except Exception as e: msg = "Could not save settings\n%s" % str(e) QMessageBox.critical(self, "Error", msg) self.statusBar().showMessage('Settings not successfully saved.') else: self._settings_filename = filename self.setWindowTitle('%s - %s[*]' % (self.appname, filename)) self.settings_changed(False) self.statusBar().showMessage('Settings successfully saved.') def on_about(self): dialog = CecogAboutDialog(self) dialog.show() def about_qt(self): QMessageBox.aboutQt(self, "about Qt") def open_preferences(self): pref = PreferencesDialog(self) pref.exec_() def updateStyleSheet(self, stylesheet): self.setStyleSheet("") self.setStyleSheet(stylesheet) self._pages.assistant.setStyleSheet("") self._pages.assistant.setStyleSheet(stylesheet) if self._browser is not None: self._browser.setStyleSheet("") self._browser.setStyleSheet(stylesheet) def _on_browser_open(self): if self._imagecontainer is None: QMessageBox.warning( self, 'Data structure not loaded', 'The input directory structure file was not loaded.\n' 'Click "Scan input directory" in section "General" to proceed.' ) elif self._browser is None: try: browser = Browser(self._settings, self._imagecontainer, None) browser.show() browser.raise_() browser.setFocus() app = AppPreferences() browser.setStyleSheet(loadStyle(app.stylesheet)) self._browser = browser except Exception as e: traceback.print_exc() QMessageBox.critical(self, "Error", str(e)) else: self._browser.show() self._browser.raise_() def _on_load_input(self): txt = "Error scanning image structure" path_in = self._settings.get(SECTION_NAME_GENERAL, 'pathin') if path_in == '': QMessageBox.critical(self, "Error", "%s\nImage path must be defined." % txt) elif not os.path.isdir(path_in) and \ not os.path.isdir(os.path.join(self.environ.package_dir, path_in)): QMessageBox.critical( self, "Error", "%s\nImage path '%s' not found." % (txt, path_in)) else: try: infos = list(ImageContainer.iter_check_plates(self._settings)) except Exception as e: QMessageBox.critical(self, "Error", "%s\n%s" % (txt, str(e))) else: found_any = numpy.any([not info[3] is None for info in infos]) cancel = False if found_any: found_plates = [ info[0] for info in infos if not info[3] is None ] missing_plates = [ info[0] for info in infos if info[3] is None ] has_missing = len(missing_plates) > 0 txt = '%s plates were already scanned.\nDo you want ' \ 'to rescan the file structure(s)? ' \ 'This can take several minutes.' % \ ('Some' if has_missing else 'All') title = 'Rescan input structure?' box = QMessageBox(QMessageBox.Question, title, title, QMessageBox.Cancel, self, Qt.Sheet) box.setWindowModality(Qt.WindowModal) box.setInformativeText(txt) box.setDetailedText( 'Plates with scanned structure: \n%s\n' '\nPlates without scanned structure: ' '\n%s' % ('\n'.join(found_plates), '\n'.join(missing_plates))) if not has_missing: btn1 = QtWidgets.QPushButton('No', box) box.addButton(btn1, QMessageBox.NoRole) box.setDefaultButton(btn1) elif len(found_plates) > 0: btn1 = QtWidgets.QPushButton('Rescan missing', box) box.addButton(btn1, QMessageBox.YesRole) box.setDefaultButton(btn1) else: btn1 = None btn2 = QtWidgets.QPushButton('Rescan all', box) box.addButton(btn2, QMessageBox.YesRole) if box.exec_() == QMessageBox.Cancel: cancel = True else: btn = box.clickedButton() if btn == btn1: if has_missing: scan_plates = dict([(info[0], info[0] in missing_plates) for info in infos]) else: scan_plates = dict( (info[0], False) for info in infos) else: scan_plates = dict( (info[0], True) for info in infos) else: has_multiple = self._settings.get(SECTION_NAME_GENERAL, "has_multiple_plates") ret = QMessageBox.question( self, "No structure data found", ("Scanning the input directory can be time " "consuming.\n\nDo you want to proceed?"), QMessageBox.Yes | QMessageBox.No) if ret == QMessageBox.No: cancel = True scan_plates = dict((info[0], True) for info in infos) if not cancel: self._load_image_container(infos, scan_plates) def _load_image_container(self, plate_infos=None, scan_plates=None, show_dialog=True): self._clear_browser() if plate_infos is None: plate_infos = list(ImageContainer.iter_check_plates( self._settings)) imagecontainer = ImageContainer() self._imagecontainer = imagecontainer if scan_plates is None: scan_plates = dict((info[0], False) for info in plate_infos) def load(emitter, icontainer, settings, splates): iter_ = icontainer.iter_import_from_settings(settings, scan_plates=splates) for idx, info in enumerate(iter_): emitter.setValue.emit(idx) emitter.setLabelText.emit("checking dimensions...") emitter.setRange.emit(0, 0) QtCore.QCoreApplication.processEvents() if len(icontainer.plates) > 0: icontainer.set_plate(icontainer.plates[0]) icontainer.check_dimensions() label = ('Please wait until the input structure is scanned\n' 'or the structure data loaded...') self._dlg = ProgressDialog(label, None, 0, len(scan_plates), self) emitter = ProgressObject() emitter.setRange.connect(self._dlg.setRange) emitter.setValue.connect(self._dlg.setValue) emitter.setLabelText.connect(self._dlg.setLabelText) try: func = lambda: load(emitter, imagecontainer, self._settings, scan_plates) self._dlg.exec_(func, (emitter, )) except ImportError as e: # structure file from versions older than 1.3 contain pdk which is # removed if 'pdk' in str(e): QMessageBox.critical( self, "Error", ("Your structure file format is outdated.\n" "You have to rescan the plate(s)")) else: QMessageBox.critical(self, "Error", traceback.format_exc()) return except Exception as e: QMessageBox.critical(self, "Error", str(e)) try: # I hate lookup tables! self._tab_lookup['Cluster'][1].set_imagecontainer(imagecontainer) except KeyError: pass if len(imagecontainer.plates) > 0: channels = imagecontainer.channels # do not report value changes to the main window self._settings.set_notify_change(False) self.set_image_crop_size() problems = [] for prefix in ['primary', 'secondary', 'tertiary']: trait = self._settings.get_trait(SECTION_NAME_OBJECTDETECTION, '%s_channelid' % prefix) if trait.set_list_data(channels) is None: problems.append(prefix) self._tabs[1].get_widget('%s_channelid' % prefix).update() # report problems about a mismatch between channel IDs found in the data # and specified by the user if len(problems) > 0: # a mismatch between settings and data will cause changed settings self.settings_changed(True) trait = self._settings.get_trait(SECTION_NAME_EVENT_SELECTION, 'duration_unit') # allow time-base tracking durations only if time-stamp # information is present meta_data = imagecontainer.get_meta_data() if meta_data.has_timestamp_info: result = trait.set_list_data(TimeConverter.units) else: result = trait.set_list_data([TimeConverter.FRAMES]) if result is None: QMessageBox.critical( self, "Could not set tracking duration units", ("The tracking duration units selected to match the " "load data. Please check your settings.")) # a mismatch between settings and data will cause changed settings self.settings_changed(True) # activate change notification again self._settings.set_notify_change(True) self.set_modules_active(state=True) if show_dialog: QMessageBox.information( self, "Information", "%d plate(s) successfully loaded." % len(imagecontainer.plates)) else: QMessageBox.critical( self, "Error", ("No images found\n" "Verifiy your nameing scheme and rescan the data.")) def set_image_crop_size(self): x0, y0, x1, y1 = self._settings.get('General', 'crop_image_x0'), \ self._settings.get('General', 'crop_image_y0'), \ self._settings.get('General', 'crop_image_x1'), \ self._settings.get('General', 'crop_image_y1') x0_, y0_, x1_, y1_ = 0, \ 0, \ self._imagecontainer.get_meta_data().dim_x, \ self._imagecontainer.get_meta_data().dim_y tr_x0 = self._settings.get_trait(SECTION_NAME_GENERAL, 'crop_image_x0') tr_y0 = self._settings.get_trait(SECTION_NAME_GENERAL, 'crop_image_y0') tr_x1 = self._settings.get_trait(SECTION_NAME_GENERAL, 'crop_image_x1') tr_y1 = self._settings.get_trait(SECTION_NAME_GENERAL, 'crop_image_y1') # Check if the crop values are valid if x0 > 0 and y0 > 0 and x1 <= x1_ and y1 <= y1_ and \ x0 != x1 and y0 != y1: # Set to default values tr_x0.set_value(tr_x0.get_widget(), x0) tr_y0.set_value(tr_y0.get_widget(), y0) tr_x1.set_value(tr_x1.get_widget(), x1) tr_y0.set_value(tr_y1.get_widget(), y1) else: tr_x0.set_value(tr_x0.get_widget(), x0_) tr_y0.set_value(tr_y0.get_widget(), y0_) tr_x1.set_value(tr_x1.get_widget(), x1_) tr_y0.set_value(tr_y1.get_widget(), y1_) # Set GUI widget valid ranges tr_x0.set_min_value(x0_) tr_x0.set_max_value(x1_) tr_y0.set_min_value(y0_) tr_y0.set_max_value(y1_) tr_x1.set_min_value(x0_) tr_x1.set_max_value(x1_) tr_y1.set_min_value(y0_) tr_y1.set_max_value(y1_) def set_modules_active(self, state=True): for name, (button, widget) in self._tab_lookup.iteritems(): widget.set_active(state) @QtCore.pyqtSlot() def _on_file_open(self): dir_ = os.path.dirname(self._settings_filename) if self._check_settings_saved() != QMessageBox.Cancel: if self._settings_filename is not None: settings_filename = self.environ.demo_settings if os.path.isfile(settings_filename): home = settings_filename filename = QtWidgets.QFileDialog.getOpenFileName( \ self, 'Open config file', dir_, ';;'.join(self.NAME_FILTERS))[0] if not bool(filename): return try: self.load_settings(filename) if self._settings.was_old_file_format(): QMessageBox.information( self, 'Information', 'Config file was updated to version %s' % self.version) except Exception as e: msg = "File could not be loaded\n%s" % str(e) QMessageBox.critical(self, "Error", msg) finally: self._clear_browser() self.set_modules_active(state=False) def _on_file_save(self): self.save_settings(False) def _on_file_save_as(self): self.save_settings(True) def _clear_browser(self): if not self._browser is None: self._browser.close() self._browser = None def _on_show_log_window(self): self._pages.showLogWindow() def _get_save_as_filename(self): dir = "" if self._settings_filename is not None: settings_filename = self.environ.demo_settings if os.path.isfile(settings_filename): dir = settings_filename filename = QtWidgets.QFileDialog.getSaveFileName( self, 'Save config file as', dir, ';;'.join(self.NAME_FILTERS))[0] return filename or None def show_assistant(self): self._pages.assistant.show() self._pages.assistant.openKeyword('index')
class BaseProcessorFrame(BaseFrame): def __init__(self, settings, parent, name): super(BaseProcessorFrame, self).__init__(settings, parent, name) self.idialog = parent.idialog self._is_running = False self._is_abort = False self._has_error = True self._current_process = None self._image_combo = None self._stage_infos = {} self._process_items = None self._control_buttons = OrderedDict() shortcut = QShortcut(QKeySequence(Qt.Key_Escape), self) shortcut.activated.connect(self._on_esc_pressed) def set_active(self, state): # set intern state and enable/disable control buttons super(BaseProcessorFrame, self).set_active(state) self.process_control.setButtonsEnabled(state) def _on_update_image(self, images, message): if self.process_control.showImages(): self.idialog.updateImages(images, message) if not self.idialog.isVisible(): self.idialog.raise_() def register_process(self, name): pass def register_control_button(self, name, cls, labels): self._control_buttons[name] = {'labels': labels, 'cls': cls} def _init_control(self, has_images=True): if not has_images: self.process_control.hideImageCheckBox() for name in self._control_buttons: slot = lambda x: lambda: self._on_process_start(x) self.process_control.addControlButton(name, slot(name)) if not self.TABS is None: self._tab.currentChanged.connect(self._on_tab_changed) self._on_tab_changed(0) else: for name in self._control_buttons: self._set_control_button_text(name=name) def _set_control_button_text(self, name=None, idx=0): if name is None: name = self._current_process try: text = self._control_buttons[name]['labels'][idx] % self._tab_name except: text = self._control_buttons[name]['labels'][idx] self.process_control.buttonByName(name).setText(text) def _toggle_control_buttons(self, name=None): if name is None: name = self._current_process for name2 in self._control_buttons: if name != name2: btn = self.process_control.buttonByName(name) btn.setEnabled(not btn.isEnabled()) @classmethod def get_special_settings(cls, settings, has_timelapse=True): settings = settings.copy() return settings def _get_modified_settings(self, name, has_timelapse): return self.get_special_settings(self._settings, has_timelapse) def _on_tab_changed(self, idx): self._tab_name = CHANNEL_PREFIX[idx] for name in self._control_buttons: self._set_control_button_text(name=name) def _clear_image(self): """Pop up and clear the image display""" self.idialog.clearImage() def _on_process_start(self, name, start_again=False): if not self._is_running or start_again: is_valid = True self._is_abort = False self._has_error = False if self._process_items is None: cls = self._control_buttons[name]['cls'] if type(cls) == types.ListType: self._process_items = cls self._current_process_item = 0 cls = cls[self._current_process_item] else: self._process_items = None self._current_process_item = 0 else: cls = self._process_items[self._current_process_item] if self.name == SECTION_NAME_CLASSIFICATION: result_frame = self._get_result_frame(self._tab_name) result_frame.load_classifier() if name == self.Training: is_valid = True if result_frame.classifier_exists(): ret = QMessageBox.question( self, 'Trained Classifier found', 'Do you want to owerwrite the already ' 'trained classifier?') if ret == QMessageBox.No: is_valid = False elif name == self.Testing and not result_frame.classifier_exists( ): is_valid = False QMessageBox.critical(self, "Error", "Please train the classifier first") if is_valid: self._current_process = name if not start_again: self.parent().log_window.clear() self._is_running = True self._stage_infos = {} self._toggle_tabs(False) # disable all section button of the main widget self.toggle_tabs.emit(self.get_name()) self._set_control_button_text(idx=1) self.process_control.toggleButtons(self._current_process) imagecontainer = self.parent().main_window._imagecontainer if cls is TrainerThread: self._current_settings = self._get_modified_settings( name, imagecontainer.has_timelapse) self._analyzer = cls(self, self._current_settings, imagecontainer) self._clear_image() elif cls is AnalyzerThread: self._current_settings = self._get_modified_settings( name, imagecontainer.has_timelapse) self._analyzer = cls(self, self._current_settings, imagecontainer) self._clear_image() elif cls is MultiAnalyzerThread: self._current_settings = self._get_modified_settings( name, imagecontainer.has_timelapse) self._analyzer = cls(self, self._current_settings, imagecontainer) elif cls is ErrorCorrectionThread: self._current_settings = self._get_modified_settings( name, imagecontainer.has_timelapse) self._analyzer = cls( self, self._current_settings, self.parent().main_window._imagecontainer) self._analyzer.finished.connect(self._on_process_finished) self._analyzer.status.connect(self._on_update_stage_info, Qt.QueuedConnection) self._analyzer.error.connect(self._on_error, Qt.QueuedConnection) self._analyzer.increment.connect( self.process_control.increment) self._analyzer.image_ready.connect(self._on_update_image) self._analyzer.start(QThread.LowestPriority) if self._current_process_item == 0: self.status_message.emit('Process started...') else: self._abort_processing() def _toggle_tabs(self, state): if not self.TABS is None: self._tab.enable_non_active(state) def _abort_processing(self): self.setCursor(Qt.BusyCursor) self._is_abort = True self.dlg = ProgressDialog('terminating...', None, 0, 0, self) self.dlg.exec_(lambda: self._analyzer.abort(wait=True)) self.setCursor(Qt.ArrowCursor) def _on_error(self, msg, short='Error'): self._has_error = True QMessageBox.critical(self, short, msg) def _on_process_finished(self): self._analyzer.image_ready.disconnect(self._on_update_image) self.process_control.reset() if (not self._process_items is None and self._current_process_item + 1 < len(self._process_items) and not self._is_abort and not self._has_error): self._current_process_item += 1 self._on_process_start(self._current_process, start_again=True) else: self._is_running = False self._set_control_button_text(idx=0) self.process_control.toggleButtons(self._current_process) self._toggle_tabs(True) # enable all section button of the main widget self.toggle_tabs.emit(self.get_name()) msg = 'Processing successfully finished' if not self._is_abort and not self._has_error: if self.name == SECTION_NAME_OBJECTDETECTION: msg = 'Object detection successfully finished.' elif self.name == SECTION_NAME_TRACKING: msg = 'Tracking successfully finished.' elif self.name == SECTION_NAME_EVENT_SELECTION: msg = 'Event selection successfully finished.' elif self.name == SECTION_NAME_PROCESSING: msg = 'Processing successfully finished.' self.status_message.emit(msg) QMessageBox.information(self, "Finished", msg) else: if self._is_abort: self.status_message.emit('Process aborted by user.') elif self._has_error: self.status_message.emit('Process aborted by error.') self._current_process = None self._process_items = None def _on_esc_pressed(self): if self._is_running: self._abort_processing() self._analyzer.image_ready.disconnect(self._on_update_image) def _on_update_stage_info(self, info): sep = ' | ' info = dict([(str(k), v) for k, v in info.iteritems()]) self.process_control.setRange(info['min'], info['max']) if info['progress'] is not None: self.process_control.setProgress(info['progress']) msg = '' if 'meta' in info: msg += '%s' % info['meta'] if 'text' in info: msg += ' %s' % info['text'] if info['interval'] is not None: prg = self.process_control.progress() max_ = self.process_control.maximum() self._intervals.append(info["interval"]) avg = numpy.average(self._intervals) estimate = seconds2datetime(avg * float(max_ - prg)) msg += '%s~ %.1fs %s%s remaining' \ % (sep, avg, sep, estimate.strftime("%H:%M:%S")) else: self._intervals = [] self.status_message.emit(msg)
class BaseProcessorFrame(BaseFrame): def __init__(self, settings, parent, name): super(BaseProcessorFrame, self).__init__(settings, parent, name) self.idialog = parent.idialog self._is_running = False self._is_abort = False self._has_error = True self._current_process = None self._image_combo = None self._stage_infos = {} self._process_items = None self._control_buttons = OrderedDict() shortcut = QShortcut(QKeySequence(Qt.Key_Escape), self) shortcut.activated.connect(self._on_esc_pressed) def set_active(self, state): # set intern state and enable/disable control buttons super(BaseProcessorFrame, self).set_active(state) self.process_control.setButtonsEnabled(state) def _on_update_image(self, images, message): if self.process_control.showImages(): self.idialog.updateImages(images, message) if not self.idialog.isVisible(): self.idialog.raise_() def register_process(self, name): pass def register_control_button(self, name, cls, labels): self._control_buttons[name] = {'labels': labels, 'cls' : cls} def _init_control(self, has_images=True): if not has_images: self.process_control.hideImageCheckBox() for name in self._control_buttons: slot = lambda x: lambda : self._on_process_start(x) self.process_control.addControlButton(name, slot(name)) if not self.TABS is None: self._tab.currentChanged.connect(self._on_tab_changed) self._on_tab_changed(0) else: for name in self._control_buttons: self._set_control_button_text(name=name) def _set_control_button_text(self, name=None, idx=0): if name is None: name = self._current_process try: text = self._control_buttons[name]['labels'][idx] % self._tab_name except: text = self._control_buttons[name]['labels'][idx] self.process_control.buttonByName(name).setText(text) def _toggle_control_buttons(self, name=None): if name is None: name = self._current_process for name2 in self._control_buttons: if name != name2: btn = self.process_control.buttonByName(name) btn.setEnabled(not btn.isEnabled()) @classmethod def get_special_settings(cls, settings, has_timelapse=True): settings = settings.copy() return settings def _get_modified_settings(self, name, has_timelapse): return self.get_special_settings(self._settings, has_timelapse) def _on_tab_changed(self, idx): self._tab_name = CHANNEL_PREFIX[idx] for name in self._control_buttons: self._set_control_button_text(name=name) def _clear_image(self): """Pop up and clear the image display""" self.idialog.clearImage() def _on_process_start(self, name, start_again=False): if not self._is_running or start_again: is_valid = True self._is_abort = False self._has_error = False if self._process_items is None: cls = self._control_buttons[name]['cls'] if type(cls) == types.ListType: self._process_items = cls self._current_process_item = 0 cls = cls[self._current_process_item] else: self._process_items = None self._current_process_item = 0 else: cls = self._process_items[self._current_process_item] if self.name == SECTION_NAME_CLASSIFICATION: result_frame = self._get_result_frame(self._tab_name) result_frame.load_classifier() if name == self.Training: is_valid = True if result_frame.classifier_exists(): ret = QMessageBox.question(self,'Trained Classifier found', 'Do you want to owerwrite the already ' 'trained classifier?') if ret == QMessageBox.No: is_valid = False elif name == self.Testing and not result_frame.classifier_exists(): is_valid = False QMessageBox.critical(self, "Error", "Please train the classifier first") if is_valid: self._current_process = name if not start_again: self.parent().log_window.clear() self._is_running = True self._stage_infos = {} self._toggle_tabs(False) # disable all section button of the main widget self.toggle_tabs.emit(self.get_name()) self._set_control_button_text(idx=1) self.process_control.toggleButtons(self._current_process) imagecontainer = self.parent().main_window._imagecontainer if cls is TrainerThread: self._current_settings = self._get_modified_settings( name, imagecontainer.has_timelapse) self._analyzer = cls( self, self._current_settings, imagecontainer) self._clear_image() elif cls is AnalyzerThread: self._current_settings = self._get_modified_settings( name, imagecontainer.has_timelapse) self._analyzer = cls( self, self._current_settings, imagecontainer) self._clear_image() elif cls is MultiAnalyzerThread: self._current_settings = self._get_modified_settings( name, imagecontainer.has_timelapse) self._analyzer = cls( self, self._current_settings, imagecontainer) elif cls is ErrorCorrectionThread: self._current_settings = self._get_modified_settings( name, imagecontainer.has_timelapse) self._analyzer = cls( self, self._current_settings, self.parent().main_window._imagecontainer) self._analyzer.finished.connect(self._on_process_finished) self._analyzer.status.connect( self._on_update_stage_info, Qt.QueuedConnection) self._analyzer.error.connect(self._on_error, Qt.QueuedConnection) self._analyzer.increment.connect(self.process_control.increment) self._analyzer.image_ready.connect(self._on_update_image) self._analyzer.start(QThread.LowestPriority) if self._current_process_item == 0: self.status_message.emit('Process started...') else: self._abort_processing() def _toggle_tabs(self, state): if not self.TABS is None: self._tab.enable_non_active(state) def _abort_processing(self): self.setCursor(Qt.BusyCursor) self._is_abort = True self.dlg = ProgressDialog('terminating...', None, 0, 0, self) self.dlg.exec_(lambda: self._analyzer.abort(wait=True)) self.setCursor(Qt.ArrowCursor) def _on_error(self, msg, short='Error'): self._has_error = True QMessageBox.critical(self, short, msg) def _on_process_finished(self): self._analyzer.image_ready.disconnect(self._on_update_image) self.process_control.reset() if (not self._process_items is None and self._current_process_item+1 < len(self._process_items) and not self._is_abort and not self._has_error): self._current_process_item += 1 self._on_process_start(self._current_process, start_again=True) else: self._is_running = False self._set_control_button_text(idx=0) self.process_control.toggleButtons(self._current_process) self._toggle_tabs(True) # enable all section button of the main widget self.toggle_tabs.emit(self.get_name()) msg = 'Processing successfully finished' if not self._is_abort and not self._has_error: if self.name == SECTION_NAME_OBJECTDETECTION: msg = 'Object detection successfully finished.' elif self.name == SECTION_NAME_TRACKING: msg = 'Tracking successfully finished.' elif self.name == SECTION_NAME_EVENT_SELECTION: msg = 'Event selection successfully finished.' elif self.name == SECTION_NAME_PROCESSING: msg = 'Processing successfully finished.' self.status_message.emit(msg) QMessageBox.information(self, "Finished", msg) else: if self._is_abort: self.status_message.emit('Process aborted by user.') elif self._has_error: self.status_message.emit('Process aborted by error.') self._current_process = None self._process_items = None def _on_esc_pressed(self): if self._is_running: self._abort_processing() self._analyzer.image_ready.disconnect(self._on_update_image) def _on_update_stage_info(self, info): sep = ' | ' info = dict([(str(k), v) for k, v in info.iteritems()]) self.process_control.setRange(info['min'], info['max']) if info['progress'] is not None: self.process_control.setProgress(info['progress']) msg = '' if 'meta' in info: msg += '%s' % info['meta'] if 'text' in info: msg += ' %s' % info['text'] if info['interval'] is not None: prg = self.process_control.progress() max_ = self.process_control.maximum() self._intervals.append(info["interval"]) avg = numpy.average(self._intervals) estimate = seconds2datetime(avg*float(max_-prg)) msg += '%s~ %.1fs %s%s remaining' \ % (sep, avg, sep, estimate.strftime("%H:%M:%S")) else: self._intervals = [] self.status_message.emit(msg)
class NavigationModule(Module): NAME = 'Navigation' coordinate_changed = pyqtSignal(Coordinate) def __init__(self, parent, browser, imagecontainer): Module.__init__(self, parent, browser) self._imagecontainer = imagecontainer frame_info = QGroupBox('Plate Information', self) layout = QGridLayout(frame_info) frame_info.setStyleSheet('QLabel { font-size: 10px }') self._label_info = QLabel(frame_info) layout.addWidget(self._label_info, 0, 0, 0, 0, Qt.AlignCenter | Qt.AlignHCenter) splitter = QSplitter(Qt.Vertical, self) splitter.setMinimumWidth(40) layout = QBoxLayout(QBoxLayout.TopToBottom, self) layout.setContentsMargins(5, 5, 5, 5) layout.addWidget(splitter) grp1 = QGroupBox('Plates', splitter) grp2 = QGroupBox('Positions', splitter) splitter.addWidget(grp1) splitter.addWidget(grp2) layout = QGridLayout(grp1) layout.setContentsMargins(5, 10, 5, 5) table = QTableWidget(grp1) table.setEditTriggers(QTableWidget.NoEditTriggers) table.setSelectionMode(QTableWidget.SingleSelection) table.setSelectionBehavior(QTableWidget.SelectRows) table.setAlternatingRowColors(True) table.setStyleSheet('font-size: 10px;') table.currentItemChanged.connect(self._on_plate_changed) self._table_plate = table layout.addWidget(table, 0, 0) self._update_plate_table() layout = QGridLayout(grp2) layout.setContentsMargins(5, 10, 5, 5) table = QTableWidget(grp2) table.setEditTriggers(QTableWidget.NoEditTriggers) table.setSelectionMode(QTableWidget.SingleSelection) table.setSelectionBehavior(QTableWidget.SelectRows) table.setAlternatingRowColors(True) table.setStyleSheet('font-size: 10px;') table.currentItemChanged.connect(self._on_position_changed) self._table_position = table layout.addWidget(table, 0, 0) if self._imagecontainer.has_timelapse: grp3 = QGroupBox('Time', splitter) splitter.addWidget(grp3) layout = QGridLayout(grp3) layout.setContentsMargins(5, 10, 5, 5) table = QTableWidget(grp3) table.setEditTriggers(QTableWidget.NoEditTriggers) table.setSelectionMode(QTableWidget.SingleSelection) table.setSelectionBehavior(QTableWidget.SelectRows) table.setAlternatingRowColors(True) table.setStyleSheet('font-size: 10px;') table.currentItemChanged.connect(self._on_time_changed) self._table_time = table layout.addWidget(table, 0, 0) splitter.addWidget(frame_info) def _update_plate_table(self): table = self._table_plate table.blockSignals(True) table.clearContents() column_names = ['Plate ID'] table.setColumnCount(len(column_names)) table.setHorizontalHeaderLabels(column_names) plates = self._imagecontainer.plates table.setRowCount(len(plates)) for idx, plate in enumerate(plates): item = QTableWidgetItem(plate) item.setData(0, plate) table.setItem(idx, 0, item) table.resizeColumnsToContents() table.resizeRowsToContents() table.blockSignals(False) def _update_time_table(self, meta_data, coordinate): coordinate = coordinate.copy() table = self._table_time table.blockSignals(True) table.clearContents() column_names = ['Frame'] if meta_data.has_timestamp_info: column_names += ['rel. t (min)', 'abs. t (GMT)'] table.setColumnCount(len(column_names)) table.setHorizontalHeaderLabels(column_names) table.setRowCount(len(meta_data.times)) for idx, time in enumerate(meta_data.times): item = QTableWidgetItem(str(time)) item.setData(0, time) table.setItem(idx, 0, item) if meta_data.has_timestamp_info: coordinate.time = time ts_rel = meta_data.get_timestamp_relative(coordinate) ts_abs = meta_data.get_timestamp_absolute(coordinate) if not numpy.isnan(ts_rel): info = '%.1f' % (ts_rel / 60) table.setItem(idx, 1, QTableWidgetItem(info)) if not numpy.isnan(ts_abs): info = time_lib.strftime("%Y-%m-%d %H:%M:%S", time_lib.gmtime(ts_abs)) table.setItem(idx, 2, QTableWidgetItem(info)) table.resizeColumnsToContents() table.resizeRowsToContents() table.blockSignals(False) def _update_position_table(self, meta_data): table = self._table_position table.blockSignals(True) table.clearContents() column_names = ['Position'] if meta_data.has_well_info: column_names += ['Well', 'Subwell'] if meta_data.has_condition_info: column_names.append('Condition') if meta_data.has_timestamp_info and meta_data.has_timelapse: column_names.append('Time-lapse') table.setColumnCount(len(column_names)) table.setHorizontalHeaderLabels(column_names) table.setRowCount(len(meta_data.positions)) for idx, pos in enumerate(meta_data.positions): item = QTableWidgetItem(pos) item.setData(0, pos) table.setItem(idx, 0, item) if 'Time-lapse' in column_names: column = column_names.index('Time-lapse') info = meta_data.get_timestamp_info(pos) info_str = '%.1fmin (%.1fs)' % (info[0] / 60, info[1]) table.setItem(idx, column, QTableWidgetItem(info_str)) if 'Well' in column_names: column = column_names.index('Well') well, subwell = meta_data.get_well_and_subwell(pos) if not well is None: table.setItem(idx, column, QTableWidgetItem(well)) if not subwell is None: table.setItem(idx, column + 1, QTableWidgetItem("%02d" % subwell)) table.resizeColumnsToContents() table.resizeRowsToContents() table.blockSignals(False) def _update_info_frame(self, meta): txt = '<table>' \ '<tr><td align="right">Positions: </td><td>%s</td></tr>' \ '<tr><td align="right">Frames: </td><td>%d</td></tr>' % \ (meta.dim_p, meta.dim_t) txt += '<tr><td align="right">Channels: </td><td>%d (%s)</td></tr>' \ '<tr><td align="right">Z-slices: </td><td>%d</td></tr>' \ '<tr><td align="right">Width / Height: </td><td>%d x %d</td></tr>' \ '<tr><td colspan="2"></td></tr>' \ '<tr><td align="right">Image Files: </td><td>%d</td></tr>' % \ (meta.dim_c, meta.pixel_type, meta.dim_z, meta.dim_x, meta.dim_y, meta.image_files) txt += '<tr><td></td></tr>' if meta.has_timestamp_info and meta.has_timelapse: info = meta.plate_timestamp_info txt += \ '<tr><td align="right">Time-lapse info: </td><td>%.1f min (+/- %.1f s)</td></tr>' % \ (info[0]/60, info[1]) else: txt += '<tr><td align="right">Time-lapse info: </td><td>no</td></tr>' txt += '<tr><td align="right">Well info: </td><td>%s</td></tr>' % \ yesno(meta.has_well_info) txt += '<tr><td align="right">Condition info: </td><td>%s</td></tr>' % \ yesno(meta.has_condition_info) txt += '</table>' self._label_info.setText(txt) def initialize(self): self.coordinate_changed.connect(self.browser.on_coordinate_changed) coordinate = self.browser.get_coordinate() meta_data = self._imagecontainer.get_meta_data() self._update_position_table(meta_data) self._update_info_frame(meta_data) self._set_current_plate(coordinate.plate) self._set_current_position(coordinate.position) if self._imagecontainer.has_timelapse: self._update_time_table(meta_data, coordinate) self._set_current_time(coordinate.time) def nav_to_coordinate(self, coordinate): """ Set the browser coordinate to a coordinate and emit signal """ self._imagecontainer.set_plate(coordinate.plate) meta_data = self._imagecontainer.get_meta_data() self._set_current_plate(coordinate.plate) self._update_position_table(meta_data) self._set_current_position(coordinate.position) if self._imagecontainer.has_timelapse: self._update_time_table(meta_data, coordinate) self._set_current_time(coordinate.time) self._update_info_frame(meta_data) self.coordinate_changed.emit(coordinate) def nav_to_time(self, time): """ Set the browser coordinate to a coordinate and emit signal """ coordinate = self.browser.get_coordinate() coordinate.time = time self._set_time(coordinate, True) def nav_to_prev_position(self): coordinate = self.browser.get_coordinate() meta_data = self._imagecontainer.get_meta_data() pos = meta_data.positions idx = pos.index(coordinate.position) if idx > 0: coordinate.position = pos[idx - 1] self._set_position(coordinate, True) def nav_to_next_position(self): coordinate = self.browser.get_coordinate() meta_data = self._imagecontainer.get_meta_data() pos = meta_data.positions idx = pos.index(coordinate.position) if idx < len(pos) - 1: coordinate.position = pos[idx + 1] self._set_position(coordinate, True) def nav_to_prev_plate(self): coordinate = self.browser.get_coordinate() plates = self._imagecontainer.plates idx = plates.index(coordinate.plate) if idx > 0: coordinate.plate = plates[idx - 1] self._set_plate(coordinate, True) def nav_to_next_plate(self): coordinate = self.browser.get_coordinate() plates = self._imagecontainer.plates idx = plates.index(coordinate.plate) if idx < len(plates) - 1: coordinate.plate = plates[idx + 1] self._set_plate(coordinate, True) def _get_closeby_position(self, coordinate_old, coordinate_new): md_new = self._imagecontainer.get_meta_data() if coordinate_old.position in md_new.positions: coordinate_new.position = coordinate_old.position else: coordinate_new.position = md_new.positions[0] def _get_closeby_time(self, coordinate_old, coordinate_new): md_new = self._imagecontainer.get_meta_data() if coordinate_old.time in md_new.times: coordinate_new.time = coordinate_old.time else: coordinate_new.time = md_new.times[0] def _on_plate_changed(self, current, previous): coordinate_new = self.browser.get_coordinate() item = self._table_plate.item(current.row(), 0) plate = item.data(0) coordinate_new.plate = plate self._set_plate(coordinate_new) def _set_plate(self, coordinate_new, set_current=False): coordinate_old = self.browser.get_coordinate() plate = coordinate_new.plate func = lambda: self._imagecontainer.set_plate(plate) self.dlg = ProgressDialog("Loading plate...", None, 0, 0, self) self.dlg.exec_(func) meta_data = self._imagecontainer.get_meta_data() if set_current: self._set_current_plate(plate) self._update_position_table(meta_data) self._get_closeby_position(coordinate_old, coordinate_new) self._set_current_position(coordinate_new.position) if self._imagecontainer.has_timelapse: self._update_time_table(meta_data, coordinate_new) self._get_closeby_time(coordinate_old, coordinate_new) self._set_current_time(coordinate_new.time) self._update_info_frame(meta_data) self.coordinate_changed.emit(coordinate_new) def _on_position_changed(self, current, previous): coordinate = self.browser.get_coordinate() item = self._table_position.item(current.row(), 0) position = item.data(0) coordinate.position = position self._set_position(coordinate) def _set_position(self, coordinate, set_current=False): if set_current: self._set_current_position(coordinate.position) if self._imagecontainer.has_timelapse: meta_data = self._imagecontainer.get_meta_data() self._update_time_table(meta_data, coordinate) self._set_current_time(coordinate.time) self.coordinate_changed.emit(coordinate) def _on_time_changed(self, current, previous): coordinate = self.browser.get_coordinate() item = self._table_time.item(current.row(), 0) time = int(item.data(0)) coordinate.time = time self._set_time(coordinate) def _set_time(self, coordinate, set_current=False): if set_current: self._set_current_time(coordinate.time) self.coordinate_changed.emit(coordinate) def _set_current_plate(self, plate): self._table_plate.blockSignals(True) item = self._table_plate.findItems(plate, Qt.MatchExactly)[0] self._table_plate.setCurrentItem(item) self._table_plate.blockSignals(False) self._table_plate.scrollToItem(item) self._table_plate.update() def _set_current_position(self, position): self._table_position.blockSignals(True) item = self._table_position.findItems(position, Qt.MatchExactly)[0] self._table_position.setCurrentItem(item) self._table_position.blockSignals(False) self._table_position.scrollToItem(item) self._table_position.update() def _set_current_time(self, time): if self._imagecontainer.has_timelapse: self._table_time.blockSignals(True) item = self._table_time.findItems(str(time), Qt.MatchExactly)[0] self._table_time.setCurrentItem(item) self._table_time.blockSignals(False) self._table_time.scrollToItem(item) self._table_time.update()
def _load_image_container(self, plate_infos=None, scan_plates=None, show_dialog=True): self._clear_browser() if plate_infos is None: plate_infos = list(ImageContainer.iter_check_plates( self._settings)) imagecontainer = ImageContainer() self._imagecontainer = imagecontainer if scan_plates is None: scan_plates = dict((info[0], False) for info in plate_infos) def load(emitter, icontainer, settings, splates): iter_ = icontainer.iter_import_from_settings(settings, scan_plates=splates) for idx, info in enumerate(iter_): emitter.setValue.emit(idx) emitter.setLabelText.emit("checking dimensions...") emitter.setRange.emit(0, 0) QtCore.QCoreApplication.processEvents() if len(icontainer.plates) > 0: icontainer.set_plate(icontainer.plates[0]) icontainer.check_dimensions() label = ('Please wait until the input structure is scanned\n' 'or the structure data loaded...') self._dlg = ProgressDialog(label, None, 0, len(scan_plates), self) emitter = ProgressObject() emitter.setRange.connect(self._dlg.setRange) emitter.setValue.connect(self._dlg.setValue) emitter.setLabelText.connect(self._dlg.setLabelText) try: func = lambda: load(emitter, imagecontainer, self._settings, scan_plates) self._dlg.exec_(func, (emitter, )) except ImportError as e: # structure file from versions older than 1.3 contain pdk which is # removed if 'pdk' in str(e): QMessageBox.critical( self, "Error", ("Your structure file format is outdated.\n" "You have to rescan the plate(s)")) else: QMessageBox.critical(self, "Error", traceback.format_exc()) return except Exception as e: QMessageBox.critical(self, "Error", str(e)) try: # I hate lookup tables! self._tab_lookup['Cluster'][1].set_imagecontainer(imagecontainer) except KeyError: pass if len(imagecontainer.plates) > 0: channels = imagecontainer.channels # do not report value changes to the main window self._settings.set_notify_change(False) self.set_image_crop_size() problems = [] for prefix in ['primary', 'secondary', 'tertiary']: trait = self._settings.get_trait(SECTION_NAME_OBJECTDETECTION, '%s_channelid' % prefix) if trait.set_list_data(channels) is None: problems.append(prefix) self._tabs[1].get_widget('%s_channelid' % prefix).update() # report problems about a mismatch between channel IDs found in the data # and specified by the user if len(problems) > 0: # a mismatch between settings and data will cause changed settings self.settings_changed(True) trait = self._settings.get_trait(SECTION_NAME_EVENT_SELECTION, 'duration_unit') # allow time-base tracking durations only if time-stamp # information is present meta_data = imagecontainer.get_meta_data() if meta_data.has_timestamp_info: result = trait.set_list_data(TimeConverter.units) else: result = trait.set_list_data([TimeConverter.FRAMES]) if result is None: QMessageBox.critical( self, "Could not set tracking duration units", ("The tracking duration units selected to match the " "load data. Please check your settings.")) # a mismatch between settings and data will cause changed settings self.settings_changed(True) # activate change notification again self._settings.set_notify_change(True) self.set_modules_active(state=True) if show_dialog: QMessageBox.information( self, "Information", "%d plate(s) successfully loaded." % len(imagecontainer.plates)) else: QMessageBox.critical( self, "Error", ("No images found\n" "Verifiy your nameing scheme and rescan the data."))
class ClusterDisplay(QGroupBox): def __init__(self, parent, clusterframe, settings): QGroupBox.__init__(self, parent) self._settings = settings self._clusterframe = clusterframe self._imagecontainer = None self._jobid = None self._toggle_state = JOB_CONTROL_SUSPEND self._service = None self._host_url = CecogEnvironment.analyzer_config.get( 'Cluster', 'host_url') try: self._host_url_fallback = CecogEnvironment.analyzer_config.get(\ 'Cluster', 'host_url_fallback') except: # old config file self._host_url_fallback = self._host_url self.setTitle('ClusterControl') label1 = QLabel('Cluster URL:', self) fixed = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) expanding = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) label1.setSizePolicy(fixed) self._label_hosturl = QLabel('', self) self._label_hosturl.setSizePolicy(expanding) label3 = QLabel('Cluster status:', self) label3.setSizePolicy(fixed) self._label_status = QLabel('', self) self._label_status.setSizePolicy(expanding) label4 = QLabel('Path mappings:', self) label4.setSizePolicy(fixed) self._table_info = QTableWidget(self) self._table_info.setSelectionMode(QTableWidget.NoSelection) labels = ['status', 'your machine', 'on the cluster'] self._table_info.setColumnCount(len(labels)) self._table_info.setHorizontalHeaderLabels(labels) self._table_info.setSizePolicy(QSizePolicy(QSizePolicy.Expanding|QSizePolicy.Maximum, QSizePolicy.MinimumExpanding)) layout = QGridLayout(self) layout.addWidget(label1, 0, 0, Qt.AlignRight) layout.addWidget(self._label_hosturl, 0, 1, 1, 4) layout.addWidget(label3, 1, 0, Qt.AlignRight) layout.addWidget(self._label_status, 1, 1, 1, 4) layout.addWidget(label4, 2, 0, Qt.AlignRight) layout.addWidget(self._table_info, 3, 0, 1, 5) label = QLabel('Mail addresses:', self) layout.addWidget(label, 4, 0, Qt.AlignRight) mails = CecogEnvironment.analyzer_config.get('Cluster', 'mail_adresses') self._txt_mail = QLineEdit(mails, self) layout.addWidget(self._txt_mail, 4, 1, 1, 4) self._btn_submit = QPushButton('Submit job', self) self._btn_submit.setEnabled(False) self._btn_submit.clicked.connect(self._on_submit_job) layout.addWidget(self._btn_submit, 5, 0, 1, 5) line = QFrame(self) line.setFrameShape(QFrame.HLine) layout.addWidget(line, 6, 0, 1, 5) label5 = QLabel('Current job ID:', self) layout.addWidget(label5, 7, 0, Qt.AlignRight) self._txt_jobid = QLineEdit(self._jobid or '', self) regexp = QRegExp('\d+') regexp.setPatternSyntax(QRegExp.RegExp2) self._txt_jobid.textEdited.connect(self._on_jobid_entered) self._txt_jobid.returnPressed.connect(self._on_update_job_status) layout.addWidget(self._txt_jobid, 7, 1) self._btn_update = QPushButton('Update', self) self._btn_update.clicked.connect(self._on_update_job_status) layout.addWidget(self._btn_update, 7, 2) self._btn_toogle = QPushButton(self._toggle_state, self) self._btn_toogle.clicked.connect(self._on_toggle_job) self._btn_toogle.setCheckable(True) layout.addWidget(self._btn_toogle, 7, 3) self._btn_terminate = QPushButton('Terminate', self) self._btn_terminate.clicked.connect(self._on_terminate_job) layout.addWidget(self._btn_terminate, 7, 4) label = QLabel('Job status:', self) layout.addWidget(label, 8, 0, Qt.AlignRight) self._label_jobstatus = QLabel('', self) layout.addWidget(self._label_jobstatus, 8, 1, 1, 4) layout.addItem(QSpacerItem(1, 1, QSizePolicy.MinimumExpanding, QSizePolicy.Expanding|QSizePolicy.Maximum), 10, 0, 1, 5) @property def jobIds(self): return self._txt_jobid.text() @jobIds.setter def jobIds(self, jobids): self._txt_jobid.setText(jobids) self._jobid = str(jobids) @property def imagecontainer(self): if self._imagecontainer is None: raise RuntimeError("Image container is not loaded yet") return self._imagecontainer @imagecontainer.deleter def imagecontainer(self): del self._imagecontainer @imagecontainer.setter def imagecontainer(self, imagecontainer): self._imagecontainer = imagecontainer def _on_jobid_entered(self, txt): self._jobid = str(txt) @pyqtSlot() def _on_submit_job(self): self._submit_settings.set_section(SECTION_NAME_GENERAL) if not self._submit_settings.get2('constrain_positions'): positions = [] for plate_id in self.imagecontainer.plates: self.imagecontainer.set_plate(plate_id) meta_data = self.imagecontainer.get_meta_data() positions += ['%s___%s' % (plate_id, p) for p in meta_data.positions] self._submit_settings.set2('positions', ','.join(positions)) nr_items = len(positions) else: positions = self._submit_settings.get2('positions') nr_items = len(positions.split(',')) # FIXME: we need to get the current value for 'position_granularity' settings_dummy = self._clusterframe.get_special_settings(self._settings) position_granularity = settings_dummy.get('Cluster', 'position_granularity') path_out = self._submit_settings.get2('pathout') emails = str(self._txt_mail.text()).split(',') try: self.dlg = ProgressDialog("submitting jobs...", None, 0, 0, self) settings_str = self._submit_settings.to_string() func = lambda: self._service.submit_job('cecog_batch', settings_str, path_out, emails, nr_items, position_granularity, version) self.dlg.exec_(func) jobid = self.dlg.getTargetResult() except Exception as e: exception(self, 'Error on job submission (%s)' %str(e)) else: # FIXME: no idea how DRMAA 1.0 compatible this is if type(jobid) == types.ListType: self._jobid = ','.join(jobid) main_jobid = jobid[0].split('.')[0] else: self._jobid = str(jobid) main_jobid = jobid self._txt_jobid.setText(self._jobid) self._update_job_status() information(self, 'Job submitted successfully', "Job successfully submitted to the cluster.\nJob ID: %s, items: %d" % (main_jobid, nr_items)) @pyqtSlot() def _on_terminate_job(self): try: self.dlg = ProgressDialog("terminating jobs...", None, 0, 0, self) func = lambda: self._service.control_job(self._jobid, JOB_CONTROL_TERMINATE) self.dlg.exec_(func) except Exception as e: exception(self, 'Error on job termination (%s)' %str(e)) else: self._btn_toogle.setChecked(False) self._toggle_state = JOB_CONTROL_SUSPEND self._btn_toogle.setText(self._toggle_state) self._update_job_status() @pyqtSlot() def _on_toggle_job(self): try: self.dlg = ProgressDialog("suspending jobs...", None, 0, 0, self) func = lambda: self._service.control_job(self._jobid, self._toggle_state) self.dlg.exec_(func) except Exception as e: self._toggle_state = JOB_CONTROL_SUSPEND self._btn_toogle.setChecked(False) exception(self, 'Error on toggle job status (%s)' %str(e)) else: if self._toggle_state == JOB_CONTROL_SUSPEND: self._toggle_state = JOB_CONTROL_RESUME else: self._toggle_state = JOB_CONTROL_SUSPEND self._update_job_status() self._btn_toogle.setText(self._toggle_state) @pyqtSlot() def _on_update_job_status(self): txt = self._update_job_status() information(self, 'Cluster update', "Message: '%s'" % txt) def _update_job_status(self): try: self.dlg = ProgressDialog("updating job status...", None, 0, 0, self) func = lambda: self._service.get_job_status(self._jobid) self.dlg.exec_(func) txt = self.dlg.getTargetResult() except Exception as e: exception(self, 'Error on retrieve job status (%s)' %str(e)) else: self._label_jobstatus.setText(txt) return txt def _check_host_url(self): url = urlparse.urlparse(self._host_url) try: test_sock = socket.create_connection((url.hostname, url.port), timeout=1) test_sock.shutdown(2) test_sock.close() except: try: url = urlparse.urlparse(self._host_url_fallback) test_sock = socket.create_connection((url.hostname, url.port), timeout=1) test_sock.shutdown(2) test_sock.close() self._host_url = self._host_url_fallback except: exception(self, 'Error on connecting to cluster control service. Please check your config.ini') def _connect(self): self._check_host_url() success = False msg = 'Error on connecting to cluster control service on %s' % self._host_url try: client = RemotingService(self._host_url) self.dlg = ProgressDialog("connecting to cluster...", None, 0, 0, self) func = lambda: client.getService('clustercontrol') self.dlg.exec_(func) self._service = self.dlg.getTargetResult() except: exception(self, msg) else: try: self.dlg.exec_(self._service.get_cecog_versions) cluster_versions = self.dlg.getTargetResult() except Exception as e: exception(self, msg + '(%s)' %str(e)) else: if not version in set(cluster_versions): warning(self, 'Cecog version %s not supported by the cluster' % version, 'Valid versions are: %s' \ % ', '.join(cluster_versions)) else: success = True return success def _get_mappable_paths(self): ''' Get the paths/filenames which have to be mapped to run on a remote cluster. Whether an option is considered or not might depend on other values/switches, e.g. if classification is not needed there is no need to map the paths. ''' #FIXME: should be done in a better way. results = [] targets = [(('General', 'pathin'), []), (('General', 'pathout'),[]), (('General', 'structure_file_extra_path_name'), [('General', 'structure_file_extra_path')]), (('Classification', 'primary_classification_envpath'), [('Processing', 'primary_classification')]), (('Classification', 'secondary_classification_envpath'), [('General', 'process_secondary'), ('Processing', 'secondary_classification')]), (('Classification', 'tertiary_classification_envpath'), [('General', 'process_tertiary'), ('Processing', 'tertiary_classification')]), (('Classification', 'merged_classification_envpath'), [('General', 'process_merged'), ('Processing', 'merged_classification')]), ] targets.extend([(('ObjectDetection', '%s_flat_field_correction_image_dir' % prefix), [('ObjectDetection', '%s_flat_field_correction' % prefix)]) for prefix in ['primary', 'secondary', 'tertiary']] ) for info, const in targets: passed = reduce(lambda x,y: x and y, map(lambda z: self._settings.get(*z), const), True) if passed: results.append(info) return results def update_display(self, is_active): if self._connect(): self._can_submit = True try: self._submit_settings = self._clusterframe.get_special_settings( \ self._settings, self.imagecontainer.has_timelapse) except: self._submit_settings = self._clusterframe.get_special_settings(self._settings) self._label_hosturl.setText(self._host_url) self._label_status.setText(self._service.get_service_info()) mappable_paths = self._get_mappable_paths() self._table_info.clearContents() self._table_info.setRowCount(len(mappable_paths)) for idx, info in enumerate(mappable_paths): value = self._settings.get(*info) mapped = CecogEnvironment.map_path_to_os( value, target_os='linux', force=False) self._submit_settings.set(info[0], info[1], mapped) status = not mapped is None item = QTableWidgetItem() item.setBackground(QBrush(QColor('green' if status else 'red'))) txt_mapped = str(mapped) if status else \ 'Warning: path can not be mapped on the cluster' self._table_info.setItem(idx, 0, item) self._table_info.setItem(idx, 1, QTableWidgetItem(value)) self._table_info.setItem(idx, 2, QTableWidgetItem(txt_mapped)) self._can_submit &= status self._table_info.resizeColumnsToContents() self._table_info.resizeRowsToContents() self._btn_submit.setEnabled(self._can_submit and is_active) self._btn_terminate.setEnabled(is_active) self._btn_toogle.setEnabled(is_active) self._btn_update.setEnabled(is_active) else: self._btn_submit.setEnabled(False) self._btn_terminate.setEnabled(False) self._btn_toogle.setEnabled(False) self._btn_update.setEnabled(False)
class ClusterDisplay(QGroupBox): def __init__(self, parent, clusterframe, settings): QGroupBox.__init__(self, parent) self._settings = settings self._clusterframe = clusterframe self._imagecontainer = None self._jobid = None self._toggle_state = JOB_CONTROL_SUSPEND self._service = None self._host_url = CecogEnvironment.analyzer_config.get('Cluster', 'host_url') try: self._host_url_fallback = CecogEnvironment.analyzer_config.get(\ 'Cluster', 'host_url_fallback') except: # old config file self._host_url_fallback = self._host_url self.setTitle('ClusterControl') label1 = QLabel('Cluster URL:', self) fixed = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) expanding = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) label1.setSizePolicy(fixed) self._label_hosturl = QLabel('', self) self._label_hosturl.setSizePolicy(expanding) label3 = QLabel('Cluster status:', self) label3.setSizePolicy(fixed) self._label_status = QLabel('', self) self._label_status.setSizePolicy(expanding) label4 = QLabel('Path mappings:', self) label4.setSizePolicy(fixed) self._table_info = QTableWidget(self) self._table_info.setSelectionMode(QTableWidget.NoSelection) labels = ['status', 'your machine', 'on the cluster'] self._table_info.setColumnCount(len(labels)) self._table_info.setHorizontalHeaderLabels(labels) self._table_info.setSizePolicy(QSizePolicy(QSizePolicy.Expanding|QSizePolicy.Maximum, QSizePolicy.MinimumExpanding)) layout = QGridLayout(self) layout.addWidget(label1, 0, 0, Qt.AlignRight) layout.addWidget(self._label_hosturl, 0, 1, 1, 4) layout.addWidget(label3, 1, 0, Qt.AlignRight) layout.addWidget(self._label_status, 1, 1, 1, 4) layout.addWidget(label4, 2, 0, Qt.AlignRight) layout.addWidget(self._table_info, 3, 0, 1, 5) label = QLabel('Mail addresses:', self) layout.addWidget(label, 4, 0, Qt.AlignRight) mails = CecogEnvironment.analyzer_config.get('Cluster', 'mail_adresses') self._txt_mail = QLineEdit(mails, self) layout.addWidget(self._txt_mail, 4, 1, 1, 4) self._btn_submit = QPushButton('Submit job', self) self._btn_submit.setEnabled(False) self._btn_submit.clicked.connect(self._on_submit_job) layout.addWidget(self._btn_submit, 5, 0, 1, 5) line = QFrame(self) line.setFrameShape(QFrame.HLine) layout.addWidget(line, 6, 0, 1, 5) label5 = QLabel('Current job ID:', self) layout.addWidget(label5, 7, 0, Qt.AlignRight) self._txt_jobid = QLineEdit(self._jobid or '', self) regexp = QRegExp('\d+') regexp.setPatternSyntax(QRegExp.RegExp2) self._txt_jobid.textEdited.connect(self._on_jobid_entered) self._txt_jobid.returnPressed.connect(self._on_update_job_status) layout.addWidget(self._txt_jobid, 7, 1) self._btn_update = QPushButton('Update', self) self._btn_update.clicked.connect(self._on_update_job_status) layout.addWidget(self._btn_update, 7, 2) self._btn_toogle = QPushButton(self._toggle_state, self) self._btn_toogle.clicked.connect(self._on_toggle_job) self._btn_toogle.setCheckable(True) layout.addWidget(self._btn_toogle, 7, 3) self._btn_terminate = QPushButton('Terminate', self) self._btn_terminate.clicked.connect(self._on_terminate_job) layout.addWidget(self._btn_terminate, 7, 4) label = QLabel('Job status:', self) layout.addWidget(label, 8, 0, Qt.AlignRight) self._label_jobstatus = QLabel('', self) layout.addWidget(self._label_jobstatus, 8, 1, 1, 4) layout.addItem(QSpacerItem(1, 1, QSizePolicy.MinimumExpanding, QSizePolicy.Expanding|QSizePolicy.Maximum), 10, 0, 1, 5) @property def imagecontainer(self): if self._imagecontainer is None: raise RuntimeError("Image container is not loaded yet") return self._imagecontainer @imagecontainer.deleter def imagecontainer(self): del self._imagecontainer @imagecontainer.setter def imagecontainer(self, imagecontainer): self._imagecontainer = imagecontainer def _on_jobid_entered(self, txt): self._jobid = str(txt) @pyqtSlot() def _on_submit_job(self): self._submit_settings.set_section(SECTION_NAME_GENERAL) if not self._submit_settings.get2('constrain_positions'): positions = [] for plate_id in self.imagecontainer.plates: self.imagecontainer.set_plate(plate_id) meta_data = self.imagecontainer.get_meta_data() positions += ['%s___%s' % (plate_id, p) for p in meta_data.positions] self._submit_settings.set2('positions', ','.join(positions)) nr_items = len(positions) else: positions = self._submit_settings.get2('positions') nr_items = len(positions.split(',')) # FIXME: we need to get the current value for 'position_granularity' settings_dummy = self._clusterframe.get_special_settings(self._settings) position_granularity = settings_dummy.get('Cluster', 'position_granularity') path_out = self._submit_settings.get2('pathout') emails = str(self._txt_mail.text()).split(',') try: self.dlg = ProgressDialog("submitting jobs...", None, 0, 0, self) settings_str = self._submit_settings.to_string() func = lambda: self._service.submit_job('cecog_batch', settings_str, path_out, emails, nr_items, position_granularity, VERSION) self.dlg.exec_(func) jobid = self.dlg.getTargetResult() except Exception as e: exception(self, 'Error on job submission (%s)' %str(e)) else: # FIXME: no idea how DRMAA 1.0 compatible this is if type(jobid) == types.ListType: self._jobid = ','.join(jobid) main_jobid = jobid[0].split('.')[0] else: self._jobid = str(jobid) main_jobid = jobid self._txt_jobid.setText(self._jobid) self._update_job_status() information(self, 'Job submitted successfully', "Job successfully submitted to the cluster.\nJob ID: %s, items: %d" % (main_jobid, nr_items)) @pyqtSlot() def _on_terminate_job(self): try: self.dlg = ProgressDialog("terminating jobs...", None, 0, 0, self) func = lambda: self._service.control_job(self._jobid, JOB_CONTROL_TERMINATE) self.dlg.exec_(func) except Exception as e: exception(self, 'Error on job termination (%s)' %str(e)) else: self._btn_toogle.setChecked(False) self._toggle_state = JOB_CONTROL_SUSPEND self._btn_toogle.setText(self._toggle_state) self._update_job_status() @pyqtSlot() def _on_toggle_job(self): try: self.dlg = ProgressDialog("suspending jobs...", None, 0, 0, self) func = lambda: self._service.control_job(self._jobid, self._toggle_state) self.dlg.exec_(func) except Exception as e: self._toggle_state = JOB_CONTROL_SUSPEND self._btn_toogle.setChecked(False) exception(self, 'Error on toggle job status (%s)' %str(e)) else: if self._toggle_state == JOB_CONTROL_SUSPEND: self._toggle_state = JOB_CONTROL_RESUME else: self._toggle_state = JOB_CONTROL_SUSPEND self._update_job_status() self._btn_toogle.setText(self._toggle_state) @pyqtSlot() def _on_update_job_status(self): txt = self._update_job_status() information(self, 'Cluster update', "Message: '%s'" % txt) def _update_job_status(self): try: self.dlg = ProgressDialog("updating job status...", None, 0, 0, self) func = lambda: self._service.get_job_status(self._jobid) self.dlg.exec_(func) txt = self.dlg.getTargetResult() except Exception as e: exception(self, 'Error on retrieve job status (%s)' %str(e)) else: self._label_jobstatus.setText(txt) return txt def _check_host_url(self): url = urlparse.urlparse(self._host_url) try: test_sock = socket.create_connection((url.hostname, url.port), timeout=1) test_sock.shutdown(2) test_sock.close() except: try: url = urlparse.urlparse(self._host_url_fallback) test_sock = socket.create_connection((url.hostname, url.port), timeout=1) test_sock.shutdown(2) test_sock.close() self._host_url = self._host_url_fallback except: exception(self, 'Error on connecting to cluster control service. Please check your config.ini') def _connect(self): self._check_host_url() success = False msg = 'Error on connecting to cluster control service on %s' % self._host_url try: client = RemotingService(self._host_url) self.dlg = ProgressDialog("connecting to cluster...", None, 0, 0, self) func = lambda: client.getService('clustercontrol') self.dlg.exec_(func) self._service = self.dlg.getTargetResult() except: exception(self, msg) else: try: self.dlg.exec_(self._service.get_cecog_versions) cluster_versions = self.dlg.getTargetResult() except Exception as e: exception(self, msg + '(%s)' %str(e)) else: if not VERSION in set(cluster_versions): warning(self, 'Cecog version %s not supported by the cluster' % VERSION, 'Valid versions are: %s' \ % ', '.join(cluster_versions)) else: success = True return success def _get_mappable_paths(self): ''' Get the paths/filenames which have to be mapped to run on a remote cluster. Whether an option is considered or not might depend on other values/switches, e.g. if classification is not needed there is no need to map the paths. ''' #FIXME: should be done in a better way. results = [] targets = [(('General', 'pathin'), []), (('General', 'pathout'),[]), (('General', 'structure_file_extra_path_name'), [('General', 'structure_file_extra_path')]), (('Classification', 'primary_classification_envpath'), [('Processing', 'primary_classification')]), (('Classification', 'secondary_classification_envpath'), [('General', 'process_secondary'), ('Processing', 'secondary_classification')]), (('Classification', 'tertiary_classification_envpath'), [('General', 'process_tertiary'), ('Processing', 'tertiary_classification')]), (('Classification', 'merged_classification_envpath'), [('General', 'process_merged'), ('Processing', 'merged_classification')]), ] targets.extend([(('ObjectDetection', '%s_flat_field_correction_image_dir' % prefix), [('ObjectDetection', '%s_flat_field_correction' % prefix)]) for prefix in ['primary', 'secondary', 'tertiary']] ) for info, const in targets: passed = reduce(lambda x,y: x and y, map(lambda z: self._settings.get(*z), const), True) if passed: results.append(info) return results def update_display(self, is_active): if self._connect(): self._can_submit = True try: self._submit_settings = self._clusterframe.get_special_settings( \ self._settings, self.imagecontainer.has_timelapse) except: self._submit_settings = self._clusterframe.get_special_settings(self._settings) self._label_hosturl.setText(self._host_url) self._label_status.setText(self._service.get_service_info()) mappable_paths = self._get_mappable_paths() self._table_info.clearContents() self._table_info.setRowCount(len(mappable_paths)) for idx, info in enumerate(mappable_paths): value = self._settings.get(*info) mapped = CecogEnvironment.map_path_to_os( value, target_os='linux', force=False) self._submit_settings.set(info[0], info[1], mapped) status = not mapped is None item = QTableWidgetItem() item.setBackground(QBrush(QColor('green' if status else 'red'))) txt_mapped = str(mapped) if status else \ 'Warning: path can not be mapped on the cluster' self._table_info.setItem(idx, 0, item) self._table_info.setItem(idx, 1, QTableWidgetItem(value)) self._table_info.setItem(idx, 2, QTableWidgetItem(txt_mapped)) self._can_submit &= status self._table_info.resizeColumnsToContents() self._table_info.resizeRowsToContents() self._btn_submit.setEnabled(self._can_submit and is_active) self._btn_terminate.setEnabled(is_active) self._btn_toogle.setEnabled(is_active) self._btn_update.setEnabled(is_active) else: self._btn_submit.setEnabled(False) self._btn_terminate.setEnabled(False) self._btn_toogle.setEnabled(False) self._btn_update.setEnabled(False)
class CecogAnalyzer(QtWidgets.QMainWindow): NAME_FILTERS = ['Settings files (*.conf)', 'All files (*.*)'] modified = QtCore.pyqtSignal('bool') def __init__(self, appname, version, redirect, settings=None, debug=False, *args, **kw): super(CecogAnalyzer, self).__init__(*args, **kw) self.setWindowTitle("%s-%s" %(appname, version) + '[*]') self.setAcceptDrops(True) self.setCentralWidget(QtWidgets.QFrame(self)) self.setObjectName(appname) self.version = version self.appname = appname self.debug = debug self.environ = CecogEnvironment(version=version, redirect=redirect, debug=debug) if debug: self.environ.pprint() self._is_initialized = False self._imagecontainer = None self._meta_data = None self._browser = None action_quit = self.create_action('&Quit', slot=self.close) action_pref = self.create_action('&Preferences', slot=self.open_preferences) action_load = self.create_action('&Load Settings...', shortcut=QtGui.QKeySequence.Open, slot=self._on_file_open) action_save = self.create_action('&Save Settings', shortcut=QtGui.QKeySequence.Save, slot=self._on_file_save) self.action_save = action_save action_save_as = self.create_action('&Save Settings As...', shortcut=QtGui.QKeySequence.SaveAs, slot=self._on_file_save_as) menu_file = self.menuBar().addMenu('&File') self.add_actions(menu_file, (action_pref, None, action_load, None, action_save, action_save_as, None, action_quit)) action_log = self.create_action('&Log window', shortcut=QtGui.QKeySequence(Qt.CTRL+Qt.Key_L), slot=self._on_show_log_window) action_open = self.create_action('&Browser', shortcut=QtGui.QKeySequence('CTRL+B'), slot=self._on_browser_open) menu_view = self.menuBar().addMenu('&View') self.add_actions(menu_view, (action_log,)) self.add_actions(menu_view, (action_open,)) action_assistant = self.create_action('&Help', shortcut=QtGui.QKeySequence.HelpContents, slot=self.show_assistant) action_about = self.create_action('&About', slot=self.on_about) action_aboutQt = self.create_action('&About Qt', slot=self.about_qt) menu_help = self.menuBar().addMenu('&Help') self.add_actions(menu_help, (action_assistant, action_about, action_aboutQt)) self.setStatusBar(QtWidgets.QStatusBar(self)) self._selection = QtWidgets.QListWidget(self.centralWidget()) self._selection.setViewMode(QtWidgets.QListView.IconMode) self._selection.setIconSize(QtCore.QSize(35, 35)) self._selection.setGridSize(QtCore.QSize(140, 60)) self._selection.setMovement(QtWidgets.QListView.Static) self._selection.setMaximumWidth(self._selection.gridSize().width() + 5) self._selection.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self._selection.setSizePolicy( QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Expanding)) self._pages = FrameStack(self) self._settings_filename = None self._settings = GuiConfigSettings(self) self._tab_lookup = OrderedDict() self._tabs = [GeneralFrame(self._settings, self._pages, SECTION_NAME_GENERAL), ObjectDetectionFrame(self._settings, self._pages, SECTION_NAME_OBJECTDETECTION), FeatureExtractionFrame(self._settings, self._pages, SECTION_NAME_FEATURE_EXTRACTION), ClassificationFrame(self._settings, self._pages, SECTION_NAME_CLASSIFICATION), TrackingFrame(self._settings, self._pages, SECTION_NAME_TRACKING), EventSelectionFrame(self._settings, self._pages, SECTION_NAME_EVENT_SELECTION), ErrorCorrectionFrame(self._settings, self._pages, SECTION_NAME_ERRORCORRECTION), OutputFrame(self._settings, self._pages, SECTION_NAME_OUTPUT), ProcessingFrame(self._settings, self._pages, SECTION_NAME_PROCESSING)] # connections for the section frames self._tabs[3].connect_browser_btn(self._on_browser_open) for frame in self._tabs: frame.status_message.connect(self.statusBar().showMessage) app = AppPreferences() if app.cluster_support: clusterframe = ClusterFrame(self._settings, self._pages, SECTION_NAME_CLUSTER) clusterframe.set_imagecontainer(self._imagecontainer) self._tabs.append(clusterframe) try: self.updateStyleSheet(loadStyle(app.stylesheet)) except Exception as e: # proceed with no stylesheet traceback.print_exc() widths = [] for tab in self._tabs: size = self._add_page(tab) widths.append(size.width()) self.set_modules_active(state=False) self._pages.setMinimumWidth(max(widths) + 45) self._selection.currentItemChanged.connect(self._on_change_page) self._selection.setCurrentRow(0) w_logo = QtWidgets.QLabel(self.centralWidget()) w_logo.setPixmap(QtGui.QPixmap(':cecog_logo_w145')) layout = QtWidgets.QGridLayout(self.centralWidget()) layout.addWidget(self._selection, 0, 0) layout.addWidget(w_logo, 1, 0, Qt.AlignBottom | Qt.AlignHCenter) layout.addWidget(self._pages, 0, 1, 2, 1) layout.setContentsMargins(1, 1, 1, 1) self.setGeometry(0, 0, 1250, 800) self.setMinimumSize(QtCore.QSize(700, 600)) self._is_initialized = True self._restore_geometry() self.show() # finally load (demo) - settings if settings is None: self.load_settings(self.environ.demo_settings) elif os.path.isfile(settings): self.load_settings(settings) else: QMessageBox.warning( self, "Warning", "File (%s) does not exist" %settings) def dragEnterEvent(self, event): event.acceptProposedAction() def dragMoveEvent(self, event): event.acceptProposedAction() def dropEvent(self, event): mimeData = event.mimeData() if mimeData.hasUrls(): if len(mimeData.urls()) == 1: self.load_settings(fix_path(mimeData.urls()[0].path())) # self._on_load_input() event.acceptProposedAction() def dragLeaveEvent(self, event): event.accept() def _save_geometry(self): settings = QtCore.QSettings(version.organisation, version.appname) settings.beginGroup('Gui') settings.setValue('state', self.saveState()) settings.setValue('geometry', self.saveGeometry()) try: settings.setValue( 'clusterjobs', self._pages.widgetByType( ClusterFrame).get_jobids()) except KeyError: pass settings.endGroup() def _restore_geometry(self): settings = QtCore.QSettings(version.organisation, version.appname) settings.beginGroup('Gui') if settings.contains('geometry'): self.restoreGeometry(settings.value('geometry')) if settings.contains('state'): self.restoreState(settings.value('state')) if settings.contains('clusterjobs'): jobids = settings.value('clusterjobs') if AppPreferences().cluster_support and jobids: self._pages.widgetByType(ClusterFrame).restore_jobids(jobids) settings.endGroup() def closeEvent(self, event): self._pages.close() # Quit dialog only if not debuging flag is not set self._save_geometry() if self.debug: QtWidgets.QApplication.exit() ret = QMessageBox.question(self, "Quit %s" %self.appname, "Do you really want to quit?", QMessageBox.Yes|QMessageBox.No) if ret == QMessageBox.No: event.ignore() else: self._check_settings_saved(QMessageBox.Yes|QMessageBox.No) QtWidgets.QApplication.exit() def settings_changed(self, changed): if self._is_initialized: self.setWindowModified(changed) self.action_save.setEnabled(changed) self.modified.emit(changed) def _add_page(self, widget): button = QtWidgets.QListWidgetItem(self._selection) button.setIcon(QtGui.QIcon(widget.ICON)) button.setText(widget.get_name()) button.setTextAlignment(Qt.AlignHCenter) self._pages.addWidget(widget) widget.toggle_tabs.connect(self._on_toggle_tabs) self._tab_lookup[widget.get_name()] = (button, widget) return widget.size() def _on_toggle_tabs(self, name): """Toggle ItemIsEnabled flag for all list items but name.""" for name2 in self._tab_lookup: if name2 != name: item, _ = self._tab_lookup[name2] flags = item.flags() # check flag (and) if flags & Qt.ItemIsEnabled: # remove flag (nand) item.setFlags(flags & ~Qt.ItemIsEnabled) else: # set flag (or) item.setFlags(flags | Qt.ItemIsEnabled) def _on_change_page(self, current, previous): if not current: current = previous index = self._selection.row(current) self._pages.setCurrentIndex(index) widget = self._pages.widget(index) widget.page_changed() def _check_settings_saved( self, buttons=QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel): if self.isWindowModified(): result = QMessageBox.question( self, "Settings have been modified", "Do you want to save the settings?", buttons) if result == QMessageBox.Yes: self.save_settings() else: result = QMessageBox.No return result def add_actions(self, target, actions): for action in actions: if action is None: target.addSeparator() else: target.addAction(action) def create_action(self, text, slot=None, shortcut=None, icon=None, tooltip=None, checkable=None, signal='triggered', checked=False): action = QtWidgets.QAction(text, self) if icon is not None: action.setIcon(QtGui.QIcon(':/%s.png' % icon)) if shortcut is not None: action.setShortcut(shortcut) if tooltip is not None: action.setToolTip(tooltip) action.setStatusTip(tooltip) if slot is not None: getattr(action, signal).connect(slot) if checkable is not None: action.setCheckable(True) action.setChecked(checked) return action def save_settings(self, save_as=False): filename = self._settings_filename if filename is None or save_as: filename = self._get_save_as_filename() if not filename is None: self._write_settings(filename) def load_settings(self, filename): try: self._settings.read(filename) except Exception as e: QMessageBox.critical(self, "Error", ("Error loading settings file\n" "Could not load settings file '%s'.\n%s" %(filename, str(e)))) self.statusBar().showMessage('Error loading settings files.') else: self._settings_filename = filename title = self.windowTitle().split(' - ')[0] self.setWindowTitle('%s - %s[*]' % (title, filename)) try: # reset naming scheme to load config file completely nst = self._settings.get_trait("General", "namingscheme") namingscheme_file = self._settings("General", "namingscheme") if not namingscheme_file in nst.list_data: self._settings.set("General", "namingscheme", nst.default_value) QMessageBox.warning(self, "Unkown naming scheme", ("%s-%s can not use the naming scheme '%s'." " Resetting to default '%s'" %(version.appname, version.version, namingscheme_file, nst.default_value))) for widget in self._tabs: widget.update_input() except Exception as e: msg = "Could not load settings file (%s)\n.%s" \ %(filename, traceback.format_exc()) QMessageBox.critical(self, "Error", msg) else: # set settings to not-changed (assume no changed since loaded from file) self.settings_changed(False) # notify tabs about new settings loaded for tab in self._tabs: tab.settings_loaded() self.statusBar().showMessage('Settings successfully loaded.') def _write_settings(self, filename): try: f = file(filename, 'w') # create a new version (copy) of the current # settings which add the needed rendering information pframe = self._tab_lookup[SECTION_NAME_PROCESSING][1] settings_dummy = pframe.get_export_settings(self._settings) settings_dummy.write(f) f.close() except Exception as e: msg = "Could not save settings\n%s" %str(e) QMessageBox.critical(self, "Error", msg) self.statusBar().showMessage('Settings not successfully saved.') else: self._settings_filename = filename self.setWindowTitle('%s - %s[*]' % (self.appname, filename)) self.settings_changed(False) self.statusBar().showMessage('Settings successfully saved.') def on_about(self): dialog = CecogAboutDialog(self) dialog.show() def about_qt(self): QMessageBox.aboutQt(self, "about Qt") def open_preferences(self): pref = PreferencesDialog(self) pref.exec_() def updateStyleSheet(self, stylesheet): self.setStyleSheet("") self.setStyleSheet(stylesheet) self._pages.assistant.setStyleSheet("") self._pages.assistant.setStyleSheet(stylesheet) if self._browser is not None: self._browser.setStyleSheet("") self._browser.setStyleSheet(stylesheet) def _on_browser_open(self): if self._imagecontainer is None: QMessageBox.warning(self, 'Data structure not loaded', 'The input directory structure file was not loaded.\n' 'Click "Scan input directory" in section "General" to proceed.') elif self._browser is None: try: browser = Browser(self._settings, self._imagecontainer, None) browser.show() browser.raise_() browser.setFocus() app = AppPreferences() browser.setStyleSheet(loadStyle(app.stylesheet)) self._browser = browser except Exception as e: traceback.print_exc() QMessageBox.critical(self, "Error", str(e)) else: self._browser.show() self._browser.raise_() def _on_load_input(self): txt = "Error scanning image structure" path_in = self._settings.get(SECTION_NAME_GENERAL, 'pathin') if path_in == '': QMessageBox.critical(self, "Error", "%s\nImage path must be defined." %txt) elif not os.path.isdir(path_in) and \ not os.path.isdir(os.path.join(self.environ.package_dir, path_in)): QMessageBox.critical(self, "Error", "%s\nImage path '%s' not found." %(txt, path_in)) else: try: infos = list(ImageContainer.iter_check_plates(self._settings)) except Exception as e: QMessageBox.critical(self, "Error", "%s\n%s" %(txt, str(e))) else: found_any = numpy.any([not info[3] is None for info in infos]) cancel = False if found_any: found_plates = [info[0] for info in infos if not info[3] is None] missing_plates = [info[0] for info in infos if info[3] is None] has_missing = len(missing_plates) > 0 txt = '%s plates were already scanned.\nDo you want ' \ 'to rescan the file structure(s)? ' \ 'This can take several minutes.' % \ ('Some' if has_missing else 'All') title = 'Rescan input structure?' box = QMessageBox(QMessageBox.Question, title, title, QMessageBox.Cancel, self, Qt.Sheet) box.setWindowModality(Qt.WindowModal) box.setInformativeText(txt) box.setDetailedText('Plates with scanned structure: \n%s\n' '\nPlates without scanned structure: ' '\n%s' % ('\n'.join(found_plates), '\n'.join(missing_plates))) if not has_missing: btn1 = QtWidgets.QPushButton('No', box) box.addButton(btn1, QMessageBox.NoRole) box.setDefaultButton(btn1) elif len(found_plates) > 0: btn1 = QtWidgets.QPushButton('Rescan missing', box) box.addButton(btn1, QMessageBox.YesRole) box.setDefaultButton(btn1) else: btn1 = None btn2 = QtWidgets.QPushButton('Rescan all', box) box.addButton(btn2, QMessageBox.YesRole) if box.exec_() == QMessageBox.Cancel: cancel = True else: btn = box.clickedButton() if btn == btn1: if has_missing: scan_plates = dict([(info[0], info[0] in missing_plates) for info in infos]) else: scan_plates = dict((info[0], False) for info in infos) else: scan_plates = dict((info[0], True) for info in infos) else: has_multiple = self._settings.get(SECTION_NAME_GENERAL, "has_multiple_plates") ret = QMessageBox.question(self, "No structure data found", ("Scanning the input directory can be time " "consuming.\n\nDo you want to proceed?"), QMessageBox.Yes|QMessageBox.No) if ret == QMessageBox.No: cancel = True scan_plates = dict((info[0], True) for info in infos) if not cancel: self._load_image_container(infos, scan_plates) def _load_image_container(self, plate_infos=None, scan_plates=None, show_dialog=True): self._clear_browser() if plate_infos is None: plate_infos = list(ImageContainer.iter_check_plates(self._settings)) imagecontainer = ImageContainer() self._imagecontainer = imagecontainer if scan_plates is None: scan_plates = dict((info[0], False) for info in plate_infos) def load(emitter, icontainer, settings, splates): iter_ = icontainer.iter_import_from_settings(settings, scan_plates=splates) for idx, info in enumerate(iter_): emitter.setValue.emit(idx) emitter.setLabelText.emit("checking dimensions...") emitter.setRange.emit(0, 0) QtCore.QCoreApplication.processEvents() if len(icontainer.plates) > 0: icontainer.set_plate(icontainer.plates[0]) icontainer.check_dimensions() label = ('Please wait until the input structure is scanned\n' 'or the structure data loaded...') self._dlg = ProgressDialog(label, None, 0, len(scan_plates), self) emitter = ProgressObject() emitter.setRange.connect(self._dlg.setRange) emitter.setValue.connect(self._dlg.setValue) emitter.setLabelText.connect(self._dlg.setLabelText) try: func = lambda: load(emitter, imagecontainer, self._settings, scan_plates) self._dlg.exec_(func, (emitter, )) except ImportError as e: # structure file from versions older than 1.3 contain pdk which is # removed if 'pdk' in str(e): QMessageBox.critical(self, "Error", ("Your structure file format is outdated.\n" "You have to rescan the plate(s)")) else: QMessageBox.critical(self, "Error", traceback.format_exc()) return except Exception as e: QMessageBox.critical(self, "Error", str(e)) try: # I hate lookup tables! self._tab_lookup['Cluster'][1].set_imagecontainer(imagecontainer) except KeyError: pass if len(imagecontainer.plates) > 0: channels = imagecontainer.channels # do not report value changes to the main window self._settings.set_notify_change(False) self.set_image_crop_size() problems = [] for prefix in ['primary', 'secondary', 'tertiary']: trait = self._settings.get_trait(SECTION_NAME_OBJECTDETECTION, '%s_channelid' % prefix) if trait.set_list_data(channels) is None: problems.append(prefix) self._tabs[1].get_widget('%s_channelid' % prefix).update() # report problems about a mismatch between channel IDs found in the data # and specified by the user if len(problems) > 0: # a mismatch between settings and data will cause changed settings self.settings_changed(True) trait = self._settings.get_trait(SECTION_NAME_EVENT_SELECTION, 'duration_unit') # allow time-base tracking durations only if time-stamp # information is present meta_data = imagecontainer.get_meta_data() if meta_data.has_timestamp_info: result = trait.set_list_data(TimeConverter.units) else: result = trait.set_list_data([TimeConverter.FRAMES]) if result is None: QMessageBox.critical(self, "Could not set tracking duration units", ("The tracking duration units selected to match the " "load data. Please check your settings.")) # a mismatch between settings and data will cause changed settings self.settings_changed(True) # activate change notification again self._settings.set_notify_change(True) self.set_modules_active(state=True) if show_dialog: QMessageBox.information( self, "Information", "%d plate(s) successfully loaded." % len(imagecontainer.plates)) else: QMessageBox.critical(self, "Error", ("No images found\n" "Verifiy your nameing scheme and rescan the data.")) def set_image_crop_size(self): x0, y0, x1, y1 = self._settings.get('General', 'crop_image_x0'), \ self._settings.get('General', 'crop_image_y0'), \ self._settings.get('General', 'crop_image_x1'), \ self._settings.get('General', 'crop_image_y1') x0_, y0_, x1_, y1_ = 0, \ 0, \ self._imagecontainer.get_meta_data().dim_x, \ self._imagecontainer.get_meta_data().dim_y tr_x0 = self._settings.get_trait(SECTION_NAME_GENERAL, 'crop_image_x0') tr_y0 = self._settings.get_trait(SECTION_NAME_GENERAL, 'crop_image_y0') tr_x1 = self._settings.get_trait(SECTION_NAME_GENERAL, 'crop_image_x1') tr_y1 = self._settings.get_trait(SECTION_NAME_GENERAL, 'crop_image_y1') # Check if the crop values are valid if x0 > 0 and y0 > 0 and x1 <= x1_ and y1 <= y1_ and \ x0 != x1 and y0 != y1: # Set to default values tr_x0.set_value(tr_x0.get_widget(), x0) tr_y0.set_value(tr_y0.get_widget(), y0) tr_x1.set_value(tr_x1.get_widget(), x1) tr_y0.set_value(tr_y1.get_widget(), y1) else: tr_x0.set_value(tr_x0.get_widget(), x0_) tr_y0.set_value(tr_y0.get_widget(), y0_) tr_x1.set_value(tr_x1.get_widget(), x1_) tr_y0.set_value(tr_y1.get_widget(), y1_) # Set GUI widget valid ranges tr_x0.set_min_value(x0_) tr_x0.set_max_value(x1_) tr_y0.set_min_value(y0_) tr_y0.set_max_value(y1_) tr_x1.set_min_value(x0_) tr_x1.set_max_value(x1_) tr_y1.set_min_value(y0_) tr_y1.set_max_value(y1_) def set_modules_active(self, state=True): for name, (button, widget) in self._tab_lookup.iteritems(): widget.set_active(state) @QtCore.pyqtSlot() def _on_file_open(self): if self._check_settings_saved() != QMessageBox.Cancel: home = "" if self._settings_filename is not None: settings_filename = self.environ.demo_settings if os.path.isfile(settings_filename): home = settings_filename filename = QtWidgets.QFileDialog.getOpenFileName( \ self, 'Open config file', home, ';;'.join(self.NAME_FILTERS))[0] if not bool(filename): return try: self.load_settings(filename) if self._settings.was_old_file_format(): QMessageBox.information( self, 'Information', 'Config file was updated to version %s' %self.version) except Exception as e: msg = "File could not be loaded\n%s" %str(e) QMessageBox.critical(self, "Error", msg) finally: self._clear_browser() self.set_modules_active(state=False) @QtCore.pyqtSlot() def _on_file_save(self): self.save_settings(False) @QtCore.pyqtSlot() def _on_file_save_as(self): self.save_settings(True) def _clear_browser(self): if not self._browser is None: self._browser.close() self._browser = None def _on_show_log_window(self): self._pages.showLogWindow() def _get_save_as_filename(self): dir = "" if self._settings_filename is not None: settings_filename = self.environ.demo_settings if os.path.isfile(settings_filename): dir = settings_filename filename = QtWidgets.QFileDialog.getSaveFileName( self, 'Save config file as', dir, ';;'.join(self.NAME_FILTERS))[0] return filename or None def show_assistant(self): self._pages.assistant.show() self._pages.assistant.openKeyword('index')
class NavigationModule(Module): NAME = 'Navigation' coordinate_changed = pyqtSignal(Coordinate) def __init__(self, parent, browser, imagecontainer): Module.__init__(self, parent, browser) self._imagecontainer = imagecontainer frame_info = QGroupBox('Plate Information', self) layout = QGridLayout(frame_info) # frame_info.setStyleSheet('QLabel { font-size: 10px }') self._label_info = QLabel(frame_info) layout.addWidget(self._label_info, 0, 0, 0, 0, Qt.AlignCenter | Qt.AlignHCenter) splitter = QSplitter(Qt.Vertical, self) splitter.setMinimumWidth(40) layout = QBoxLayout(QBoxLayout.TopToBottom, self) layout.setContentsMargins(5, 5, 5, 5) layout.addWidget(splitter) grp1 = QGroupBox('Plates', splitter) grp2 = QGroupBox('Positions', splitter) splitter.addWidget(grp1) splitter.addWidget(grp2) layout = QGridLayout(grp1) layout.setContentsMargins(5, 10, 5, 5) table = QTableWidget(grp1) table.setEditTriggers(QTableWidget.NoEditTriggers) table.setSelectionMode(QTableWidget.SingleSelection) table.setSelectionBehavior(QTableWidget.SelectRows) table.setAlternatingRowColors(True) # table.setStyleSheet('font-size: 10px;') table.currentItemChanged.connect(self._on_plate_changed) self._table_plate = table layout.addWidget(table, 0, 0) self._update_plate_table() layout = QGridLayout(grp2) layout.setContentsMargins(5, 10, 5, 5) table = QTableWidget(grp2) table.setEditTriggers(QTableWidget.NoEditTriggers) table.setSelectionMode(QTableWidget.SingleSelection) table.setSelectionBehavior(QTableWidget.SelectRows) table.setAlternatingRowColors(True) # table.setStyleSheet('font-size: 10px;') table.currentItemChanged.connect(self._on_position_changed) self._table_position = table layout.addWidget(table, 0, 0) if self._imagecontainer.has_timelapse: grp3 = QGroupBox('Time', splitter) splitter.addWidget(grp3) layout = QGridLayout(grp3) layout.setContentsMargins(5, 10, 5, 5) table = QTableWidget(grp3) table.setEditTriggers(QTableWidget.NoEditTriggers) table.setSelectionMode(QTableWidget.SingleSelection) table.setSelectionBehavior(QTableWidget.SelectRows) table.setAlternatingRowColors(True) # table.setStyleSheet('font-size: 10px;') table.currentItemChanged.connect(self._on_time_changed) self._table_time = table layout.addWidget(table, 0, 0) splitter.addWidget(frame_info) def _update_plate_table(self): table = self._table_plate table.blockSignals(True) table.clearContents() column_names = ['Plate ID'] table.setColumnCount(len(column_names)) table.setHorizontalHeaderLabels(column_names) plates = self._imagecontainer.plates table.setRowCount(len(plates)) for idx, plate in enumerate(plates): item = QTableWidgetItem(plate) item.setData(0, plate) table.setItem(idx, 0, item) table.resizeColumnsToContents() table.resizeRowsToContents() table.blockSignals(False) def _update_time_table(self, meta_data, coordinate): coordinate = coordinate.copy() table = self._table_time table.blockSignals(True) table.clearContents() column_names = ['Frame'] if meta_data.has_timestamp_info: column_names += ['rel. t (min)', 'abs. t (GMT)'] table.setColumnCount(len(column_names)) table.setHorizontalHeaderLabels(column_names) table.setRowCount(len(meta_data.times)) for idx, time in enumerate(meta_data.times): item = QTableWidgetItem(str(time)) item.setData(0, time) table.setItem(idx, 0, item) if meta_data.has_timestamp_info: coordinate.time = time ts_rel = meta_data.get_timestamp_relative(coordinate) ts_abs = meta_data.get_timestamp_absolute(coordinate) if not numpy.isnan(ts_rel): info = '%.1f' % (ts_rel / 60) table.setItem(idx, 1, QTableWidgetItem(info)) if not numpy.isnan(ts_abs): info = time_lib.strftime("%Y-%m-%d %H:%M:%S", time_lib.gmtime(ts_abs)) table.setItem(idx, 2, QTableWidgetItem(info)) table.resizeColumnsToContents() table.resizeRowsToContents() table.blockSignals(False) def _update_position_table(self, meta_data): table = self._table_position table.blockSignals(True) table.clearContents() column_names = ['Position'] if meta_data.has_well_info: column_names += ['Well', 'Subwell'] if meta_data.has_condition_info: column_names.append('Condition') if meta_data.has_timestamp_info and meta_data.has_timelapse: column_names.append('Time-lapse') table.setColumnCount(len(column_names)) table.setHorizontalHeaderLabels(column_names) table.setRowCount(len(meta_data.positions)) for idx, pos in enumerate(meta_data.positions): item = QTableWidgetItem(pos) item.setData(0, pos) table.setItem(idx, 0, item) if 'Time-lapse' in column_names: column = column_names.index('Time-lapse') info = meta_data.get_timestamp_info(pos) info_str = '%.1fmin (%.1fs)' % (info[0] / 60, info[1]) table.setItem(idx, column, QTableWidgetItem(info_str)) if 'Well' in column_names: column = column_names.index('Well') well, subwell = meta_data.get_well_and_subwell(pos) if not well is None: table.setItem(idx, column, QTableWidgetItem(well)) if not subwell is None: table.setItem(idx, column+1, QTableWidgetItem("%02d" % subwell)) table.resizeColumnsToContents() table.resizeRowsToContents() table.blockSignals(False) def _update_info_frame(self, meta): txt = '<table>' \ '<tr><td align="right">Positions: </td><td>%s</td></tr>' \ '<tr><td align="right">Frames: </td><td>%d</td></tr>' % \ (meta.dim_p, meta.dim_t) txt += '<tr><td align="right">Channels: </td><td>%d (%s)</td></tr>' \ '<tr><td align="right">Z-slices: </td><td>%d</td></tr>' \ '<tr><td align="right">Width / Height: </td><td>%d x %d</td></tr>' \ '<tr><td colspan="2"></td></tr>' \ '<tr><td align="right">Image Files: </td><td>%d</td></tr>' % \ (meta.dim_c, meta.pixel_type, meta.dim_z, meta.dim_x, meta.dim_y, meta.image_files) txt += '<tr><td></td></tr>' if meta.has_timestamp_info and meta.has_timelapse: info = meta.plate_timestamp_info txt += \ '<tr><td align="right">Time-lapse info: </td><td>%.1f min (+/- %.1f s)</td></tr>' % \ (info[0]/60, info[1]) else: txt += '<tr><td align="right">Time-lapse info: </td><td>no</td></tr>' txt += '<tr><td align="right">Well info: </td><td>%s</td></tr>' % \ yesno(meta.has_well_info) txt += '<tr><td align="right">Condition info: </td><td>%s</td></tr>' % \ yesno(meta.has_condition_info) txt += '</table>' self._label_info.setText(txt) def initialize(self): self.coordinate_changed.connect(self.browser.on_coordinate_changed) coordinate = self.browser.get_coordinate() meta_data = self._imagecontainer.get_meta_data() self._update_position_table(meta_data) self._update_info_frame(meta_data) self._set_current_plate(coordinate.plate) self._set_current_position(coordinate.position) if self._imagecontainer.has_timelapse: self._update_time_table(meta_data, coordinate) self._set_current_time(coordinate.time) def nav_to_coordinate(self, coordinate): """ Set the browser coordinate to a coordinate and emit signal """ self._imagecontainer.set_plate(coordinate.plate) meta_data = self._imagecontainer.get_meta_data() self._set_current_plate(coordinate.plate) self._update_position_table(meta_data) self._set_current_position(coordinate.position) if self._imagecontainer.has_timelapse: self._update_time_table(meta_data, coordinate) self._set_current_time(coordinate.time) self._update_info_frame(meta_data) self.coordinate_changed.emit(coordinate) def nav_to_time(self, time): """ Set the browser coordinate to a coordinate and emit signal """ coordinate = self.browser.get_coordinate() coordinate.time = time self._set_time(coordinate, True) def nav_to_prev_position(self): coordinate = self.browser.get_coordinate() meta_data = self._imagecontainer.get_meta_data() pos = meta_data.positions idx = pos.index(coordinate.position) if idx > 0: coordinate.position = pos[idx-1] self._set_position(coordinate, True) def nav_to_next_position(self): coordinate = self.browser.get_coordinate() meta_data = self._imagecontainer.get_meta_data() pos = meta_data.positions idx = pos.index(coordinate.position) if idx < len(pos)-1: coordinate.position = pos[idx+1] self._set_position(coordinate, True) def nav_to_prev_plate(self): coordinate = self.browser.get_coordinate() plates = self._imagecontainer.plates idx = plates.index(coordinate.plate) if idx > 0: coordinate.plate = plates[idx-1] self._set_plate(coordinate, True) def nav_to_next_plate(self): coordinate = self.browser.get_coordinate() plates = self._imagecontainer.plates idx = plates.index(coordinate.plate) if idx < len(plates)-1: coordinate.plate = plates[idx+1] self._set_plate(coordinate, True) def _get_closeby_position(self, coordinate_old, coordinate_new): md_new = self._imagecontainer.get_meta_data() if coordinate_old.position in md_new.positions: coordinate_new.position = coordinate_old.position else: coordinate_new.position = md_new.positions[0] def _get_closeby_time(self, coordinate_old, coordinate_new): md_new = self._imagecontainer.get_meta_data() if coordinate_old.time in md_new.times: coordinate_new.time = coordinate_old.time else: coordinate_new.time = md_new.times[0] def _on_plate_changed(self, current, previous): coordinate_new = self.browser.get_coordinate() item = self._table_plate.item(current.row(), 0) plate = item.data(0) coordinate_new.plate = plate self._set_plate(coordinate_new) def _set_plate(self, coordinate_new, set_current=False): coordinate_old = self.browser.get_coordinate() plate = coordinate_new.plate func = lambda: self._imagecontainer.set_plate(plate) self.dlg = ProgressDialog("Loading plate...", None, 0, 0, self) self.dlg.exec_(func) meta_data = self._imagecontainer.get_meta_data() if set_current: self._set_current_plate(plate) self._update_position_table(meta_data) self._get_closeby_position(coordinate_old, coordinate_new) self._set_current_position(coordinate_new.position) if self._imagecontainer.has_timelapse: self._update_time_table(meta_data, coordinate_new) self._get_closeby_time(coordinate_old, coordinate_new) self._set_current_time(coordinate_new.time) self._update_info_frame(meta_data) self.coordinate_changed.emit(coordinate_new) def _on_position_changed(self, current, previous): coordinate = self.browser.get_coordinate() item = self._table_position.item(current.row(), 0) position = item.data(0) coordinate.position = position self._set_position(coordinate) def _set_position(self, coordinate, set_current=False): if set_current: self._set_current_position(coordinate.position) if self._imagecontainer.has_timelapse: meta_data = self._imagecontainer.get_meta_data() self._update_time_table(meta_data, coordinate) self._set_current_time(coordinate.time) self.coordinate_changed.emit(coordinate) def _on_time_changed(self, current, previous): coordinate = self.browser.get_coordinate() item = self._table_time.item(current.row(), 0) time = int(item.data(0)) coordinate.time = time self._set_time(coordinate) def _set_time(self, coordinate, set_current=False): if set_current: self._set_current_time(coordinate.time) self.coordinate_changed.emit(coordinate) def _set_current_plate(self, plate): self._table_plate.blockSignals(True) item = self._table_plate.findItems(plate, Qt.MatchExactly)[0] self._table_plate.setCurrentItem(item) self._table_plate.blockSignals(False) self._table_plate.scrollToItem(item) self._table_plate.update() def _set_current_position(self, position): self._table_position.blockSignals(True) item = self._table_position.findItems(position, Qt.MatchExactly)[0] self._table_position.setCurrentItem(item) self._table_position.blockSignals(False) self._table_position.scrollToItem(item) self._table_position.update() def _set_current_time(self, time): if self._imagecontainer.has_timelapse: self._table_time.blockSignals(True) item = self._table_time.findItems(str(time), Qt.MatchExactly)[0] self._table_time.setCurrentItem(item) self._table_time.blockSignals(False) self._table_time.scrollToItem(item) self._table_time.update()
def _load_image_container(self, plate_infos=None, scan_plates=None, show_dialog=True): self._clear_browser() if plate_infos is None: plate_infos = list(ImageContainer.iter_check_plates(self._settings)) imagecontainer = ImageContainer() self._imagecontainer = imagecontainer if scan_plates is None: scan_plates = dict((info[0], False) for info in plate_infos) def load(emitter, icontainer, settings, splates): iter_ = icontainer.iter_import_from_settings(settings, scan_plates=splates) for idx, info in enumerate(iter_): emitter.setValue.emit(idx) emitter.setLabelText.emit("checking dimensions...") emitter.setRange.emit(0, 0) QtCore.QCoreApplication.processEvents() if len(icontainer.plates) > 0: icontainer.set_plate(icontainer.plates[0]) icontainer.check_dimensions() label = ('Please wait until the input structure is scanned\n' 'or the structure data loaded...') self._dlg = ProgressDialog(label, None, 0, len(scan_plates), self) emitter = ProgressObject() emitter.setRange.connect(self._dlg.setRange) emitter.setValue.connect(self._dlg.setValue) emitter.setLabelText.connect(self._dlg.setLabelText) try: func = lambda: load(emitter, imagecontainer, self._settings, scan_plates) self._dlg.exec_(func, (emitter, )) except ImportError as e: # structure file from versions older than 1.3 contain pdk which is # removed if 'pdk' in str(e): QMessageBox.critical(self, "Error", ("Your structure file format is outdated.\n" "You have to rescan the plate(s)")) else: QMessageBox.critical(self, "Error", traceback.format_exc()) return except Exception as e: QMessageBox.critical(self, "Error", str(e)) try: # I hate lookup tables! self._tab_lookup['Cluster'][1].set_imagecontainer(imagecontainer) except KeyError: pass if len(imagecontainer.plates) > 0: channels = imagecontainer.channels # do not report value changes to the main window self._settings.set_notify_change(False) self.set_image_crop_size() problems = [] for prefix in ['primary', 'secondary', 'tertiary']: trait = self._settings.get_trait(SECTION_NAME_OBJECTDETECTION, '%s_channelid' % prefix) if trait.set_list_data(channels) is None: problems.append(prefix) self._tabs[1].get_widget('%s_channelid' % prefix).update() # report problems about a mismatch between channel IDs found in the data # and specified by the user if len(problems) > 0: # a mismatch between settings and data will cause changed settings self.settings_changed(True) trait = self._settings.get_trait(SECTION_NAME_EVENT_SELECTION, 'duration_unit') # allow time-base tracking durations only if time-stamp # information is present meta_data = imagecontainer.get_meta_data() if meta_data.has_timestamp_info: result = trait.set_list_data(TimeConverter.units) else: result = trait.set_list_data([TimeConverter.FRAMES]) if result is None: QMessageBox.critical(self, "Could not set tracking duration units", ("The tracking duration units selected to match the " "load data. Please check your settings.")) # a mismatch between settings and data will cause changed settings self.settings_changed(True) # activate change notification again self._settings.set_notify_change(True) self.set_modules_active(state=True) if show_dialog: QMessageBox.information( self, "Information", "%d plate(s) successfully loaded." % len(imagecontainer.plates)) else: QMessageBox.critical(self, "Error", ("No images found\n" "Verifiy your nameing scheme and rescan the data."))