class Config(SignalNode.Config): """Config widget displayed for ArtificialDelay.""" def __init__(self, parent=None): super().__init__(parent=parent) self.delay = QSpinBox() self.delay.setSuffix(" ms") self.delay.setMinimum(0) self.delay.setMaximum(1000000) self.delay.valueChanged.connect(self.updateModel) layout = QFormLayout() self.setLayout(layout) layout.addRow("Delay:", self.delay) def updateModel(self): n = self.node() if n is None: return n.setDelay(self.delay.value()) def updateView(self): n = self.node() if n is None: return self.delay.blockSignals(True) self.delay.setValue(n.delay()) self.delay.blockSignals(False)
class LabeledSpinBox(QWidget): valueChanged = Signal(int) def __init__(self, label_string, minimum=1, maximum=100, starting_value=0): super(LabeledSpinBox, self).__init__() self.spinbox = QSpinBox() self.spinbox.setRange(minimum, maximum) self.spinbox.setSuffix('/{}'.format(maximum)) self.spinbox.setValue(starting_value) self.spinbox.valueChanged.connect(self._valueChanged) self.label = QLabel() self.label.setText(label_string) SpinBoxLayout = QHBoxLayout(self) SpinBoxLayout.addWidget(self.label) SpinBoxLayout.addWidget(self.spinbox) def setValue(self, value, quiet=False): if quiet: self.spinbox.blockSignals(True) self.spinbox.setValue(value) self.spinbox.blockSignals(False) else: self.spinbox.setValue(value) def value(self): return self.spinbox.value() def setRange(self, minimum, maximum): self.spinbox.setRange(minimum, maximum) self.spinbox.setSuffix("/{}".format(maximum)) def _valueChanged(self, value): self.valueChanged.emit(value)
class ElaWidget(ToolWidget): def __init__(self, image, parent=None): super(ElaWidget, self).__init__(parent) params_layout = QHBoxLayout() params_layout.addWidget(QLabel(self.tr('Quality:'))) self.quality_spin = QSpinBox() self.quality_spin.setRange(0, 100) self.quality_spin.setSuffix(self.tr(' %')) self.quality_spin.valueChanged.connect(self.process) params_layout.addWidget(self.quality_spin) params_layout.addWidget(QLabel(self.tr('Scale:'))) self.scale_spin = QSpinBox() self.scale_spin.setRange(1, 100) self.scale_spin.valueChanged.connect(self.process) params_layout.addWidget(self.scale_spin) self.equalize_check = QCheckBox(self.tr('Equalized')) self.equalize_check.stateChanged.connect(self.process) params_layout.addWidget(self.equalize_check) params_layout.addStretch() default_button = QPushButton(self.tr('Default')) default_button.clicked.connect(self.default) params_layout.addWidget(default_button) self.image = image self.viewer = ImageViewer(self.image, self.image) self.default() self.process() main_layout = QVBoxLayout() main_layout.addLayout(params_layout) main_layout.addWidget(self.viewer) self.setLayout(main_layout) def process(self): equalize = self.equalize_check.isChecked() self.scale_spin.setEnabled(not equalize) start = time() quality = self.quality_spin.value() scale = self.scale_spin.value() compressed = compress_jpeg(self.image, quality) if not equalize: ela = cv.convertScaleAbs(cv.subtract(compressed, self.image), None, scale) else: ela = cv.merge([ cv.equalizeHist(c) for c in cv.split(cv.absdiff(compressed, self.image)) ]) self.viewer.update_processed(ela) self.info_message.emit( self.tr('Error Level Analysis = {}'.format(elapsed_time(start)))) def default(self): self.quality_spin.setValue(75) self.scale_spin.setValue(20)
class ParamSlider(QWidget): valueChanged = Signal(int) def __init__(self, interval, ticks=10, reset=0, suffix=None, label=None, bold=False, parent=None): super(ParamSlider, self).__init__(parent) self.slider = QSlider(Qt.Horizontal) self.slider.setRange(interval[0], interval[1]) self.slider.setTickPosition(QSlider.TicksBelow) self.slider.setTickInterval((interval[1] - interval[0] + 1) / ticks) self.slider.setSingleStep(1) self.slider.setPageStep(1) self.slider.setValue(reset) self.slider.mouseDoubleClickEvent = self.doubleClicked self.spin = QSpinBox() self.spin.setRange(interval[0], interval[1]) self.spin.setValue(reset) self.spin.setSuffix(suffix) self.spin.setFixedWidth(50) self.reset = reset self.slider.valueChanged.connect(self.spin.setValue) self.spin.valueChanged.connect(self.slider.setValue) self.slider.valueChanged.connect(self.valueChanged) layout = QHBoxLayout() if label is not None: lab = QLabel(label) modify_font(lab, bold=bold) layout.addWidget(lab) layout.addWidget(self.slider) layout.addWidget(self.spin) self.setLayout(layout) self.setMaximumWidth(200) def doubleClicked(self, _): self.slider.setValue(self.reset) self.spin.setValue(self.reset) def value(self): return self.slider.value() def setValue(self, value): self.spin.setValue(value) self.slider.setValue(value) self.valueChanged.emit(value) def sync(self): self.spin.setValue(self.slider.value()) self.slider.setValue(self.spin.value()) self.valueChanged.emit(self.slider.value())
class EchoWidget(ToolWidget): def __init__(self, image, parent=None): super(EchoWidget, self).__init__(parent) self.radius_spin = QSpinBox() self.radius_spin.setRange(1, 15) self.radius_spin.setSuffix(self.tr(' px')) self.radius_spin.setValue(2) self.radius_spin.setToolTip(self.tr('Laplacian filter radius')) self.contrast_spin = QSpinBox() self.contrast_spin.setRange(0, 100) self.contrast_spin.setSuffix(self.tr(' %')) self.contrast_spin.setValue(85) self.contrast_spin.setToolTip(self.tr('Output tonality compression')) self.gray_check = QCheckBox(self.tr('Grayscale')) self.gray_check.setToolTip(self.tr('Desaturated output mode')) self.image = image self.viewer = ImageViewer(self.image, self.image, None) self.process() self.radius_spin.valueChanged.connect(self.process) self.contrast_spin.valueChanged.connect(self.process) self.gray_check.stateChanged.connect(self.process) params_layout = QHBoxLayout() params_layout.addWidget(QLabel(self.tr('Radius:'))) params_layout.addWidget(self.radius_spin) params_layout.addWidget(QLabel(self.tr('Contrast:'))) params_layout.addWidget(self.contrast_spin) params_layout.addWidget(self.gray_check) params_layout.addStretch() main_layout = QVBoxLayout() main_layout.addLayout(params_layout) main_layout.addWidget(self.viewer) self.setLayout(main_layout) def process(self): start = time() kernel = 2 * self.radius_spin.value() + 1 contrast = int(self.contrast_spin.value() / 100 * 255) lut = create_lut(0, contrast) laplace = [] for channel in cv.split(self.image): deriv = np.fabs(cv.Laplacian(channel, cv.CV_64F, None, kernel)) deriv = cv.normalize(deriv, None, 0, 255, cv.NORM_MINMAX, cv.CV_8UC1) laplace.append(cv.LUT(deriv, lut)) result = cv.merge(laplace) if self.gray_check.isChecked(): result = bgr_to_gray3(result) self.viewer.update_processed(result) self.info_message.emit('Echo Edge Filter = {}'.format( elapsed_time(start)))
def __init__(self): super().__init__() widget = QSpinBox() # QDoubleSpinBox() widget.setRange(-10, 10) widget.setPrefix("$") widget.setSuffix("c") widget.setSingleStep(1) # 0.1 widget.valueChanged.connect(self.value_changed) widget.valueChanged[str].connect(self.value_changed_str) self.setCentralWidget(widget)
class NetPositionUI(QWidget): def __init__(self, *args, **kwargs): super(NetPositionUI, self).__init__(*args, **kwargs) main_layout = QVBoxLayout() main_layout.setContentsMargins(QMargins(2, 1, 2, 1)) main_layout.setSpacing(1) # 操作栏 opt_layout = QHBoxLayout() self.interval_days = QSpinBox(self) self.interval_days.setMinimum(1) self.interval_days.setMaximum(30) self.interval_days.setValue(5) self.interval_days.setPrefix("日期间隔 ") self.interval_days.setSuffix(" 天") opt_layout.addWidget(self.interval_days) self.query_button = QPushButton('确定', self) opt_layout.addWidget(self.query_button) self.tip_label = QLabel('左侧可选择间隔天数,确定查询数据. ', self) opt_layout.addWidget(self.tip_label) opt_layout.addStretch() main_layout.addLayout(opt_layout) # 显示数据的表 self.data_table = QTableWidget(self) self.data_table.setFrameShape(QFrame.NoFrame) self.data_table.setEditTriggers(QAbstractItemView.NoEditTriggers) # 不可编辑 self.data_table.setFocusPolicy(Qt.NoFocus) # 去选中时的虚线框 self.data_table.setAlternatingRowColors(True) # 交替行颜色 self.data_table.horizontalHeader().setDefaultSectionSize(85) # 默认的标题头宽 self.data_table.verticalHeader().hide() self.data_table.verticalHeader().setDefaultSectionSize(18) # 设置行高(与下行代码同时才生效) self.data_table.verticalHeader().setMinimumSectionSize(18) main_layout.addWidget(self.data_table) self.setLayout(main_layout) self.tip_label.setObjectName("tipLabel") self.data_table.setObjectName("dataTable") self.data_table.horizontalHeader().setStyleSheet("QHeaderView::section," "QTableCornerButton::section{height:25px;background-color:rgb(243,245,248);font-weight:bold;font-size:13px}") self.setStyleSheet( "#tipLabel{color:rgb(230,50,50);font-weight:bold;}" "#dataTable::item{padding:2px}" "#dataTable{selection-color:rgb(255,255,255);selection-background-color:rgb(51,143,255);alternate-background-color:rgb(245,250,248)}" )
def __init__(self): super().__init__() self.setWindowTitle("Line edit") widget = QSpinBox() # QDoubleSpinBox is for floats widget.setMinimum(-10) widget.setMaximum(3) # or: widget.setRange(-10, 3) widget.setPrefix("$") widget.setSuffix("c") widget.setSingleStep(3) widget.valueChanged.connect(self.value_changed) widget.valueChanged[str].connect(self.value_changed_str) self.setCentralWidget(widget)
def __init__(self): super().__init__() self.setWindowTitle("My App") widget = QSpinBox() # Or: widget = QDoubleSpinBox() widget.setMinimum(-10) widget.setMaximum(3) # Or: widget.setRange(-10,3) widget.setPrefix("$") widget.setSuffix("c") widget.setSingleStep(3) # Or e.g. 0.5 for QDoubleSpinBox widget.valueChanged.connect(self.value_changed) widget.textChanged.connect(self.value_changed_str) self.setCentralWidget(widget)
class Canvas (QWidget): def __init__(self): QWidget.__init__(self) self.file = "mug.webp" self.__img = cv2.imread(self.file) self.__mask = np.zeros(1) self.original = cv2.imread(self.file) self.__thirdChannelMask = np.dstack((self.__mask, self.__mask, self.__mask)) self.__nseg = 1 self.__sig = 1 self.__comp = 1 self.nSlider = QSlider(orientation=Qt.Horizontal) self.sigSlider = QSlider(orientation=Qt.Horizontal) self.thicSlider = QSlider(orientation=Qt.Horizontal) self.resize_spinbox = QSpinBox(self) self.resize_spinbox.setRange(1, 100) self.resize_spinbox.setValue(100) self.resize_spinbox.setSuffix(" %") self.double_spin_width = QDoubleSpinBox(self) self.double_spin_width.setSuffix(" px") self.double_spin_width.setValue(0) self.double_spin_width.setRange(1, 2000) self.double_spin_height = QDoubleSpinBox(self) self.double_spin_height.setSuffix(" px") self.double_spin_height.setValue(0) self.double_spin_height.setRange(1, 2000) self.zeroModeCheck = QCheckBox("Usar SLIC0") self.__highlightcolor = QColor(255, 255, 255) self.__transparency = 0.5 self.__AllColors = [self.__highlightcolor.toTuple()[:3]] nLabel = QLabel("Numero de segmentos:") sigLabel = QLabel("Sigma:") thicLabel = QLabel("Compactação:") resizeLabel = QLabel("Fator de resize da image:") makssizeLabel = QLabel("Dimensão da mascara de saída:") self.__label = QLabel() nLabel.setToolTip("O número aproximado de labels da imagem segmentada") sigLabel.setToolTip("A largura da Gaussiana") thicLabel.setToolTip("Equilibra a proximidade das cores e a proximidade do espaço, maiores valores tornam os Superpixels mais quadrados") self.nSlider.setMinimum(1) self.nSlider.setMaximum(100) self.sigSlider.setMinimum(1) self.sigSlider.setMaximum(100) self.thicSlider.setMinimum(1) self.thicSlider.setMaximum(100) glayout1 = QGridLayout() glayout1.addWidget(nLabel, 0, 0) glayout1.addWidget(self.nSlider, 0, 1) glayout1.addWidget(sigLabel, 1, 0) glayout1.addWidget(self.sigSlider, 1, 1) glayout1.addWidget(thicLabel, 2, 0) glayout1.addWidget(self.thicSlider, 2, 1) glayout2 = QGridLayout() glayout2.addWidget(resizeLabel, 0, 0) glayout2.addWidget(self.resize_spinbox, 0, 1) glayout2.addWidget(self.zeroModeCheck, 0, 2) glayout2.addWidget(makssizeLabel, 1, 0) glayout2.addWidget(self.double_spin_width, 1, 1) glayout2.addWidget(self.double_spin_height, 1, 2) glayout2.setColumnStretch(3, 1) self.__label.setAlignment(Qt.AlignLeft | Qt.AlignTop) mainlayout = QVBoxLayout() mainlayout.addLayout(glayout1) mainlayout.addLayout(glayout2) mainlayout.addStretch(1) mainlayout.addWidget(self.__label) mainlayout.addStretch(1) mainlayout.setAlignment(Qt.AlignCenter) self.setLayout(mainlayout) self.nSlider.sliderReleased.connect(self.onNsegChange) self.sigSlider.sliderReleased.connect(self.onSigChange) self.thicSlider.sliderReleased.connect(self.onCompChange) self.__label.mousePressEvent = self.Highlight self.resize_spinbox.valueChanged.connect(self.Resize) self.open_image(self.__img) def getBackground(self): mask = self.__thirdChannelMask.copy() mask_r = mask[:, :, 2] mask_g = mask[:, :, 1] mask_b = mask[:, :, 0] offImage = list() for color in self.__AllColors: b_off = mask_b != color[2] g_off = mask_g != color[1] r_off = mask_r != color[0] aux = np.logical_and(b_off, g_off) offImage.append(np.logical_and(aux, r_off)) final = offImage[0] for cut in offImage: final = np.logical_or(final, cut) return final def changeImage(self): self.__mask = slic(self.__img, n_segments=self.__nseg, compactness=self.__comp, sigma=self.__sig, convert2lab=True, slic_zero=self.zeroModeCheck.isChecked()) mask = self.__mask.copy() mask = np.dstack((mask, mask, mask)) mask = img_as_ubyte(mask) self.__thirdChannelMask = mask img = cv2.addWeighted(self.__img, 1, mask, 0.5, 0) marc_img = mark_boundaries(img, self.__mask) self.open_image(marc_img) def load_image(self): self.__img = cv2.imread(self.file) self.original = self.__img self.double_spin_width.setValue(self.__img.shape[1]) self.double_spin_height.setValue(self.__img.shape[0]) val = self.resize_spinbox.value() newDim = int(self.__img.shape[1]*val/100), int(self.__img.shape[0]*val/100) self.__img = cv2.resize(self.__img, newDim) self.open_image(self.__img) def open_image(self, img): if img.shape[2] == 4: qformat = QImage.Format_RGBA8888 else: qformat = QImage.Format_RGB888 copy = img_as_ubyte(img) qimg = QImage(copy.data, copy.shape[1], copy.shape[0], copy.strides[0], qformat).rgbSwapped() pixmap = QPixmap.fromImage(qimg) self.__label.setPixmap(pixmap) self.__label.adjustSize() @Slot() def onNsegChange(self): self.__nseg = self.nSlider.value() self.changeImage() @Slot() def onSigChange(self): self.__sig = self.sigSlider.value() self.changeImage() @Slot() def onCompChange(self): self.__comp = self.thicSlider.value() self.changeImage() @Slot() def onFileOpen(self): self.thicSlider.setValue(1) self.nSlider.setValue(1) self.sigSlider.setValue(1) diag = QFileDialog() file = diag.getOpenFileName()[0] if file != "": self.file = file self.load_image() @Slot() def onSaveFile(self): diag = QFileDialog() file = diag.getSaveFileName()[0] if self.file != "": self.__label.pixmap().save(file) @Slot() def onSaveMask(self): diag = QFileDialog() file = diag.getSaveFileName()[0] final_img = cv2.resize(self.__mask, (self.double_spin_width.value(), self.double_spin_height.value())) if file != "": cv2.imwrite(file, final_img) @Slot() def Highlight(self, e): if e.x() < 0 or e.x() > self.__img.shape[1] or e.y() < 0 or e.y() > self.__img.shape[0]: return self.__mask = flood_fill(self.__mask, (e.y(), e.x()), 255) self.__thirdChannelMask[:, :, 2] = flood_fill(self.__thirdChannelMask[:, :, 2], (e.y(), e.x()), self.__highlightcolor.red()) self.__thirdChannelMask[:, :, 1] = flood_fill(self.__thirdChannelMask[:, :, 1], (e.y(), e.x()), self.__highlightcolor.green()) self.__thirdChannelMask[:, :, 0] = flood_fill(self.__thirdChannelMask[:, :, 0], (e.y(), e.x()), self.__highlightcolor.blue()) img = cv2.addWeighted(self.__img, 1, self.__thirdChannelMask, self.__transparency, 0) marc_img = mark_boundaries(img, self.__mask) self.open_image(marc_img) @Slot() def exportBinary(self): diag = QFileDialog() file = diag.getSaveFileName()[0] mask = self.__thirdChannelMask.copy() final = self.getBackground() b = mask[:, :, 0] g = mask[:, :, 1] r = mask[:, :, 2] b[final] = 0 g[final] = 0 r[final] = 0 final_img = cv2.resize(mask, (int(self.double_spin_width.value()), int(self.double_spin_height.value()))) if file != "": cv2.imwrite(file, final_img) @Slot() def onRemoveBackgroud(self): box = QMessageBox() box.setText("Selecione a cor do background") box.setIcon(QMessageBox.Information) box.exec() diag = QColorDialog() backColor = diag.getColor() final = self.getBackground() b = self.__img[:, :, 0] g = self.__img[:, :, 1] r = self.__img[:, :, 2] b[final] = backColor.blue() g[final] = backColor.green() r[final] = backColor.red() self.open_image(self.__img) @Slot() def Resize(self): val = self.resize_spinbox.value() newDim = int(self.original.shape[1] * val / 100), int(self.original.shape[0] * val / 100) self.__img = cv2.resize(self.original, newDim) self.open_image(self.__img) @Slot() def setHighlightColor(self, color): self.__highlightcolor = color @Slot() def getAllColors(self, colors): self.__AllColors = colors @Slot() def setTran(self, value): self.__transparency = 1- value/100 @Slot() def onUndo(self): self.thicSlider.setValue(1) self.nSlider.setValue(1) self.sigSlider.setValue(1) self.onNsegChange() self.onSigChange() self.onCompChange() self.__img = self.original self.open_image(self.__img)
class MagnifierWidget(ToolWidget): def __init__(self, image, parent=None): super(MagnifierWidget, self).__init__(parent) self.equalize_radio = QRadioButton(self.tr('Equalization')) self.contrast_radio = QRadioButton(self.tr('Auto Contrast')) self.retinex_radio = QRadioButton(self.tr('Human Retina')) self.centile_spin = QSpinBox() self.centile_spin.setRange(0, 100) self.centile_spin.setValue(20) self.centile_spin.setSuffix(self.tr(' %')) self.channel_check = QCheckBox(self.tr('By channel')) self.equalize_radio.setChecked(True) self.last_radio = self.equalize_radio self.image = image self.viewer = ImageViewer(self.image, self.image) self.change() self.viewer.view_changed.connect(self.process) self.equalize_radio.toggled.connect(self.change) self.contrast_radio.toggled.connect(self.change) self.centile_spin.valueChanged.connect(self.change) self.channel_check.stateChanged.connect(self.change) self.retinex_radio.toggled.connect(self.change) top_layout = QHBoxLayout() top_layout.addWidget(QLabel(self.tr('Enhancement:'))) top_layout.addWidget(self.equalize_radio) top_layout.addWidget(self.contrast_radio) top_layout.addWidget(self.centile_spin) top_layout.addWidget(self.channel_check) top_layout.addWidget(self.retinex_radio) top_layout.addStretch() main_layout = QVBoxLayout() main_layout.addLayout(top_layout) main_layout.addWidget(self.viewer) self.setLayout(main_layout) def process(self, rect): y1 = rect.top() y2 = rect.bottom() x1 = rect.left() x2 = rect.right() roi = self.image[y1:y2, x1:x2] if self.equalize_radio.isChecked(): roi = cv.merge([cv.equalizeHist(c) for c in cv.split(roi)]) self.last_radio = self.equalize_radio elif self.contrast_radio.isChecked(): centile = self.centile_spin.value() / 200 if self.channel_check.isChecked(): roi = cv.merge([cv.LUT(c, auto_lut(c, centile)) for c in cv.split(roi)]) else: roi = cv.LUT(roi, auto_lut(cv.cvtColor(roi, cv.COLOR_BGR2GRAY), centile)) self.last_radio = self.contrast_radio elif self.retinex_radio.isChecked(): self.last_radio = self.retinex_radio else: self.last_radio.setChecked(True) return processed = np.copy(self.image) processed[y1:y2, x1:x2] = roi self.viewer.update_processed(processed) def change(self): self.process(self.viewer.get_rect())
class NoiseWidget(ToolWidget): def __init__(self, image, parent=None): super(NoiseWidget, self).__init__(parent) self.mode_combo = QComboBox() self.mode_combo.addItems([ self.tr('Median'), self.tr('Gaussian'), self.tr('BoxBlur'), self.tr('Bilateral'), self.tr('NonLocal') ]) self.radius_spin = QSpinBox() self.radius_spin.setRange(1, 10) self.radius_spin.setSuffix(self.tr(' px')) self.radius_spin.setValue(1) self.sigma_spin = QSpinBox() self.sigma_spin.setRange(1, 200) self.sigma_spin.setValue(3) self.levels_spin = QSpinBox() self.levels_spin.setRange(0, 255) self.levels_spin.setSpecialValueText(self.tr('Equalized')) self.levels_spin.setValue(32) self.gray_check = QCheckBox(self.tr('Grayscale')) self.denoised_check = QCheckBox(self.tr('Denoised')) self.image = image self.viewer = ImageViewer(self.image, self.image) self.process() params_layout = QHBoxLayout() params_layout.addWidget(QLabel(self.tr('Mode:'))) params_layout.addWidget(self.mode_combo) params_layout.addWidget(QLabel(self.tr('Radius:'))) params_layout.addWidget(self.radius_spin) params_layout.addWidget(QLabel(self.tr('Sigma:'))) params_layout.addWidget(self.sigma_spin) params_layout.addWidget(QLabel(self.tr('Levels:'))) params_layout.addWidget(self.levels_spin) params_layout.addWidget(self.gray_check) params_layout.addWidget(self.denoised_check) params_layout.addStretch() main_layout = QVBoxLayout() main_layout.addLayout(params_layout) main_layout.addWidget(self.viewer) self.setLayout(main_layout) self.mode_combo.currentTextChanged.connect(self.process) self.radius_spin.valueChanged.connect(self.process) self.sigma_spin.valueChanged.connect(self.process) self.levels_spin.valueChanged.connect(self.process) self.gray_check.stateChanged.connect(self.process) self.denoised_check.stateChanged.connect(self.process) def process(self): start = time() grayscale = self.gray_check.isChecked() if grayscale: original = cv.cvtColor(self.image, cv.COLOR_BGR2GRAY) else: original = self.image radius = self.radius_spin.value() kernel = radius * 2 + 1 sigma = self.sigma_spin.value() choice = self.mode_combo.currentText() if choice == self.tr('Median'): self.sigma_spin.setEnabled(False) denoised = cv.medianBlur(original, kernel) elif choice == self.tr('Gaussian'): self.sigma_spin.setEnabled(False) denoised = cv.GaussianBlur(original, (kernel, kernel), 0) elif choice == self.tr('BoxBlur'): self.sigma_spin.setEnabled(False) denoised = cv.blur(original, (kernel, kernel)) elif choice == self.tr('Bilateral'): self.sigma_spin.setEnabled(True) denoised = cv.bilateralFilter(original, kernel, sigma, sigma) elif choice == self.tr('NonLocal'): if grayscale: denoised = cv.fastNlMeansDenoising(original, None, kernel) else: denoised = cv.fastNlMeansDenoisingColored( original, None, kernel, kernel) else: denoised = None if self.denoised_check.isChecked(): self.levels_spin.setEnabled(False) result = denoised else: self.levels_spin.setEnabled(True) noise = cv.absdiff(original, denoised) levels = self.levels_spin.value() if levels == 0: if grayscale: result = cv.equalizeHist(noise) else: result = equalize_img(noise) else: result = cv.LUT(noise, create_lut(0, 255 - levels)) if grayscale: result = cv.cvtColor(result, cv.COLOR_GRAY2BGR) self.viewer.update_processed(result) self.info_message.emit( self.tr('Noise estimation = {}'.format(elapsed_time(start))))
def setup_beam_configuration_elements(self): def _add_header(text: str, col: int): label = QLabel(self.beam_configuration_group) label.setText(text) self.beam_configuration_layout.addWidget(label, 0, col) _add_header("Ammo A", 1) _add_header("Ammo B", 2) _add_header("Uncharged", 3) _add_header("Charged", 4) _add_header("Combo", 5) _add_header("Missiles for Combo", 6) self._beam_ammo_a = {} self._beam_ammo_b = {} self._beam_uncharged = {} self._beam_charged = {} self._beam_combo = {} self._beam_missile = {} def _create_ammo_combo(): combo = QComboBox(self.beam_configuration_group) combo.addItem("None", -1) combo.addItem("Power Bomb", 43) combo.addItem("Missile", 44) combo.addItem("Dark Ammo", 45) combo.addItem("Light Ammo", 46) return combo row = 1 for beam, beam_name in _BEAMS.items(): label = QLabel(self.beam_configuration_group) label.setText(beam_name) self.beam_configuration_layout.addWidget(label, row, 0) ammo_a = _create_ammo_combo() ammo_a.currentIndexChanged.connect( functools.partial(self._on_ammo_type_combo_changed, beam, ammo_a, False)) self._beam_ammo_a[beam] = ammo_a self.beam_configuration_layout.addWidget(ammo_a, row, 1) ammo_b = _create_ammo_combo() ammo_b.currentIndexChanged.connect( functools.partial(self._on_ammo_type_combo_changed, beam, ammo_b, True)) self._beam_ammo_b[beam] = ammo_b self.beam_configuration_layout.addWidget(ammo_b, row, 2) spin_box = QSpinBox(self.beam_configuration_group) spin_box.setSuffix(" ammo") spin_box.setMaximum(250) spin_box.valueChanged.connect( functools.partial(self._on_ammo_cost_spin_changed, beam, "uncharged_cost")) self._beam_uncharged[beam] = spin_box self.beam_configuration_layout.addWidget(spin_box, row, 3) spin_box = QSpinBox(self.beam_configuration_group) spin_box.setSuffix(" ammo") spin_box.setMaximum(250) spin_box.valueChanged.connect( functools.partial(self._on_ammo_cost_spin_changed, beam, "charged_cost")) self._beam_charged[beam] = spin_box self.beam_configuration_layout.addWidget(spin_box, row, 4) spin_box = QSpinBox(self.beam_configuration_group) spin_box.setSuffix(" ammo") spin_box.setMaximum(250) spin_box.valueChanged.connect( functools.partial(self._on_ammo_cost_spin_changed, beam, "combo_ammo_cost")) self._beam_combo[beam] = spin_box self.beam_configuration_layout.addWidget(spin_box, row, 5) spin_box = QSpinBox(self.beam_configuration_group) spin_box.setSuffix(" missile") spin_box.setMaximum(250) spin_box.setMinimum(1) spin_box.valueChanged.connect( functools.partial(self._on_ammo_cost_spin_changed, beam, "combo_missile_cost")) self._beam_missile[beam] = spin_box self.beam_configuration_layout.addWidget(spin_box, row, 6) row += 1
class DomainDock(PlotterDock): """ Domain options dock """ def __init__(self, model, font_metric, parent=None): super().__init__(model, font_metric, parent) self.setAllowedAreas(QtCore.Qt.LeftDockWidgetArea) # Create Controls self._createOriginBox() self._createOptionsBox() self._createResolutionBox() # Create submit button self.applyButton = QPushButton("Apply Changes") # Mac bug fix self.applyButton.setMinimumHeight(self.font_metric.height() * 1.6) self.applyButton.clicked.connect(self.main_window.applyChanges) # Create Zoom box self.zoomBox = QSpinBox() self.zoomBox.setSuffix(' %') self.zoomBox.setRange(25, 2000) self.zoomBox.setValue(100) self.zoomBox.setSingleStep(25) self.zoomBox.valueChanged.connect(self.main_window.editZoom) self.zoomLayout = QHBoxLayout() self.zoomLayout.addWidget(QLabel('Zoom:')) self.zoomLayout.addWidget(self.zoomBox) self.zoomLayout.setContentsMargins(0, 0, 0, 0) self.zoomWidget = QWidget() self.zoomWidget.setLayout(self.zoomLayout) # Create Layout self.dockLayout = QVBoxLayout() self.dockLayout.addWidget(QLabel("Geometry/Properties")) self.dockLayout.addWidget(HorizontalLine()) self.dockLayout.addWidget(self.originGroupBox) self.dockLayout.addWidget(self.optionsGroupBox) self.dockLayout.addWidget(self.resGroupBox) self.dockLayout.addWidget(HorizontalLine()) self.dockLayout.addWidget(self.zoomWidget) self.dockLayout.addWidget(HorizontalLine()) self.dockLayout.addStretch() self.dockLayout.addWidget(self.applyButton) self.dockLayout.addWidget(HorizontalLine()) self.optionsWidget = QWidget() self.optionsWidget.setLayout(self.dockLayout) self.setWidget(self.optionsWidget) def _createOriginBox(self): # X Origin self.xOrBox = QDoubleSpinBox() self.xOrBox.setDecimals(9) self.xOrBox.setRange(-99999, 99999) xbox_connector = partial(self.main_window.editSingleOrigin, dimension=0) self.xOrBox.valueChanged.connect(xbox_connector) # Y Origin self.yOrBox = QDoubleSpinBox() self.yOrBox.setDecimals(9) self.yOrBox.setRange(-99999, 99999) ybox_connector = partial(self.main_window.editSingleOrigin, dimension=1) self.yOrBox.valueChanged.connect(ybox_connector) # Z Origin self.zOrBox = QDoubleSpinBox() self.zOrBox.setDecimals(9) self.zOrBox.setRange(-99999, 99999) zbox_connector = partial(self.main_window.editSingleOrigin, dimension=2) self.zOrBox.valueChanged.connect(zbox_connector) # Origin Form Layout self.orLayout = QFormLayout() self.orLayout.addRow('X:', self.xOrBox) self.orLayout.addRow('Y:', self.yOrBox) self.orLayout.addRow('Z:', self.zOrBox) self.orLayout.setLabelAlignment(QtCore.Qt.AlignLeft) self.orLayout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow) # Origin Group Box self.originGroupBox = QGroupBox('Origin') self.originGroupBox.setLayout(self.orLayout) def _createOptionsBox(self): # Width self.widthBox = QDoubleSpinBox(self) self.widthBox.setRange(.1, 99999) self.widthBox.setDecimals(9) self.widthBox.valueChanged.connect(self.main_window.editWidth) # Height self.heightBox = QDoubleSpinBox(self) self.heightBox.setRange(.1, 99999) self.heightBox.setDecimals(9) self.heightBox.valueChanged.connect(self.main_window.editHeight) # ColorBy self.colorbyBox = QComboBox(self) self.colorbyBox.addItem("material") self.colorbyBox.addItem("cell") self.colorbyBox.addItem("temperature") self.colorbyBox.addItem("density") self.colorbyBox.currentTextChanged[str].connect( self.main_window.editColorBy) # Universe level (applies to cell coloring only) self.universeLevelBox = QComboBox(self) self.universeLevelBox.addItem('all') for i in range(self.model.max_universe_levels): self.universeLevelBox.addItem(str(i)) self.universeLevelBox.currentTextChanged[str].connect( self.main_window.editUniverseLevel) # Alpha self.domainAlphaBox = QDoubleSpinBox(self) self.domainAlphaBox.setValue(self.model.activeView.domainAlpha) self.domainAlphaBox.setSingleStep(0.05) self.domainAlphaBox.setDecimals(2) self.domainAlphaBox.setRange(0.0, 1.0) self.domainAlphaBox.valueChanged.connect(self.main_window.editPlotAlpha) # Visibility self.visibilityBox = QCheckBox(self) self.visibilityBox.stateChanged.connect( self.main_window.editPlotVisibility) # Outlines self.outlinesBox = QCheckBox(self) self.outlinesBox.stateChanged.connect(self.main_window.toggleOutlines) # Basis self.basisBox = QComboBox(self) self.basisBox.addItem("xy") self.basisBox.addItem("xz") self.basisBox.addItem("yz") self.basisBox.currentTextChanged.connect(self.main_window.editBasis) # Advanced Color Options self.colorOptionsButton = QPushButton('Color Options...') self.colorOptionsButton.setMinimumHeight(self.font_metric.height() * 1.6) self.colorOptionsButton.clicked.connect(self.main_window.showColorDialog) # Options Form Layout self.opLayout = QFormLayout() self.opLayout.addRow('Width:', self.widthBox) self.opLayout.addRow('Height:', self.heightBox) self.opLayout.addRow('Basis:', self.basisBox) self.opLayout.addRow('Color By:', self.colorbyBox) self.opLayout.addRow('Universe Level:', self.universeLevelBox) self.opLayout.addRow('Plot alpha:', self.domainAlphaBox) self.opLayout.addRow('Visible:', self.visibilityBox) self.opLayout.addRow('Outlines:', self.outlinesBox) self.opLayout.addRow(self.colorOptionsButton) self.opLayout.setLabelAlignment(QtCore.Qt.AlignLeft) self.opLayout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow) # Options Group Box self.optionsGroupBox = QGroupBox('Options') self.optionsGroupBox.setLayout(self.opLayout) def _createResolutionBox(self): # Horizontal Resolution self.hResBox = QSpinBox(self) self.hResBox.setRange(1, 99999) self.hResBox.setSingleStep(25) self.hResBox.setSuffix(' px') self.hResBox.valueChanged.connect(self.main_window.editHRes) # Vertical Resolution self.vResLabel = QLabel('Pixel Height:') self.vResBox = QSpinBox(self) self.vResBox.setRange(1, 99999) self.vResBox.setSingleStep(25) self.vResBox.setSuffix(' px') self.vResBox.valueChanged.connect(self.main_window.editVRes) # Ratio checkbox self.ratioCheck = QCheckBox("Fixed Aspect Ratio", self) self.ratioCheck.stateChanged.connect(self.main_window.toggleAspectLock) # Resolution Form Layout self.resLayout = QFormLayout() self.resLayout.addRow(self.ratioCheck) self.resLayout.addRow('Pixel Width:', self.hResBox) self.resLayout.addRow(self.vResLabel, self.vResBox) self.resLayout.setLabelAlignment(QtCore.Qt.AlignLeft) self.resLayout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow) # Resolution Group Box self.resGroupBox = QGroupBox("Resolution") self.resGroupBox.setLayout(self.resLayout) def updateDock(self): self.updateOrigin() self.updateWidth() self.updateHeight() self.updateColorBy() self.updateUniverseLevel() self.updatePlotAlpha() self.updatePlotVisibility() self.updateOutlines() self.updateBasis() self.updateAspectLock() self.updateHRes() self.updateVRes() def updateOrigin(self): self.xOrBox.setValue(self.model.activeView.origin[0]) self.yOrBox.setValue(self.model.activeView.origin[1]) self.zOrBox.setValue(self.model.activeView.origin[2]) def updateWidth(self): self.widthBox.setValue(self.model.activeView.width) def updateHeight(self): self.heightBox.setValue(self.model.activeView.height) def updateColorBy(self): self.colorbyBox.setCurrentText(self.model.activeView.colorby) if self.model.activeView.colorby != 'cell': self.universeLevelBox.setEnabled(False) else: self.universeLevelBox.setEnabled(True) def updateUniverseLevel(self): self.universeLevelBox.setCurrentIndex(self.model.activeView.level + 1) def updatePlotAlpha(self): self.domainAlphaBox.setValue(self.model.activeView.domainAlpha) def updatePlotVisibility(self): self.visibilityBox.setChecked(self.model.activeView.domainVisible) def updateOutlines(self): self.outlinesBox.setChecked(self.model.activeView.outlines) def updateBasis(self): self.basisBox.setCurrentText(self.model.activeView.basis) def updateAspectLock(self): aspect_lock = bool(self.model.activeView.aspectLock) self.ratioCheck.setChecked(aspect_lock) self.vResBox.setDisabled(aspect_lock) self.vResLabel.setDisabled(aspect_lock) def updateHRes(self): self.hResBox.setValue(self.model.activeView.h_res) def updateVRes(self): self.vResBox.setValue(self.model.activeView.v_res) def revertToCurrent(self): cv = self.model.currentView self.xOrBox.setValue(cv.origin[0]) self.yOrBox.setValue(cv.origin[1]) self.zOrBox.setValue(cv.origin[2]) self.widthBox.setValue(cv.width) self.heightBox.setValue(cv.height) def resizeEvent(self, event): self.main_window.resizeEvent(event) hideEvent = showEvent = moveEvent = resizeEvent
class EmptyVolumeUI(QSplitter): def __init__(self, *args, **kwargs): super(EmptyVolumeUI, self).__init__(*args, **kwargs) self.visible = QGraphicsOpacityEffect(self) self.visible.setOpacity(1.0) self.disvisible = QGraphicsOpacityEffect(self) self.disvisible.setOpacity(0.0) main_layout = QHBoxLayout() self.variety_tree = VarietyTree(self) main_layout.addWidget(self.variety_tree) self.right_widget = QWidget(self) right_layout = QVBoxLayout() right_layout.setContentsMargins(QMargins(1, 1, 1, 1)) opts_layout = QHBoxLayout() # 选择分析的目标数据类别 self.radio_button_group = QButtonGroup(self) radio_button_1 = QRadioButton("行情统计", self) radio_button_1.setChecked(True) self.radio_button_group.addButton(radio_button_1) radio_button_2 = QRadioButton("排名持仓", self) self.radio_button_group.addButton(radio_button_2) opts_layout.addWidget(radio_button_1) opts_layout.addWidget(radio_button_2) self.rank_spinbox = QSpinBox(self) self.rank_spinbox.setPrefix("前 ") self.rank_spinbox.setSuffix(" 名") self.rank_spinbox.setRange(1, 20) self.rank_spinbox.setValue(20) self.rank_spinbox.setEnabled(False) opts_layout.addWidget(self.rank_spinbox) # 分割线 vertical_line = QFrame(self) vertical_line.setFrameShape(QFrame.VLine) opts_layout.addWidget(vertical_line) opts_layout.addWidget(QLabel("选择合约:", self)) self.contract_combobox = QComboBox(self) opts_layout.addWidget(self.contract_combobox) self.confirm_button = QPushButton("确定", self) opts_layout.addWidget(self.confirm_button) self.tip_button = QPushButton('左侧选择品种后进行查询 ', self) # 提示文字 opts_layout.addWidget(self.tip_button) self.tip_button.setGraphicsEffect(self.disvisible) opts_layout.addStretch() right_layout.addLayout(opts_layout) self.web_container = QWebEngineView(self) right_layout.addWidget(self.web_container) self.right_widget.setLayout(right_layout) main_layout.addWidget(self.right_widget) self.setStretchFactor(1, 2) self.setStretchFactor(2, 8) self.setHandleWidth(1) self.contract_combobox.setMinimumWidth(80) self.setLayout(main_layout) self.tip_button.setObjectName("tipButton") self.setStyleSheet( "#tipButton{border:none;color:rgb(230,50,50);font-weight:bold;}")
class GradientWidget(ToolWidget): def __init__(self, image, parent=None): super(GradientWidget, self).__init__(parent) self.intensity_spin = QSpinBox() self.intensity_spin.setRange(0, 100) self.intensity_spin.setValue(95) self.intensity_spin.setSuffix(self.tr(" %")) self.intensity_spin.setToolTip(self.tr("Tonality compression amount")) self.blue_combo = QComboBox() self.blue_combo.addItems([ self.tr("None"), self.tr("Flat"), self.tr("Abs"), self.tr("Norm") ]) self.blue_combo.setCurrentIndex(2) self.blue_combo.setToolTip(self.tr("Blue component inclusion mode")) self.invert_check = QCheckBox(self.tr("Invert")) self.invert_check.setToolTip(self.tr("Reverse lighting direction")) self.equalize_check = QCheckBox(self.tr("Equalize")) self.equalize_check.setToolTip(self.tr("Apply histogram equalization")) self.image = image self.viewer = ImageViewer(self.image, self.image) self.dx, self.dy = cv.spatialGradient( cv.cvtColor(self.image, cv.COLOR_BGR2GRAY)) self.process() self.intensity_spin.valueChanged.connect(self.process) self.blue_combo.currentIndexChanged.connect(self.process) self.invert_check.stateChanged.connect(self.process) self.equalize_check.stateChanged.connect(self.process) top_layout = QHBoxLayout() top_layout.addWidget(QLabel(self.tr("Intensity:"))) top_layout.addWidget(self.intensity_spin) top_layout.addWidget(QLabel(self.tr("Blue channel:"))) top_layout.addWidget(self.blue_combo) top_layout.addWidget(self.invert_check) top_layout.addWidget(self.equalize_check) top_layout.addStretch() main_layout = QVBoxLayout() main_layout.addLayout(top_layout) main_layout.addWidget(self.viewer) self.setLayout(main_layout) def process(self): start = time() intensity = int(self.intensity_spin.value() / 100 * 127) invert = self.invert_check.isChecked() equalize = self.equalize_check.isChecked() self.intensity_spin.setEnabled(not equalize) blue_mode = self.blue_combo.currentIndex() if invert: dx = (-self.dx).astype(np.float32) dy = (-self.dy).astype(np.float32) else: dx = (+self.dx).astype(np.float32) dy = (+self.dy).astype(np.float32) dx_abs = np.abs(dx) dy_abs = np.abs(dy) red = ((dx / np.max(dx_abs) * 127) + 127).astype(np.uint8) green = ((dy / np.max(dy_abs) * 127) + 127).astype(np.uint8) if blue_mode == 0: blue = np.zeros_like(red) elif blue_mode == 1: blue = np.full_like(red, 255) elif blue_mode == 2: blue = norm_mat(dx_abs + dy_abs) elif blue_mode == 3: blue = norm_mat(np.linalg.norm(cv.merge((red, green)), axis=2)) else: blue = None gradient = cv.merge([blue, green, red]) if equalize: gradient = equalize_img(gradient) elif intensity > 0: gradient = cv.LUT(gradient, create_lut(intensity, intensity)) self.viewer.update_processed(gradient) self.info_message.emit( self.tr(f"Luminance Gradient = {elapsed_time(start)}"))
class WaveletWidget(ToolWidget): def __init__(self, image, parent=None): super(WaveletWidget, self).__init__(parent) self.family_combo = QComboBox() self.family_combo.addItems([ self.tr("Daubechies"), self.tr("Symlets"), self.tr("Coiflets"), self.tr("Biorthogonal") ]) self.wavelet_combo = QComboBox() self.wavelet_combo.setMinimumWidth(70) self.threshold_spin = QSpinBox() self.threshold_spin.setRange(0, 100) self.threshold_spin.setSuffix("%") self.mode_combo = QComboBox() self.mode_combo.addItems([ self.tr("Soft"), self.tr("Hard"), self.tr("Garrote"), self.tr("Greater"), self.tr("Less") ]) self.level_spin = QSpinBox() self.image = image self.coeffs = None self.viewer = ImageViewer(self.image, self.image) self.update_wavelet() self.family_combo.activated.connect(self.update_wavelet) self.wavelet_combo.activated.connect(self.update_level) self.threshold_spin.valueChanged.connect(self.compute_idwt) self.mode_combo.activated.connect(self.compute_idwt) self.level_spin.valueChanged.connect(self.compute_idwt) top_layout = QHBoxLayout() top_layout.addWidget(QLabel(self.tr("Family:"))) top_layout.addWidget(self.family_combo) top_layout.addWidget(QLabel(self.tr("Wavelet:"))) top_layout.addWidget(self.wavelet_combo) top_layout.addWidget(QLabel(self.tr("Threshold:"))) top_layout.addWidget(self.threshold_spin) top_layout.addWidget(QLabel(self.tr("Mode:"))) top_layout.addWidget(self.mode_combo) top_layout.addWidget(QLabel(self.tr("Level:"))) top_layout.addWidget(self.level_spin) top_layout.addStretch() main_layout = QVBoxLayout() main_layout.addLayout(top_layout) main_layout.addWidget(self.viewer) self.setLayout(main_layout) def update_wavelet(self): self.wavelet_combo.clear() family = self.family_combo.currentIndex() if family == 0: self.wavelet_combo.addItems([f"db{i}" for i in range(1, 21)]) elif family == 1: self.wavelet_combo.addItems([f"sym{i}" for i in range(2, 21)]) elif family == 2: self.wavelet_combo.addItems([f"coif{i}" for i in range(1, 6)]) else: types = [ "1.1", "1.3", "1.5", "2.2", "2.4", "2.6", "2.8", "3.1", "3.3", "3.5", "3.7", "3.9", "4.4", "5.5", "6.8", ] self.wavelet_combo.addItems([f"bior{t}" for t in types]) self.update_level() def update_level(self): wavelet = self.wavelet_combo.currentText() max_level = pywt.dwtn_max_level(self.image.shape[:-1], wavelet) self.level_spin.blockSignals(True) self.level_spin.setRange(1, max_level) self.level_spin.setValue(max_level // 2) self.level_spin.blockSignals(False) self.compute_dwt() def compute_dwt(self): wavelet = self.wavelet_combo.currentText() self.coeffs = pywt.wavedec2(self.image[:, :, 0], wavelet) self.compute_idwt() def compute_idwt(self): thr = self.threshold_spin.value() if thr > 0: level = self.level_spin.value() coeffs = deepcopy(self.coeffs) threshold = self.threshold_spin.value() / 100 mode = self.mode_combo.currentText().lower() for i in range(1, level + 1): octave = [None] * 3 for j in range(3): plane = coeffs[-i][j] t = threshold * np.max(np.abs(plane)) octave[j] = pywt.threshold(plane, t, mode) coeffs[-i] = tuple(octave) else: coeffs = self.coeffs wavelet = self.wavelet_combo.currentText() image = cv.cvtColor( pywt.waverec2(coeffs, wavelet).astype(np.uint8), cv.COLOR_GRAY2BGR) self.viewer.update_processed(image)
class ElaWidget(ToolWidget): def __init__(self, image, parent=None): super(ElaWidget, self).__init__(parent) self.quality_spin = QSpinBox() self.quality_spin.setRange(0, 100) self.quality_spin.setSuffix(self.tr(' %')) self.quality_spin.setToolTip(self.tr('JPEG reference quality level')) self.scale_spin = QSpinBox() self.scale_spin.setRange(1, 100) self.scale_spin.setSuffix(' %') self.scale_spin.setToolTip(self.tr('Output multiplicative gain')) self.contrast_spin = QSpinBox() self.contrast_spin.setRange(0, 100) self.contrast_spin.setSuffix(' %') self.contrast_spin.setToolTip(self.tr('Output tonality compression')) self.equalize_check = QCheckBox(self.tr('Equalized')) self.equalize_check.setToolTip(self.tr('Apply histogram equalization')) self.gray_check = QCheckBox(self.tr('Grayscale')) self.gray_check.setToolTip(self.tr('Desaturated output')) default_button = QPushButton(self.tr('Default')) default_button.setToolTip(self.tr('Revert to default parameters')) params_layout = QHBoxLayout() params_layout.addWidget(QLabel(self.tr('Quality:'))) params_layout.addWidget(self.quality_spin) params_layout.addWidget(QLabel(self.tr('Scale:'))) params_layout.addWidget(self.scale_spin) params_layout.addWidget(QLabel(self.tr('Contrast:'))) params_layout.addWidget(self.contrast_spin) params_layout.addWidget(self.equalize_check) params_layout.addWidget(self.gray_check) params_layout.addWidget(default_button) params_layout.addStretch() self.image = image self.original = image.astype(np.float32) / 255 self.viewer = ImageViewer(self.image, self.image) self.default() self.quality_spin.valueChanged.connect(self.process) self.scale_spin.valueChanged.connect(self.process) self.contrast_spin.valueChanged.connect(self.process) self.equalize_check.stateChanged.connect(self.process) self.gray_check.stateChanged.connect(self.process) default_button.clicked.connect(self.default) main_layout = QVBoxLayout() main_layout.addLayout(params_layout) main_layout.addWidget(self.viewer) self.setLayout(main_layout) def process(self): start = time() quality = self.quality_spin.value() scale = self.scale_spin.value() / 20 contrast = int(self.contrast_spin.value() / 100 * 128) equalize = self.equalize_check.isChecked() grayscale = self.gray_check.isChecked() self.scale_spin.setEnabled(not equalize) self.contrast_spin.setEnabled(not equalize) compressed = compress_img(self.image, quality).astype(np.float32) / 255 difference = cv.absdiff(self.original, compressed) if equalize: ela = equalize_img((difference * 255).astype(np.uint8)) else: ela = cv.convertScaleAbs(cv.sqrt(difference) * 255, None, scale) ela = cv.LUT(ela, create_lut(contrast, contrast)) if grayscale: ela = desaturate(ela) self.viewer.update_processed(ela) self.info_message.emit( self.tr('Error Level Analysis = {}'.format(elapsed_time(start)))) def default(self): self.blockSignals(True) self.equalize_check.setChecked(False) self.gray_check.setChecked(False) self.quality_spin.setValue(75) self.scale_spin.setValue(50) self.contrast_spin.setValue(20) self.process() self.blockSignals(False)
class TabDisplays(QTabWidget): def __init__(self, parent=None): super(TabDisplays, self).__init__(parent) # Initialize logging logging_conf_file = os.path.join(os.path.dirname(__file__), 'cfg/aecgviewer_aecg_logging.conf') logging.config.fileConfig(logging_conf_file) self.logger = logging.getLogger(__name__) self.studyindex_info = aecg.tools.indexer.StudyInfo() self.validator = QWidget() self.studyinfo = QWidget() self.statistics = QWidget() self.waveforms = QWidget() self.waveforms.setAccessibleName("Waveforms") self.scatterplot = QWidget() self.histogram = QWidget() self.trends = QWidget() self.xmlviewer = QWidget() self.xml_display = QTextEdit(self.xmlviewer) self.options = QWidget() self.aecg_display_area = QScrollArea() self.cbECGLayout = QComboBox() self.aecg_display = EcgDisplayWidget(self.aecg_display_area) self.aecg_display_area.setWidget(self.aecg_display) self.addTab(self.validator, "Study information") self.addTab(self.waveforms, "Waveforms") self.addTab(self.xmlviewer, "XML") self.addTab(self.options, "Options") self.setup_validator() self.setup_waveforms() self.setup_xmlviewer() self.setup_options() size = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) size.setHeightForWidth(False) self.setSizePolicy(size) # Initialized a threpool with 2 threads 1 for the GUI, 1 for long # tasks, so GUI remains responsive self.threadpool = QThreadPool() self.threadpool.setMaxThreadCount(2) self.validator_worker = None self.indexing_timer = QElapsedTimer() def setup_validator(self): self.directory_indexer = None # aecg.indexing.DirectoryIndexer() self.validator_layout_container = QWidget() self.validator_layout = QFormLayout() self.validator_form_layout = QFormLayout( self.validator_layout_container) self.validator_grid_layout = QGridLayout() self.study_info_file = QLineEdit() self.study_info_file.setToolTip("Study index file") self.study_info_description = QLineEdit() self.study_info_description.setToolTip("Description") self.app_type = QLineEdit() self.app_type.setToolTip("Application type (e.g., NDA, IND, BLA, IDE)") self.app_num = QLineEdit() self.app_num.setToolTip("Six-digit application number") self.app_num.setValidator(QIntValidator(self.app_num)) self.study_id = QLineEdit() self.study_id.setToolTip("Study identifier") self.study_sponsor = QLineEdit() self.study_sponsor.setToolTip("Sponsor of the study") self.study_annotation_aecg_cb = QComboBox() self.study_annotation_aecg_cb.addItems( ["Rhythm", "Derived beat", "Holter-rhythm", "Holter-derived"]) self.study_annotation_aecg_cb.setToolTip( "Waveforms used to perform the ECG measurements (i.e., " "annotations)\n" "\tRhythm: annotations in a rhythm strip or discrete ECG " "extraction (e.g., 10-s strips)\n" "\tDerived beat: annotations in a representative beat derived " "from a rhythm strip\n" "\tHolter-rhythm: annotations in a the analysis window of a " "continuous recording\n" "\tHolter-derived: annotations in a representative beat derived " "from analysis window of a continuous recording\n") self.study_annotation_lead_cb = QComboBox() self.ui_leads = ["GLOBAL"] + aecg.STD_LEADS[0:12] +\ [aecg.KNOWN_NON_STD_LEADS[1]] + aecg.STD_LEADS[12:15] + ["Other"] self.study_annotation_lead_cb.addItems(self.ui_leads) self.study_annotation_lead_cb.setToolTip( "Primary analysis lead annotated per protocol. There could be " "annotations in other leads also, but only the primary lead should" " be selected here.\n" "Select global if all leads were used at the " "same time (e.g., superimposed on screen).\n" "Select other if the primary lead used is not in the list.") self.study_numsubjects = QLineEdit() self.study_numsubjects.setToolTip( "Number of subjects with ECGs in the study") self.study_numsubjects.setValidator( QIntValidator(self.study_numsubjects)) self.study_aecgpersubject = QLineEdit() self.study_aecgpersubject.setToolTip( "Number of scheduled ECGs (or analysis windows) per subject as " "specified in the study protocol.\n" "Enter average number of ECGs " "per subject if the protocol does not specify a fixed number of " "ECGs per subject.") self.study_aecgpersubject.setValidator( QIntValidator(self.study_aecgpersubject)) self.study_numaecg = QLineEdit() self.study_numaecg.setToolTip( "Total number of aECG XML files in the study") self.study_numaecg.setValidator(QIntValidator(self.study_numaecg)) self.study_annotation_numbeats = QLineEdit() self.study_annotation_numbeats.setToolTip( "Minimum number of beats annotated in each ECG or analysis window" ".\nEnter 1 if annotations were done in the derived beat.") self.study_annotation_numbeats.setValidator( QIntValidator(self.study_annotation_numbeats)) self.aecg_numsubjects = QLineEdit() self.aecg_numsubjects.setToolTip( "Number of subjects found across the provided aECG XML files") self.aecg_numsubjects.setReadOnly(True) self.aecg_aecgpersubject = QLineEdit() self.aecg_aecgpersubject.setToolTip( "Average number of ECGs per subject found across the provided " "aECG XML files") self.aecg_aecgpersubject.setReadOnly(True) self.aecg_numaecg = QLineEdit() self.aecg_numaecg.setToolTip( "Number of aECG XML files found in the study aECG directory") self.aecg_numaecg.setReadOnly(True) self.subjects_less_aecgs = QLineEdit() self.subjects_less_aecgs.setToolTip( "Percentage of subjects with less aECGs than specified per " "protocol") self.subjects_less_aecgs.setReadOnly(True) self.subjects_more_aecgs = QLineEdit() self.subjects_more_aecgs.setToolTip( "Percentage of subjects with more aECGs than specified per " "protocol") self.subjects_more_aecgs.setReadOnly(True) self.aecgs_no_annotations = QLineEdit() self.aecgs_no_annotations.setToolTip( "Percentage of aECGs with no annotations") self.aecgs_no_annotations.setReadOnly(True) self.aecgs_less_qt_in_primary_lead = QLineEdit() self.aecgs_less_qt_in_primary_lead.setToolTip( "Percentage of aECGs with less QT intervals in the primary lead " "than specified per protocol") self.aecgs_less_qt_in_primary_lead.setReadOnly(True) self.aecgs_less_qts = QLineEdit() self.aecgs_less_qts.setToolTip( "Percentage of aECGs with less QT intervals than specified per " "protocol") self.aecgs_less_qts.setReadOnly(True) self.aecgs_annotations_multiple_leads = QLineEdit() self.aecgs_annotations_multiple_leads.setToolTip( "Percentage of aECGs with QT annotations in multiple leads") self.aecgs_annotations_multiple_leads.setReadOnly(True) self.aecgs_annotations_no_primary_lead = QLineEdit() self.aecgs_annotations_no_primary_lead.setToolTip( "Percentage of aECGs with QT annotations not in the primary lead") self.aecgs_annotations_no_primary_lead.setReadOnly(True) self.aecgs_with_errors = QLineEdit() self.aecgs_with_errors.setToolTip("Number of aECG files with errors") self.aecgs_with_errors.setReadOnly(True) self.aecgs_potentially_digitized = QLineEdit() self.aecgs_potentially_digitized.setToolTip( "Number of aECG files potentially digitized (i.e., with more than " "5% of samples missing)") self.aecgs_potentially_digitized.setReadOnly(True) self.study_dir = QLineEdit() self.study_dir.setToolTip("Directory containing the aECG files") self.study_dir_button = QPushButton("...") self.study_dir_button.clicked.connect(self.select_study_dir) self.study_dir_button.setToolTip("Open select directory dialog") self.validator_form_layout.addRow("Application Type", self.app_type) self.validator_form_layout.addRow("Application Number", self.app_num) self.validator_form_layout.addRow("Study name/ID", self.study_id) self.validator_form_layout.addRow("Sponsor", self.study_sponsor) self.validator_form_layout.addRow("Study description", self.study_info_description) self.validator_form_layout.addRow("Annotations in", self.study_annotation_aecg_cb) self.validator_form_layout.addRow("Annotations primary lead", self.study_annotation_lead_cb) self.validator_grid_layout.addWidget(QLabel(""), 0, 0) self.validator_grid_layout.addWidget( QLabel("Per study protocol or report"), 0, 1) self.validator_grid_layout.addWidget(QLabel("Found in aECG files"), 0, 2) self.validator_grid_layout.addWidget(QLabel("Number of subjects"), 1, 0) self.validator_grid_layout.addWidget(self.study_numsubjects, 1, 1) self.validator_grid_layout.addWidget(self.aecg_numsubjects, 1, 2) self.validator_grid_layout.addWidget( QLabel("Number of aECG per subject"), 2, 0) self.validator_grid_layout.addWidget(self.study_aecgpersubject, 2, 1) self.validator_grid_layout.addWidget(self.aecg_aecgpersubject, 2, 2) self.validator_grid_layout.addWidget(QLabel("Total number of aECG"), 3, 0) self.validator_grid_layout.addWidget(self.study_numaecg, 3, 1) self.validator_grid_layout.addWidget(self.aecg_numaecg, 3, 2) self.validator_grid_layout.addWidget( QLabel("Number of beats per aECG"), 4, 0) self.validator_grid_layout.addWidget(self.study_annotation_numbeats, 4, 1) self.validator_grid_layout.addWidget( QLabel("Subjects with fewer ECGs"), 5, 1) self.validator_grid_layout.addWidget(self.subjects_less_aecgs, 5, 2) self.validator_grid_layout.addWidget(QLabel("Subjects with more ECGs"), 6, 1) self.validator_grid_layout.addWidget(self.subjects_more_aecgs, 6, 2) self.validator_grid_layout.addWidget( QLabel("aECGs without annotations"), 7, 1) self.validator_grid_layout.addWidget(self.aecgs_no_annotations, 7, 2) self.validator_grid_layout.addWidget( QLabel("aECGs without expected number of QTs in primary lead"), 8, 1) self.validator_grid_layout.addWidget( self.aecgs_less_qt_in_primary_lead, 8, 2) self.validator_grid_layout.addWidget( QLabel("aECGs without expected number of QTs"), 9, 1) self.validator_grid_layout.addWidget(self.aecgs_less_qts, 9, 2) self.validator_grid_layout.addWidget( QLabel("aECGs annotated in multiple leads"), 10, 1) self.validator_grid_layout.addWidget( self.aecgs_annotations_multiple_leads, 10, 2) self.validator_grid_layout.addWidget( QLabel("aECGs with annotations not in primary lead"), 11, 1) self.validator_grid_layout.addWidget( self.aecgs_annotations_no_primary_lead, 11, 2) self.validator_grid_layout.addWidget(QLabel("aECGs with errors"), 12, 1) self.validator_grid_layout.addWidget(self.aecgs_with_errors, 12, 2) self.validator_grid_layout.addWidget( QLabel("Potentially digitized aECGs"), 13, 1) self.validator_grid_layout.addWidget(self.aecgs_potentially_digitized, 13, 2) self.validator_form_layout.addRow(self.validator_grid_layout) tmp = QHBoxLayout() tmp.addWidget(self.study_dir) tmp.addWidget(self.study_dir_button) self.validator_form_layout.addRow("Study aECGs directory", tmp) self.validator_form_layout.addRow("Study index file", self.study_info_file) self.validator_layout.addWidget(self.validator_layout_container) self.validator_effective_dirs = QLabel("") self.validator_effective_dirs.setWordWrap(True) self.validator_layout.addWidget(self.validator_effective_dirs) self.val_button = QPushButton("Generate/update study index") self.val_button.clicked.connect(self.importstudy_dialog) self.validator_layout.addWidget(self.val_button) self.cancel_val_button = QPushButton("Cancel study index generation") self.cancel_val_button.clicked.connect(self.cancel_validator) self.cancel_val_button.setEnabled(False) self.validator_layout.addWidget(self.cancel_val_button) self.validator_pl = QLabel("") self.validator_layout.addWidget(self.validator_pl) self.validator_pb = QProgressBar() self.validator_layout.addWidget(self.validator_pb) self.validator.setLayout(self.validator_layout) self.stop_indexing = False self.lastindexing_starttime = None self.update_validator_effective_dirs() def effective_aecgs_dir(self, navwidget, silent=False): aecgs_effective_dir = self.study_dir.text() if navwidget.project_loaded != '': # Path specified in the GUI potential_aecgs_dirs = [self.study_dir.text()] # StudyDir path from current working directory potential_aecgs_dirs += [self.studyindex_info.StudyDir] # StudyDir path from directory where the index is located potential_aecgs_dirs += [ os.path.join(os.path.dirname(navwidget.project_loaded), self.studyindex_info.StudyDir) ] # StudyDir replaced with the directory where the index is located potential_aecgs_dirs += [os.path.dirname(navwidget.project_loaded)] dir_found = False # Get xml and zip filenames from first element in the index aecg_xml_file = navwidget.data_index["AECGXML"][0] zipfile = "" if aecg_xml_file != "": zipfile = navwidget.data_index["ZIPFILE"][0] for p in potential_aecgs_dirs: testfn = os.path.join(p, aecg_xml_file) if zipfile != "": testfn = os.path.join(p, zipfile) if os.path.isfile(testfn): dir_found = True aecgs_effective_dir = p break if not silent: if not dir_found: QMessageBox.warning( self, f"Study aECGs directory not found", f"The following paths were checked:" f"{[','.join(p) for p in potential_aecgs_dirs]} and " f"none of them is valid") elif p != self.study_dir.text(): QMessageBox.warning( self, f"Study aECGs directory not found", f"The path specified in the study aECGs directory is " f"not valid and {p} is being used instead. Check and " f"update the path in Study aECGs directory textbox if " f"the suggested path is not the adequate path") return aecgs_effective_dir def update_validator_effective_dirs(self): msg = f"Working directory: {os.getcwd()}" if self.parent() is not None: if isinstance(self.parent(), QSplitter): navwidget = self.parent().parent() else: # Tabs widget has not been allocated the QSplitter yet navwidget = self.parent() project_loaded = navwidget.project_loaded if project_loaded != '': msg = f"{msg}\nLoaded project index: "\ f"{navwidget.project_loaded}" effective_aecgs_path = self.effective_aecgs_dir(navwidget) msg = f"{msg}\nEffective study aECGs directory: "\ f"{effective_aecgs_path}" else: msg = f"{msg}\nLoaded project index: None" else: msg = f"{msg}\nLoaded project index: None" self.validator_effective_dirs.setText(msg) def load_study_info(self, fileName): self.study_info_file.setText(fileName) try: study_info = pd.read_excel(fileName, sheet_name="Info") self.studyindex_info = aecg.tools.indexer.StudyInfo() self.studyindex_info.__dict__.update( study_info.set_index("Property").transpose().reset_index( drop=True).to_dict('index')[0]) sponsor = "" description = "" if self.studyindex_info.Sponsor is not None and\ isinstance(self.studyindex_info.Sponsor, str): sponsor = self.studyindex_info.Sponsor if self.studyindex_info.Description is not None and\ isinstance(self.studyindex_info.Description, str): description = self.studyindex_info.Description self.study_sponsor.setText(sponsor) self.study_info_description.setText(description) self.app_type.setText(self.studyindex_info.AppType) self.app_num.setText(f"{int(self.studyindex_info.AppNum):06d}") self.study_id.setText(self.studyindex_info.StudyID) self.study_numsubjects.setText(str(self.studyindex_info.NumSubj)) self.study_aecgpersubject.setText( str(self.studyindex_info.NECGSubj)) self.study_numaecg.setText(str(self.studyindex_info.TotalECGs)) anns_in = self.studyindex_info.AnMethod.upper() idx = 0 if anns_in == "RHYTHM": idx = 0 elif anns_in == "DERIVED": idx = 1 elif anns_in == "HOLTER_RHYTHM": idx = 2 elif anns_in == "HOLTER_MEDIAN_BEAT": idx = 3 else: idx = int(anns_in) - 1 self.study_annotation_aecg_cb.setCurrentIndex(idx) the_lead = self.studyindex_info.AnLead idx = self.study_annotation_lead_cb.findText(str(the_lead)) if idx == -1: idx = self.study_annotation_lead_cb.findText("MDC_ECG_LEAD_" + str(the_lead)) if idx == -1: idx = int(the_lead) self.study_annotation_lead_cb.setCurrentIndex(idx) self.study_annotation_numbeats.setText( str(self.studyindex_info.AnNbeats)) if self.studyindex_info.StudyDir == "": self.studyindex_info.StudyDir = os.path.dirname(fileName) self.study_dir.setText(self.studyindex_info.StudyDir) self.update_validator_effective_dirs() self.setCurrentWidget(self.validator) except Exception as ex: QMessageBox.critical( self, "Import study error", "Error reading the study information file: '" + fileName + "'") def setup_waveforms(self): wflayout = QVBoxLayout() # ECG plot layout selection box self.cbECGLayout.addItems( ['12-lead stacked', '3x4 + lead II rhythm', 'Superimposed']) self.cbECGLayout.currentIndexChanged.connect( self.ecgplotlayout_changed) # Zoom buttons blayout = QHBoxLayout() pb_zoomin = QPushButton() pb_zoomin.setText("Zoom +") pb_zoomin.clicked.connect(self.zoom_in) pb_zoomreset = QPushButton() pb_zoomreset.setText("Zoom 1:1") pb_zoomreset.clicked.connect(self.zoom_reset) pb_zoomout = QPushButton() pb_zoomout.setText("Zoom -") pb_zoomout.clicked.connect(self.zoom_out) blayout.addWidget(self.cbECGLayout) blayout.addWidget(pb_zoomout) blayout.addWidget(pb_zoomreset) blayout.addWidget(pb_zoomin) wflayout.addLayout(blayout) # Add QScrollArea to main layout of waveforms tab self.aecg_display_area.setWidgetResizable(False) wflayout.addWidget(self.aecg_display_area) self.waveforms.setLayout(wflayout) size = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) size.setHeightForWidth(False) self.aecg_display_area.setSizePolicy(size) self.waveforms.setSizePolicy(size) def setup_xmlviewer(self): wf_layout = QHBoxLayout() wf_layout.addWidget(self.xml_display) self.xmlviewer.setLayout(wf_layout) def setup_options(self): self.options_layout = QFormLayout() self.aecg_schema_filename = QLineEdit(aecg.get_aecg_schema_location()) self.options_layout.addRow("aECG XML schema path", self.aecg_schema_filename) self.save_index_every_n_aecgs = QSpinBox() self.save_index_every_n_aecgs.setMinimum(0) self.save_index_every_n_aecgs.setMaximum(50000) self.save_index_every_n_aecgs.setValue(0) self.save_index_every_n_aecgs.setSingleStep(100) self.save_index_every_n_aecgs.setSuffix(" aECGs") self.save_index_every_n_aecgs.setToolTip( "Set o 0 to save the study index file only after its generation " "is completed.\nOtherwise, the file is saved everytime the " " specified number of ECGs have been appended to the index.") self.options_layout.addRow("Save index every ", self.save_index_every_n_aecgs) self.save_all_intervals_cb = QCheckBox("") self.save_all_intervals_cb.setChecked(False) self.options_layout.addRow("Save individual beat intervals", self.save_all_intervals_cb) self.parallel_processing_cb = QCheckBox("") self.parallel_processing_cb.setChecked(True) self.options_layout.addRow("Parallel processing of files", self.parallel_processing_cb) self.options.setLayout(self.options_layout) def zoom_in(self): self.aecg_display.apply_zoom(self.aecg_display.zoom_factor + 0.1) def zoom_out(self): self.aecg_display.apply_zoom(self.aecg_display.zoom_factor - 0.1) def zoom_reset(self): self.aecg_display.apply_zoom(1.0) def ecgplotlayout_changed(self, i): self.aecg_display.update_aecg_plot( ecg_layout=aecg.utils.ECG_plot_layout(i + 1)) def update_search_progress(self, i, n): self.validator_pl.setText( f"Searching aECGs in directory ({n} XML files found)") def update_progress(self, i, n): j = i m = n if i <= 1: j = 1 if self.validator_pb.value() > 0: j = self.validator_pb.value() + 1 m = self.validator_pb.maximum() running_time = self.indexing_timer.elapsed() * 1e-3 # in seconds time_per_item = running_time / j # reamining = seconds per item so far * total pending items to process remaining_time = time_per_item * (m - j) eta = datetime.datetime.now() +\ datetime.timedelta(seconds=round(remaining_time, 0)) self.validator_pl.setText( f"Validating aECG {j}/{m} | " f"Execution time: " f"{str(datetime.timedelta(0,seconds=round(running_time)))} | " f"{round(1/time_per_item,2)} aECGs per second | " f"ETA: {eta.isoformat(timespec='seconds')}") self.validator_pb.setValue(j) if self.save_index_every_n_aecgs.value() > 0 and\ len(self.directory_indexer.studyindex) % \ self.save_index_every_n_aecgs.value() == 0: self.save_validator_results( pd.concat(self.directory_indexer.studyindex, ignore_index=True)) def save_validator_results(self, res): if res.shape[0] > 0: self.studyindex_info = aecg.tools.indexer.StudyInfo() self.studyindex_info.StudyDir = self.study_dir.text() self.studyindex_info.IndexFile = self.study_info_file.text() self.studyindex_info.Sponsor = self.study_sponsor.text() self.studyindex_info.Description =\ self.study_info_description.text() self.studyindex_info.Date = self.lastindexing_starttime.isoformat() self.studyindex_info.End_date = datetime.datetime.now().isoformat() self.studyindex_info.Version = aecg.__version__ self.studyindex_info.AppType = self.app_type.text() self.studyindex_info.AppNum = f"{int(self.app_num.text()):06d}" self.studyindex_info.StudyID = self.study_id.text() self.studyindex_info.NumSubj = int(self.study_numsubjects.text()) self.studyindex_info.NECGSubj = int( self.study_aecgpersubject.text()) self.studyindex_info.TotalECGs = int(self.study_numaecg.text()) anmethod = aecg.tools.indexer.AnnotationMethod( self.study_annotation_aecg_cb.currentIndex()) self.studyindex_info.AnMethod = anmethod.name self.studyindex_info.AnLead =\ self.study_annotation_lead_cb.currentText() self.studyindex_info.AnNbeats = int( self.study_annotation_numbeats.text()) # Calculate stats study_stats = aecg.tools.indexer.StudyStats( self.studyindex_info, res) # Save to file aecg.tools.indexer.save_study_index(self.studyindex_info, res, study_stats) validator_data_ready = Signal() def save_validator_results_and_load_index(self, res): self.save_validator_results(res) self.validator_data_ready.emit() def indexer_validator_results(self, res): self.studyindex_df = pd.concat([self.studyindex_df, res], ignore_index=True) def subindex_thread_complete(self): return def index_directory_thread_complete(self): tmp = self.validator_pl.text().replace("ETA:", "Completed: ").replace( "Validating", "Validated") self.validator_pl.setText(tmp) self.val_button.setEnabled(True) self.cancel_val_button.setEnabled(False) self.validator_layout_container.setEnabled(True) def index_directory(self, progress_callback): self.lastindexing_starttime = datetime.datetime.now() self.indexing_timer.start() studyindex_df = [] n_cores = os.cpu_count() aecg_schema = None if self.aecg_schema_filename.text() != "": aecg_schema = self.aecg_schema_filename.text() if self.parallel_processing_cb.isChecked(): studyindex_df = self.directory_indexer.index_directory( self.save_all_intervals_cb.isChecked(), aecg_schema, n_cores, progress_callback) else: studyindex_df = self.directory_indexer.index_directory( self.save_all_intervals_cb.isChecked(), aecg_schema, 1, progress_callback) return studyindex_df def importstudy_dialog(self): dirName = os.path.normpath(self.study_dir.text()) if dirName != "": if os.path.exists(dirName): self.directory_indexer = aecg.indexing.DirectoryIndexer() self.directory_indexer.set_aecg_dir( dirName, self.update_search_progress) self.validator_pb.setMaximum(self.directory_indexer.num_files) self.validator_pb.reset() self.stop_indexing = False self.validator_layout_container.setEnabled(False) self.val_button.setEnabled(False) self.cancel_val_button.setEnabled(True) self.validator_worker = Worker(self.index_directory) self.validator_worker.signals.result.connect( self.save_validator_results_and_load_index) self.validator_worker.signals.finished.connect( self.index_directory_thread_complete) self.validator_worker.signals.progress.connect( self.update_progress) # Execute self.threadpool.start(self.validator_worker) else: QMessageBox.critical( self, "Directory not found", f"Specified study directory not found:\n{dirName}") else: QMessageBox.critical(self, "Import study error", "Study directory cannot be empty") def cancel_validator(self): self.cancel_val_button.setEnabled(False) self.stop_indexing = True self.directory_indexer.cancel_indexing = True self.threadpool.waitForDone(3000) self.val_button.setEnabled(True) def select_study_dir(self): cd = self.study_dir.text() if cd == "": cd = "." dir = QFileDialog.getExistingDirectory( self, "Open Directory", cd, QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks) if dir != "": self.study_dir.setText(dir)
class CloningWidget(ToolWidget): def __init__(self, image, parent=None): super(CloningWidget, self).__init__(parent) self.detector_combo = QComboBox() self.detector_combo.addItems( [self.tr('BRISK'), self.tr('ORB'), self.tr('AKAZE')]) self.detector_combo.setCurrentIndex(0) self.detector_combo.setToolTip( self.tr('Algorithm used for localization and description')) self.response_spin = QSpinBox() self.response_spin.setRange(0, 100) self.response_spin.setSuffix(self.tr('%')) self.response_spin.setValue(90) self.response_spin.setToolTip( self.tr('Maximum keypoint response to perform matching')) self.matching_spin = QSpinBox() self.matching_spin.setRange(1, 100) self.matching_spin.setSuffix(self.tr('%')) self.matching_spin.setValue(20) self.matching_spin.setToolTip( self.tr('Maximum metric difference to accept matching')) self.distance_spin = QSpinBox() self.distance_spin.setRange(1, 100) self.distance_spin.setSuffix(self.tr('%')) self.distance_spin.setValue(15) self.distance_spin.setToolTip( self.tr('Maximum distance between matches in the same cluster')) self.cluster_spin = QSpinBox() self.cluster_spin.setRange(1, 20) self.cluster_spin.setValue(5) self.cluster_spin.setToolTip( self.tr('Minimum number of keypoints to create a new cluster')) self.nolines_check = QCheckBox(self.tr('Hide lines')) self.nolines_check.setToolTip(self.tr('Disable match line drawing')) self.process_button = QPushButton(self.tr('Process')) self.process_button.setToolTip(self.tr('Perform automatic detection')) self.status_label = QLabel( self.tr('[Press "Process" button to search for cloned regions]')) self.image = image self.viewer = ImageViewer(self.image, self.image) self.gray = cv.cvtColor(self.image, cv.COLOR_BGR2GRAY) self.keypoints = self.kpts = self.desc = self.matches = self.clusters = None self.canceled = False self.detector_combo.currentIndexChanged.connect(self.update_detector) self.response_spin.valueChanged.connect(self.update_detector) self.matching_spin.valueChanged.connect(self.update_matching) self.distance_spin.valueChanged.connect(self.update_cluster) self.cluster_spin.valueChanged.connect(self.update_cluster) self.nolines_check.stateChanged.connect(self.process) self.process_button.clicked.connect(self.process) top_layout = QHBoxLayout() top_layout.addWidget(QLabel(self.tr('Detector:'))) top_layout.addWidget(self.detector_combo) top_layout.addWidget(QLabel(self.tr('Response:'))) top_layout.addWidget(self.response_spin) top_layout.addWidget(QLabel(self.tr('Matching:'))) top_layout.addWidget(self.matching_spin) top_layout.addWidget(QLabel(self.tr('Distance:'))) top_layout.addWidget(self.distance_spin) top_layout.addWidget(QLabel(self.tr('Cluster:'))) top_layout.addWidget(self.cluster_spin) top_layout.addWidget(self.nolines_check) top_layout.addWidget(self.process_button) top_layout.addStretch() main_layout = QVBoxLayout() main_layout.addLayout(top_layout) main_layout.addWidget(self.status_label) main_layout.addWidget(self.viewer) self.setLayout(main_layout) def update_detector(self): self.keypoints = self.kpts = self.desc = self.matches = self.clusters = None self.process_button.setEnabled(True) def update_matching(self): self.matches = self.clusters = None self.process_button.setEnabled(True) def update_cluster(self): self.clusters = None self.process_button.setEnabled(True) def cancel(self): self.canceled = True self.keypoints = self.kpts = self.desc = self.matches = self.clusters = None self.status_label.setText(self.tr('Processing interrupted!')) modify_font(self.status_label, bold=False, italic=False) def process(self): start = time() self.status_label.setText(self.tr('Processing, please wait...')) algorithm = self.detector_combo.currentIndex() response = 100 - self.response_spin.value() matching = self.matching_spin.value() / 100 * 255 distance = self.distance_spin.value() / 100 cluster = self.cluster_spin.value() modify_font(self.status_label, bold=False, italic=True) QCoreApplication.processEvents() if self.kpts is None: if algorithm == 0: detector = cv.BRISK_create() elif algorithm == 1: detector = cv.ORB_create() elif algorithm == 2: detector = cv.AKAZE_create() else: return self.kpts, self.desc = detector.detectAndCompute(self.gray, None) self.keypoints = len(self.kpts) responses = np.array([k.response for k in self.kpts]) strongest = (cv.normalize(responses, None, 0, 100, cv.NORM_MINMAX) >= response).flatten() self.kpts = list(compress(self.kpts, strongest)) self.desc = self.desc[strongest] if self.matches is None: matcher = cv.BFMatcher_create(cv.NORM_HAMMING, True) self.matches = matcher.radiusMatch(self.desc, self.desc, matching) if self.matches is None: self.status_label.setText( self.tr('No keypoint match found with current settings')) modify_font(self.status_label, italic=False, bold=True) return self.matches = [ item for sublist in self.matches for item in sublist ] self.matches = [ m for m in self.matches if m.queryIdx != m.trainIdx ] if self.clusters is None: self.clusters = [] total = len(self.matches) min_dist = distance * np.min(self.gray.shape) / 2 progress = QProgressDialog(self.tr('Clustering matches...'), self.tr('Cancel'), 0, total, self) progress.canceled.connect(self.cancel) progress.setWindowModality(Qt.WindowModal) for i in range(total): match0 = self.matches[i] query0 = match0.queryIdx train0 = match0.trainIdx group = [match0] a0 = np.array(self.kpts[query0].pt) b0 = np.array(self.kpts[train0].pt) d0 = np.linalg.norm(a0 - b0) if d0 < min_dist: continue for j in range(i + 1, total): match1 = self.matches[j] query1 = match1.queryIdx train1 = match1.trainIdx if query1 == train0 and train1 == query0: continue a1 = np.array(self.kpts[query1].pt) b1 = np.array(self.kpts[train1].pt) d1 = np.linalg.norm(a1 - b1) if d1 < min_dist or np.abs(d0 - d1) > min_dist: continue aa = np.linalg.norm(a0 - a1) bb = np.linalg.norm(b0 - b1) ab = np.linalg.norm(a0 - b1) ba = np.linalg.norm(b0 - a1) smallest = np.partition(np.array([aa, bb, ab, ba]), 1)[:2] if np.all(np.logical_and(smallest > 0, smallest < min_dist)): for g in group: if g.queryIdx == train1 and g.trainIdx == query1: break else: group.append(match1) if len(group) >= cluster: self.clusters.append(group) progress.setValue(i) if self.canceled: self.canceled = False return progress.setValue(total) output = np.copy(self.image) hsv = np.zeros((1, 1, 3)) nolines = self.nolines_check.isChecked() angles = [] for c in self.clusters: for m in c: ka = self.kpts[m.queryIdx] pa = tuple(map(int, ka.pt)) sa = int(np.round(ka.size)) kb = self.kpts[m.trainIdx] pb = tuple(map(int, kb.pt)) sb = int(np.round(kb.size)) angle = np.arctan2(pb[1] - pa[1], pb[0] - pa[0]) if angle < 0: angle += np.pi angles.append(angle) hsv[0, 0, 0] = angle / np.pi * 180 hsv[0, 0, 1] = 255 hsv[0, 0, 2] = m.distance / matching * 255 rgb = cv.cvtColor(hsv.astype(np.uint8), cv.COLOR_HSV2BGR) rgb = tuple([int(x) for x in rgb[0, 0]]) cv.circle(output, pa, sa, rgb, 1, cv.LINE_AA) cv.circle(output, pb, sb, rgb, 1, cv.LINE_AA) if not nolines: cv.line(output, pa, pb, rgb, 1, cv.LINE_AA) regions = 0 if angles: angles = np.reshape(np.array(angles, dtype=np.float32), (len(angles), 1)) if np.std(angles) < 0.1: regions = 1 else: criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 10, 1.0) attempts = 10 flags = cv.KMEANS_PP_CENTERS compact = [ cv.kmeans(angles, k, None, criteria, attempts, flags)[0] for k in range(1, 11) ] compact = cv.normalize(np.array(compact), None, 0, 1, cv.NORM_MINMAX) regions = np.argmax(compact < 0.005) + 1 self.viewer.update_processed(output) self.process_button.setEnabled(False) modify_font(self.status_label, italic=False, bold=True) self.status_label.setText( self. tr('Keypoints: {} --> Filtered: {} --> Matches: {} --> Clusters: {} --> Regions: {}' .format(self.keypoints, len(self.kpts), len(self.matches), len(self.clusters), regions))) self.info_message.emit( self.tr('Copy-Move Forgery = {}'.format(elapsed_time(start))))
class ParametersComponent(QGroupBox): def __init__(self): super().__init__() self.setTitle("Paramètres") self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) # Main layout main_layout = QHBoxLayout(self) main_layout.setSpacing(20) left_layout = QFormLayout() left_layout.setHorizontalSpacing(20) left_layout.setVerticalSpacing(14) self._threshold_sbox = QSpinBox() self._threshold_sbox.setRange(1, 99) self._threshold_sbox.setSuffix(" %") self._threshold_sbox.setMaximumWidth(250) left_layout.addRow("Seuil de détection :", self._threshold_sbox) self._devices_list = QComboBox() self._devices_list.setMaximumWidth(250) self._setDevicesList() left_layout.addRow("Processeur à utiliser :", self._devices_list) morphotype_layout = QGridLayout() morphotype_layout.setSpacing(5) morphotype_layout.addWidget(QLabel("Morphotypes à détecter :"), 0, 0, 1, 4) self._tab_cbox = {} for k,m in Loader.SpongesMorphotypes().items(): sponge_cbox = QCheckBox(m.name()) self._tab_cbox[k] = sponge_cbox x = k%2 + 1 y = k//2 + 1 morphotype_layout.addWidget(sponge_cbox, x, y) morphotype_layout.setColumnMinimumWidth(0, 15) morphotype_layout.setColumnStretch(0, 0) main_layout.addLayout(left_layout) main_layout.addLayout(morphotype_layout) self.setLayout(main_layout) def _setDevicesList(self): self._devices_list.clear() available_devices = NeuralNetwork.getAvailableCalculationDevices() for device_id, device_name in available_devices.items(): self._devices_list.addItem(device_name, device_id) if len(available_devices) == 1: self._devices_list.addItem("GPU (Indisponible)", None) self._devices_list.model().item(1).setEnabled(False) else: self._devices_list.setCurrentIndex(1) def reset(self, parameters: Parameters): self._threshold_sbox.setValue(parameters.threshold()*100) for k,v in parameters.morphotypes().items(): self._tab_cbox[k].setChecked(v) def updateParameters(self, parameters: Parameters): parameters.setThreshold(self._threshold_sbox.value()/100) parameters.setDeviceId(self._devices_list.currentData()) for k in parameters.morphotypes(): parameters.morphotypes()[k] = self._tab_cbox[k].isChecked()
class OptionsDock(QDockWidget): def __init__(self, model, FM, parent=None): super(OptionsDock, self).__init__(parent) self.model = model self.FM = FM self.mw = parent self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding) # Doesn't work? self.setAllowedAreas(QtCore.Qt.LeftDockWidgetArea | QtCore.Qt.RightDockWidgetArea) # Create Controls self.createOriginBox() self.createOptionsBox() self.createResolutionBox() # Create submit button self.applyButton = QPushButton("Apply Changes") self.applyButton.setMinimumHeight(self.FM.height() * 1.6) # Mac bug fix self.applyButton.clicked.connect(self.mw.applyChanges) # Create Zoom box self.zoomBox = QSpinBox() self.zoomBox.setSuffix(' %') self.zoomBox.setRange(25, 2000) self.zoomBox.setValue(100) self.zoomBox.setSingleStep(25) self.zoomBox.valueChanged.connect(self.mw.editZoom) self.zoomLayout = QHBoxLayout() self.zoomLayout.addWidget(QLabel('Zoom:')) self.zoomLayout.addWidget(self.zoomBox) self.zoomLayout.setContentsMargins(0, 0, 0, 0) self.zoomWidget = QWidget() self.zoomWidget.setLayout(self.zoomLayout) # Create Layout self.dockLayout = QVBoxLayout() self.dockLayout.addWidget(self.originGroupBox) self.dockLayout.addWidget(self.optionsGroupBox) self.dockLayout.addWidget(self.resGroupBox) self.dockLayout.addWidget(self.applyButton) self.dockLayout.addStretch() self.dockLayout.addWidget(HorizontalLine()) self.dockLayout.addWidget(self.zoomWidget) self.optionsWidget = QWidget() self.optionsWidget.setLayout(self.dockLayout) self.setWidget(self.optionsWidget) def createOriginBox(self): # X Origin self.xOrBox = QDoubleSpinBox() self.xOrBox.setDecimals(9) self.xOrBox.setRange(-99999, 99999) self.xOrBox.valueChanged.connect( lambda value: self.mw.editSingleOrigin(value, 0)) # Y Origin self.yOrBox = QDoubleSpinBox() self.yOrBox.setDecimals(9) self.yOrBox.setRange(-99999, 99999) self.yOrBox.valueChanged.connect( lambda value: self.mw.editSingleOrigin(value, 1)) # Z Origin self.zOrBox = QDoubleSpinBox() self.zOrBox.setDecimals(9) self.zOrBox.setRange(-99999, 99999) self.zOrBox.valueChanged.connect( lambda value: self.mw.editSingleOrigin(value, 2)) # Origin Form Layout self.orLayout = QFormLayout() self.orLayout.addRow('X:', self.xOrBox) self.orLayout.addRow('Y:', self.yOrBox) self.orLayout.addRow('Z:', self.zOrBox) #self.orLayout.setVerticalSpacing(4) self.orLayout.setLabelAlignment(QtCore.Qt.AlignLeft) self.orLayout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow) # Origin Group Box self.originGroupBox = QGroupBox('Origin') self.originGroupBox.setLayout(self.orLayout) def createOptionsBox(self): # Width self.widthBox = QDoubleSpinBox(self) self.widthBox.setRange(.1, 99999) self.widthBox.valueChanged.connect(self.mw.editWidth) # Height self.heightBox = QDoubleSpinBox(self) self.heightBox.setRange(.1, 99999) self.heightBox.valueChanged.connect(self.mw.editHeight) # ColorBy self.colorbyBox = QComboBox(self) self.colorbyBox.addItem("material") self.colorbyBox.addItem("cell") self.colorbyBox.currentTextChanged[str].connect(self.mw.editColorBy) # Alpha self.plotAlphaBox = QDoubleSpinBox(self) self.plotAlphaBox.setValue(self.model.activeView.plotAlpha) self.plotAlphaBox.setSingleStep(0.05) self.plotAlphaBox.setDecimals(2) self.plotAlphaBox.setRange(0.0, 1.0) self.plotAlphaBox.valueChanged.connect(self.mw.editPlotAlpha) # Basis self.basisBox = QComboBox(self) self.basisBox.addItem("xy") self.basisBox.addItem("xz") self.basisBox.addItem("yz") self.basisBox.currentTextChanged.connect(self.mw.editBasis) # Advanced Color Options self.colorOptionsButton = QPushButton('Color Options...') self.colorOptionsButton.setMinimumHeight(self.FM.height() * 1.6) self.colorOptionsButton.clicked.connect(self.mw.showColorDialog) # Options Form Layout self.opLayout = QFormLayout() self.opLayout.addRow('Width:', self.widthBox) self.opLayout.addRow('Height:', self.heightBox) self.opLayout.addRow('Basis:', self.basisBox) self.opLayout.addRow('Color By:', self.colorbyBox) self.opLayout.addRow('Plot alpha:', self.plotAlphaBox) self.opLayout.addRow(self.colorOptionsButton) self.opLayout.setLabelAlignment(QtCore.Qt.AlignLeft) self.opLayout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow) # Options Group Box self.optionsGroupBox = QGroupBox('Options') self.optionsGroupBox.setLayout(self.opLayout) def createResolutionBox(self): # Horizontal Resolution self.hResBox = QSpinBox(self) self.hResBox.setRange(1, 99999) self.hResBox.setSingleStep(25) self.hResBox.setSuffix(' px') self.hResBox.valueChanged.connect(self.mw.editHRes) # Vertical Resolution self.vResLabel = QLabel('Pixel Height:') self.vResBox = QSpinBox(self) self.vResBox.setRange(1, 99999) self.vResBox.setSingleStep(25) self.vResBox.setSuffix(' px') self.vResBox.valueChanged.connect(self.mw.editVRes) # Ratio checkbox self.ratioCheck = QCheckBox("Fixed Aspect Ratio", self) self.ratioCheck.stateChanged.connect(self.mw.toggleAspectLock) # Resolution Form Layout self.resLayout = QFormLayout() self.resLayout.addRow(self.ratioCheck) self.resLayout.addRow('Pixel Width:', self.hResBox) self.resLayout.addRow(self.vResLabel, self.vResBox) self.resLayout.setLabelAlignment(QtCore.Qt.AlignLeft) self.resLayout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow) # Resolution Group Box self.resGroupBox = QGroupBox("Resolution") self.resGroupBox.setLayout(self.resLayout) def updateDock(self): self.updateOrigin() self.updateWidth() self.updateHeight() self.updateColorBy() self.updatePlotAlpha() self.updateBasis() self.updateAspectLock() self.updateHRes() self.updateVRes() def updateOrigin(self): self.xOrBox.setValue(self.model.activeView.origin[0]) self.yOrBox.setValue(self.model.activeView.origin[1]) self.zOrBox.setValue(self.model.activeView.origin[2]) def updateWidth(self): self.widthBox.setValue(self.model.activeView.width) def updateHeight(self): self.heightBox.setValue(self.model.activeView.height) def updateColorBy(self): self.colorbyBox.setCurrentText(self.model.activeView.colorby) def updatePlotAlpha(self): self.plotAlphaBox.setValue(self.model.activeView.plotAlpha) def updateBasis(self): self.basisBox.setCurrentText(self.model.activeView.basis) def updateAspectLock(self): if self.model.activeView.aspectLock: self.ratioCheck.setChecked(True) self.vResBox.setDisabled(True) self.vResLabel.setDisabled(True) else: self.ratioCheck.setChecked(False) self.vResBox.setDisabled(False) self.vResLabel.setDisabled(False) def updateHRes(self): self.hResBox.setValue(self.model.activeView.h_res) def updateVRes(self): self.vResBox.setValue(self.model.activeView.v_res) def revertToCurrent(self): cv = self.model.currentView self.xOrBox.setValue(cv.origin[0]) self.yOrBox.setValue(cv.origin[1]) self.zOrBox.setValue(cv.origin[2]) self.widthBox.setValue(cv.width) self.heightBox.setValue(cv.height) def resizeEvent(self, event): self.mw.resizeEvent(event) def hideEvent(self, event): self.mw.resizeEvent(event) def showEvent(self, event): self.mw.resizeEvent(event) def moveEvent(self, event): self.mw.resizeEvent(event)
class Window(QDialog): def __init__(self, parent=None): super(Window, self).__init__(parent) self.iconGroupBox = QGroupBox() self.iconLabel = QLabel() self.iconComboBox = QComboBox() self.showIconCheckBox = QCheckBox() self.messageGroupBox = QGroupBox() self.typeLabel = QLabel() self.durationLabel = QLabel() self.durationWarningLabel = QLabel() self.titleLabel = QLabel() self.bodyLabel = QLabel() self.typeComboBox = QComboBox() self.durationSpinBox = QSpinBox() self.titleEdit = QLineEdit() self.bodyEdit = QTextEdit() self.showMessageButton = QPushButton() self.minimizeAction = QAction() self.maximizeAction = QAction() self.restoreAction = QAction() self.quitAction = QAction() self.trayIcon = QSystemTrayIcon() self.trayIconMenu = QMenu() self.createIconGroupBox() self.createMessageGroupBox() self.iconLabel.setMinimumWidth(self.durationLabel.sizeHint().width()) self.createActions() self.createTrayIcon() self.showMessageButton.clicked.connect(self.showMessage) self.showIconCheckBox.toggled.connect(self.trayIcon.setVisible) self.iconComboBox.currentIndexChanged.connect(self.setIcon) self.trayIcon.messageClicked.connect(self.messageClicked) self.trayIcon.activated.connect(self.iconActivated) self.mainLayout = QVBoxLayout() self.mainLayout.addWidget(self.iconGroupBox) self.mainLayout.addWidget(self.messageGroupBox) self.setLayout(self.mainLayout) self.iconComboBox.setCurrentIndex(1) self.trayIcon.show() self.setWindowTitle("Systray") self.resize(400, 300) def setVisible(self, visible): self.minimizeAction.setEnabled(visible) self.maximizeAction.setEnabled(not self.isMaximized()) self.restoreAction.setEnabled(self.isMaximized() or not visible) super().setVisible(visible) def closeEvent(self, event): if not event.spontaneous() or not self.isVisible(): return if self.trayIcon.isVisible(): QMessageBox.information(self, "Systray", "The program will keep running in the system tray. " "To terminate the program, choose <b>Quit</b> in the context " "menu of the system tray entry.") self.hide() event.ignore() @Slot(int) def setIcon(self, index): icon = self.iconComboBox.itemIcon(index) self.trayIcon.setIcon(icon) self.setWindowIcon(icon) self.trayIcon.setToolTip(self.iconComboBox.itemText(index)) @Slot(str) def iconActivated(self, reason): if reason == QSystemTrayIcon.Trigger: pass if reason == QSystemTrayIcon.DoubleClick: self.iconComboBox.setCurrentIndex( (self.iconComboBox.currentIndex() + 1) % self.iconComboBox.count() ) if reason == QSystemTrayIcon.MiddleClick: self.showMessage() @Slot() def showMessage(self): self.showIconCheckBox.setChecked(True) selectedIcon = self.typeComboBox.itemData(self.typeComboBox.currentIndex()) msgIcon = QSystemTrayIcon.MessageIcon(selectedIcon) if selectedIcon == -1: # custom icon icon = QIcon(self.iconComboBox.itemIcon(self.iconComboBox.currentIndex())) self.trayIcon.showMessage( self.titleEdit.text(), self.bodyEdit.toPlainText(), icon, self.durationSpinBox.value() * 1000, ) else: self.trayIcon.showMessage( self.titleEdit.text(), self.bodyEdit.toPlainText(), msgIcon, self.durationSpinBox.value() * 1000, ) @Slot() def messageClicked(self): QMessageBox.information(None, "Systray", "Sorry, I already gave what help I could.\n" "Maybe you should try asking a human?") def createIconGroupBox(self): self.iconGroupBox = QGroupBox("Tray Icon") self.iconLabel = QLabel("Icon:") self.iconComboBox = QComboBox() self.iconComboBox.addItem(QIcon(":/images/bad.png"), "Bad") self.iconComboBox.addItem(QIcon(":/images/heart.png"), "Heart") self.iconComboBox.addItem(QIcon(":/images/trash.png"), "Trash") self.showIconCheckBox = QCheckBox("Show icon") self.showIconCheckBox.setChecked(True) iconLayout = QHBoxLayout() iconLayout.addWidget(self.iconLabel) iconLayout.addWidget(self.iconComboBox) iconLayout.addStretch() iconLayout.addWidget(self.showIconCheckBox) self.iconGroupBox.setLayout(iconLayout) def createMessageGroupBox(self): self.messageGroupBox = QGroupBox("Balloon Message") self.typeLabel = QLabel("Type:") self.typeComboBox = QComboBox() self.typeComboBox.addItem("None", QSystemTrayIcon.NoIcon) self.typeComboBox.addItem( self.style().standardIcon(QStyle.SP_MessageBoxInformation), "Information", QSystemTrayIcon.Information, ) self.typeComboBox.addItem( self.style().standardIcon(QStyle.SP_MessageBoxWarning), "Warning", QSystemTrayIcon.Warning, ) self.typeComboBox.addItem( self.style().standardIcon(QStyle.SP_MessageBoxCritical), "Critical", QSystemTrayIcon.Critical, ) self.typeComboBox.addItem(QIcon(), "Custom icon", -1) self.typeComboBox.setCurrentIndex(1) self.durationLabel = QLabel("Duration:") self.durationSpinBox = QSpinBox() self.durationSpinBox.setRange(5, 60) self.durationSpinBox.setSuffix(" s") self.durationSpinBox.setValue(15) self.durationWarningLabel = QLabel("(some systems might ignore this hint)") self.durationWarningLabel.setIndent(10) self.titleLabel = QLabel("Title:") self.titleEdit = QLineEdit("Cannot connect to network") self.bodyLabel = QLabel("Body:") self.bodyEdit = QTextEdit() self.bodyEdit.setPlainText("Don't believe me. Honestly, I don't have a clue." "\nClick this balloon for details.") self.showMessageButton = QPushButton("Show Message") self.showMessageButton.setDefault(True) messageLayout = QGridLayout() messageLayout.addWidget(self.typeLabel, 0, 0) messageLayout.addWidget(self.typeComboBox, 0, 1, 1, 2) messageLayout.addWidget(self.durationLabel, 1, 0) messageLayout.addWidget(self.durationSpinBox, 1, 1) messageLayout.addWidget(self.durationWarningLabel, 1, 2, 1, 3) messageLayout.addWidget(self.titleLabel, 2, 0) messageLayout.addWidget(self.titleEdit, 2, 1, 1, 4) messageLayout.addWidget(self.bodyLabel, 3, 0) messageLayout.addWidget(self.bodyEdit, 3, 1, 2, 4) messageLayout.addWidget(self.showMessageButton, 5, 4) messageLayout.setColumnStretch(3, 1) messageLayout.setRowStretch(4, 1) self.messageGroupBox.setLayout(messageLayout) def createActions(self): self.minimizeAction = QAction("Minimize", self) self.minimizeAction.triggered.connect(self.hide) self.maximizeAction = QAction("Maximize", self) self.maximizeAction.triggered.connect(self.showMaximized) self.restoreAction = QAction("Restore", self) self.restoreAction.triggered.connect(self.showNormal) self.quitAction = QAction("Quit", self) self.quitAction.triggered.connect(qApp.quit) def createTrayIcon(self): self.trayIconMenu = QMenu(self) self.trayIconMenu.addAction(self.minimizeAction) self.trayIconMenu.addAction(self.maximizeAction) self.trayIconMenu.addAction(self.restoreAction) self.trayIconMenu.addSeparator() self.trayIconMenu.addAction(self.quitAction) self.trayIcon = QSystemTrayIcon(self) self.trayIcon.setContextMenu(self.trayIconMenu)
def __init_slicer_options_widget__(self): self.__slicer_options_widget = QGroupBox("Slicer Options", self) thickness_label = QLabel("Layer Thickness", self.__slicer_options_widget) thickness_edit = QDoubleSpinBox(self.__slicer_options_widget) thickness_edit.setSuffix(str('\u03BCm')) thickness_edit.setMaximum(1000000) thickness_edit.setMinimum(0) thickness_edit.setDecimals(3) thickness_edit.setSingleStep(0.001) thickness_edit.setValue(self.dlp_controller.support_thickness * 1000) # self.__opengl_widget.set_slice_thickness(self.dlp_controller.support_thickness) thickness_edit.valueChanged.connect( self.__slicer_widget.set_slice_thickness) pixel_size_label = QLabel("Projector Pixel Size", self.__slicer_options_widget) pixel_size_edit = QDoubleSpinBox(self.__slicer_options_widget) pixel_size_edit.setSuffix(str('\u03BCm')) pixel_size_edit.setMaximum(1000000) pixel_size_edit.setMinimum(0) pixel_size_edit.setDecimals(2) pixel_size_edit.setSingleStep(0.01) pixel_size_edit.setValue(self.dlp_controller.projector_pixel_size * 1000) pixel_size_edit.valueChanged.connect( self.__slicer_widget.set_pixel_size) projector_resolution_label = QLabel("Projector Resolution", self.__slicer_options_widget) projector_resolution_edit_x = QSpinBox(self.__slicer_options_widget) projector_resolution_edit_x.setSuffix(str('W')) projector_resolution_edit_x.setMaximum(1000000) projector_resolution_edit_x.setMinimum(0) projector_resolution_edit_x.setValue( self.dlp_controller.projector_width) projector_resolution_edit_x.valueChanged.connect( self.__slicer_widget.set_projector_width) projector_resolution_edit_y = QSpinBox(self.__slicer_options_widget) projector_resolution_edit_y.setSuffix(str('H')) projector_resolution_edit_y.setMaximum(1000000) projector_resolution_edit_y.setMinimum(0) projector_resolution_edit_y.setValue( self.dlp_controller.projector_height) projector_resolution_edit_y.valueChanged.connect( self.__slicer_widget.set_projector_height) samples_per_pixel_label = QLabel("Samples per Pixel", self.__slicer_options_widget) samples_per_pixel_edit = QSpinBox(self.__slicer_options_widget) samples_per_pixel_edit.setMaximum(1000000) samples_per_pixel_edit.setMinimum(1) samples_per_pixel_edit.setValue(self.dlp_controller.samples_per_pixel) samples_per_pixel_edit.valueChanged.connect( self.__slicer_widget.set_samples_per_pixel) slice_geometry_button = QPushButton("Slice Geometry") slice_geometry_button.clicked.connect(self.start_slicing_process) slice_interrupt_button = QPushButton("Stop Slicing") slice_interrupt_button.clicked.connect( self.__slicer_widget.interrupt_slicing) self.slices_label = QLabel(f'Slicing progress: {0:.0f}/{0:.0f}', self.__info_widget) thickness_label_row = 0 pixel_size_row = 1 projector_resolution_row = 2 samples_per_pixel_row = 3 slice_button_row = 4 slices_label_row = 5 # slice_interrupt_row = slice_button_row slice_layout = QGridLayout(self.__slicer_options_widget) slice_layout.addWidget(thickness_label, thickness_label_row, 0) slice_layout.addWidget(thickness_edit, thickness_label_row, 1) slice_layout.addWidget(pixel_size_label, pixel_size_row, 0) slice_layout.addWidget(pixel_size_edit, pixel_size_row, 1) slice_layout.addWidget(projector_resolution_label, projector_resolution_row, 0) slice_layout.addWidget(projector_resolution_edit_x, projector_resolution_row, 1) slice_layout.addWidget(projector_resolution_edit_y, projector_resolution_row, 2) slice_layout.addWidget(self.slices_label, slice_button_row, 0) slice_layout.addWidget(slice_geometry_button, slice_button_row, 1) slice_layout.addWidget(slice_interrupt_button, slice_button_row, 2) slice_layout.addWidget(samples_per_pixel_label, samples_per_pixel_row, 0) slice_layout.addWidget(samples_per_pixel_edit, samples_per_pixel_row, 1) self.__slicer_options_widget.setLayout(slice_layout)
class MagnifierWidget(ToolWidget): def __init__(self, image, parent=None): super(MagnifierWidget, self).__init__(parent) self.equalize_radio = QRadioButton(self.tr("Equalization")) self.equalize_radio.setToolTip(self.tr("RGB histogram equalization")) self.contrast_radio = QRadioButton(self.tr("Auto Contrast")) self.contrast_radio.setToolTip(self.tr("Compress luminance tonality")) self.centile_spin = QSpinBox() self.centile_spin.setRange(0, 100) self.centile_spin.setValue(20) self.centile_spin.setSuffix(self.tr(" %")) self.centile_spin.setToolTip(self.tr("Histogram percentile amount")) self.channel_check = QCheckBox(self.tr("By channel")) self.channel_check.setToolTip(self.tr("Independent RGB compression")) self.equalize_radio.setChecked(True) self.last_radio = self.equalize_radio self.image = image self.viewer = ImageViewer(self.image, self.image) self.change() self.viewer.viewChanged.connect(self.process) self.equalize_radio.clicked.connect(self.change) self.contrast_radio.clicked.connect(self.change) self.centile_spin.valueChanged.connect(self.change) self.channel_check.stateChanged.connect(self.change) top_layout = QHBoxLayout() top_layout.addWidget(QLabel(self.tr("Mode:"))) top_layout.addWidget(self.equalize_radio) top_layout.addWidget(self.contrast_radio) top_layout.addWidget(self.centile_spin) top_layout.addWidget(self.channel_check) top_layout.addStretch() main_layout = QVBoxLayout() main_layout.addLayout(top_layout) main_layout.addWidget(self.viewer) self.setLayout(main_layout) def process(self, rect): y1 = rect.top() y2 = rect.bottom() x1 = rect.left() x2 = rect.right() roi = self.image[y1:y2, x1:x2] if self.equalize_radio.isChecked(): self.centile_spin.setEnabled(False) self.channel_check.setEnabled(False) roi = equalize_img(roi) self.last_radio = self.equalize_radio elif self.contrast_radio.isChecked(): self.centile_spin.setEnabled(True) self.channel_check.setEnabled(True) centile = self.centile_spin.value() / 200 if self.channel_check.isChecked(): roi = cv.merge( [cv.LUT(c, auto_lut(c, centile)) for c in cv.split(roi)]) else: roi = cv.LUT( roi, auto_lut(cv.cvtColor(roi, cv.COLOR_BGR2GRAY), centile)) self.last_radio = self.contrast_radio else: self.last_radio.setChecked(True) return processed = np.copy(self.image) processed[y1:y2, x1:x2] = roi self.viewer.update_processed(processed) def change(self): self.process(self.viewer.get_rect())
class CloningWidget(ToolWidget): def __init__(self, image, parent=None): super(CloningWidget, self).__init__(parent) self.detector_combo = QComboBox() self.detector_combo.addItems( [self.tr('BRISK'), self.tr('ORB'), self.tr('AKAZE')]) self.detector_combo.setCurrentIndex(0) self.detector_combo.setToolTip( self.tr('Algorithm used for localization and description')) self.response_spin = QSpinBox() self.response_spin.setRange(0, 100) self.response_spin.setSuffix(self.tr('%')) self.response_spin.setValue(90) self.response_spin.setToolTip( self.tr('Maximum keypoint response to perform matching')) self.matching_spin = QSpinBox() self.matching_spin.setRange(1, 100) self.matching_spin.setSuffix(self.tr('%')) self.matching_spin.setValue(20) self.matching_spin.setToolTip( self.tr('Maximum metric difference to accept matching')) self.distance_spin = QSpinBox() self.distance_spin.setRange(1, 100) self.distance_spin.setSuffix(self.tr('%')) self.distance_spin.setValue(15) self.distance_spin.setToolTip( self.tr('Maximum distance between matches in the same cluster')) self.cluster_spin = QSpinBox() self.cluster_spin.setRange(1, 20) self.cluster_spin.setValue(5) self.cluster_spin.setToolTip( self.tr('Minimum number of keypoints to create a new cluster')) self.kpts_check = QCheckBox(self.tr('Show keypoints')) self.kpts_check.setToolTip(self.tr('Show keypoint coverage')) self.nolines_check = QCheckBox(self.tr('Hide lines')) self.nolines_check.setToolTip(self.tr('Disable match line drawing')) self.process_button = QToolButton() self.process_button.setText(self.tr('Process')) self.process_button.setToolTip(self.tr('Perform automatic detection')) modify_font(self.process_button, bold=True) self.status_label = QLabel() self.mask_label = QLabel() self.mask_button = QToolButton() self.mask_button.setText(self.tr('Load mask...')) self.mask_button.setToolTip( self.tr('Load an image to be used as mask')) self.onoff_button = QToolButton() self.onoff_button.setText(self.tr('OFF')) self.onoff_button.setCheckable(True) self.onoff_button.setToolTip(self.tr('Toggle keypoint detection mask')) self.image = image self.viewer = ImageViewer(self.image, self.image) self.gray = cv.cvtColor(self.image, cv.COLOR_BGR2GRAY) self.total = self.kpts = self.desc = self.matches = self.clusters = self.mask = None self.canceled = False self.detector_combo.currentIndexChanged.connect(self.update_detector) self.response_spin.valueChanged.connect(self.update_detector) self.matching_spin.valueChanged.connect(self.update_matching) self.distance_spin.valueChanged.connect(self.update_cluster) self.cluster_spin.valueChanged.connect(self.update_cluster) self.nolines_check.stateChanged.connect(self.process) self.kpts_check.stateChanged.connect(self.process) self.process_button.clicked.connect(self.process) self.mask_button.clicked.connect(self.load_mask) self.onoff_button.toggled.connect(self.toggle_mask) self.onoff_button.setEnabled(False) top_layout = QHBoxLayout() top_layout.addWidget(QLabel(self.tr('Detector:'))) top_layout.addWidget(self.detector_combo) top_layout.addWidget(QLabel(self.tr('Response:'))) top_layout.addWidget(self.response_spin) top_layout.addWidget(QLabel(self.tr('Matching:'))) top_layout.addWidget(self.matching_spin) top_layout.addWidget(QLabel(self.tr('Distance:'))) top_layout.addWidget(self.distance_spin) top_layout.addWidget(QLabel(self.tr('Cluster:'))) top_layout.addWidget(self.cluster_spin) top_layout.addWidget(self.nolines_check) top_layout.addWidget(self.kpts_check) top_layout.addStretch() bottom_layout = QHBoxLayout() bottom_layout.addWidget(self.process_button) bottom_layout.addWidget(self.status_label) bottom_layout.addStretch() bottom_layout.addWidget(self.mask_button) bottom_layout.addWidget(self.onoff_button) main_layout = QVBoxLayout() main_layout.addLayout(top_layout) main_layout.addLayout(bottom_layout) main_layout.addWidget(self.viewer) self.setLayout(main_layout) def toggle_mask(self, checked): self.onoff_button.setText('ON' if checked else 'OFF') if checked: self.viewer.update_processed( cv.merge([c * self.mask for c in cv.split(self.image)])) else: self.viewer.update_processed(self.image) self.update_detector() def load_mask(self): filename, basename, mask = load_image(self) if filename is None: return if self.image.shape[:-1] != mask.shape[:-1]: QMessageBox.critical( self, self.tr('Error'), self.tr('Both image and mask must have the same size!')) return _, self.mask = cv.threshold(cv.cvtColor(mask, cv.COLOR_BGR2GRAY), 0, 1, cv.THRESH_BINARY) self.onoff_button.setEnabled(True) self.onoff_button.setChecked(True) self.mask_button.setText('"{}"'.format(splitext(basename)[0])) self.mask_button.setToolTip(self.tr('Current detection mask image')) def update_detector(self): self.total = self.kpts = self.desc = self.matches = self.clusters = None self.status_label.setText('') self.process_button.setEnabled(True) def update_matching(self): self.matches = self.clusters = None self.process_button.setEnabled(True) def update_cluster(self): self.clusters = None self.process_button.setEnabled(True) def cancel(self): self.canceled = True self.status_label.setText(self.tr('Processing interrupted!')) modify_font(self.status_label, bold=False, italic=False) def process(self): start = time() self.canceled = False self.status_label.setText(self.tr('Processing, please wait...')) algorithm = self.detector_combo.currentIndex() response = 100 - self.response_spin.value() matching = self.matching_spin.value() / 100 * 255 distance = self.distance_spin.value() / 100 cluster = self.cluster_spin.value() modify_font(self.status_label, bold=False, italic=True) QCoreApplication.processEvents() if self.kpts is None: if algorithm == 0: detector = cv.BRISK_create() elif algorithm == 1: detector = cv.ORB_create() elif algorithm == 2: detector = cv.AKAZE_create() else: return mask = self.mask if self.onoff_button.isChecked() else None self.kpts, self.desc = detector.detectAndCompute(self.gray, mask) self.total = len(self.kpts) responses = np.array([k.response for k in self.kpts]) strongest = (cv.normalize(responses, None, 0, 100, cv.NORM_MINMAX) >= response).flatten() self.kpts = list(compress(self.kpts, strongest)) if len(self.kpts) > 30000: QMessageBox.warning( self, self.tr('Warning'), self. tr('Too many keypoints found ({}), please reduce response value' .format(self.total))) self.kpts = self.desc = None self.total = 0 self.status_label.setText('') return self.desc = self.desc[strongest] if self.matches is None: matcher = cv.BFMatcher_create(cv.NORM_HAMMING, True) self.matches = matcher.radiusMatch(self.desc, self.desc, matching) if self.matches is None: self.status_label.setText( self.tr('No keypoint match found with current settings')) modify_font(self.status_label, italic=False, bold=True) return self.matches = [ item for sublist in self.matches for item in sublist ] self.matches = [ m for m in self.matches if m.queryIdx != m.trainIdx ] if not self.matches: self.clusters = [] elif self.clusters is None: self.clusters = [] min_dist = distance * np.min(self.gray.shape) / 2 kpts_a = np.array([p.pt for p in self.kpts]) ds = np.linalg.norm([ kpts_a[m.queryIdx] - kpts_a[m.trainIdx] for m in self.matches ], axis=1) self.matches = [ m for i, m in enumerate(self.matches) if ds[i] > min_dist ] total = len(self.matches) progress = QProgressDialog(self.tr('Clustering matches...'), self.tr('Cancel'), 0, total, self) progress.canceled.connect(self.cancel) progress.setWindowModality(Qt.WindowModal) for i in range(total): match0 = self.matches[i] d0 = ds[i] query0 = match0.queryIdx train0 = match0.trainIdx group = [match0] for j in range(i + 1, total): match1 = self.matches[j] query1 = match1.queryIdx train1 = match1.trainIdx if query1 == train0 and train1 == query0: continue d1 = ds[j] if np.abs(d0 - d1) > min_dist: continue a0 = np.array(self.kpts[query0].pt) b0 = np.array(self.kpts[train0].pt) a1 = np.array(self.kpts[query1].pt) b1 = np.array(self.kpts[train1].pt) aa = np.linalg.norm(a0 - a1) bb = np.linalg.norm(b0 - b1) ab = np.linalg.norm(a0 - b1) ba = np.linalg.norm(b0 - a1) if not (0 < aa < min_dist and 0 < bb < min_dist or 0 < ab < min_dist and 0 < ba < min_dist): continue for g in group: if g.queryIdx == train1 and g.trainIdx == query1: break else: group.append(match1) if len(group) >= cluster: self.clusters.append(group) progress.setValue(i) if self.canceled: self.update_detector() return progress.close() output = np.copy(self.image) hsv = np.zeros((1, 1, 3)) nolines = self.nolines_check.isChecked() show_kpts = self.kpts_check.isChecked() if show_kpts: for kpt in self.kpts: cv.circle(output, (int(kpt.pt[0]), int(kpt.pt[1])), 2, (250, 227, 72)) angles = [] for c in self.clusters: for m in c: ka = self.kpts[m.queryIdx] pa = tuple(map(int, ka.pt)) sa = int(np.round(ka.size)) kb = self.kpts[m.trainIdx] pb = tuple(map(int, kb.pt)) sb = int(np.round(kb.size)) angle = np.arctan2(pb[1] - pa[1], pb[0] - pa[0]) if angle < 0: angle += np.pi angles.append(angle) hsv[0, 0, 0] = angle / np.pi * 180 hsv[0, 0, 1] = 255 hsv[0, 0, 2] = m.distance / matching * 255 rgb = cv.cvtColor(hsv.astype(np.uint8), cv.COLOR_HSV2BGR) rgb = tuple([int(x) for x in rgb[0, 0]]) cv.circle(output, pa, sa, rgb, 1, cv.LINE_AA) cv.circle(output, pb, sb, rgb, 1, cv.LINE_AA) if not nolines: cv.line(output, pa, pb, rgb, 1, cv.LINE_AA) regions = 0 if angles: angles = np.reshape(np.array(angles, dtype=np.float32), (len(angles), 1)) if np.std(angles) < 0.1: regions = 1 else: criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 10, 1.0) attempts = 10 flags = cv.KMEANS_PP_CENTERS compact = [ cv.kmeans(angles, k, None, criteria, attempts, flags)[0] for k in range(1, 11) ] compact = cv.normalize(np.array(compact), None, 0, 1, cv.NORM_MINMAX) regions = np.argmax(compact < 0.005) + 1 self.viewer.update_processed(output) self.process_button.setEnabled(False) modify_font(self.status_label, italic=False, bold=True) self.status_label.setText( self. tr('Keypoints: {} --> Filtered: {} --> Matches: {} --> Clusters: {} --> Regions: {}' .format(self.total, len(self.kpts), len(self.matches), len(self.clusters), regions))) self.info_message.emit( self.tr('Copy-Move Forgery = {}'.format(elapsed_time(start))))
class WaveletWidget(ToolWidget): def __init__(self, image, parent=None): super(WaveletWidget, self).__init__(parent) self.family_combo = QComboBox() self.family_combo.addItems( [self.tr('Daubechies'), self.tr('Symlets'), self.tr('Coiflets'), self.tr('Biorthogonal')]) self.wavelet_combo = QComboBox() self.wavelet_combo.setMinimumWidth(70) self.threshold_spin = QSpinBox() self.threshold_spin.setRange(0, 100) self.threshold_spin.setSuffix('%') self.mode_combo = QComboBox() self.mode_combo.addItems( [self.tr('Soft'), self.tr('Hard'), self.tr('Garrote'), self.tr('Greater'), self.tr('Less')]) self.level_spin = QSpinBox() self.image = image self.coeffs = None self.viewer = ImageViewer(self.image, self.image) self.update_wavelet() self.family_combo.activated.connect(self.update_wavelet) self.wavelet_combo.activated.connect(self.update_level) self.threshold_spin.valueChanged.connect(self.compute_idwt) self.mode_combo.activated.connect(self.compute_idwt) self.level_spin.valueChanged.connect(self.compute_idwt) top_layout = QHBoxLayout() top_layout.addWidget(QLabel(self.tr('Family:'))) top_layout.addWidget(self.family_combo) top_layout.addWidget(QLabel(self.tr('Wavelet:'))) top_layout.addWidget(self.wavelet_combo) top_layout.addWidget(QLabel(self.tr('Threshold:'))) top_layout.addWidget(self.threshold_spin) top_layout.addWidget(QLabel(self.tr('Mode:'))) top_layout.addWidget(self.mode_combo) top_layout.addWidget(QLabel(self.tr('Level:'))) top_layout.addWidget(self.level_spin) top_layout.addStretch() main_layout = QVBoxLayout() main_layout.addLayout(top_layout) main_layout.addWidget(self.viewer) self.setLayout(main_layout) def update_wavelet(self): self.wavelet_combo.clear() family = self.family_combo.currentIndex() if family == 0: self.wavelet_combo.addItems(['db{}'.format(i) for i in range(1, 21)]) elif family == 1: self.wavelet_combo.addItems(['sym{}'.format(i) for i in range(2, 21)]) elif family == 2: self.wavelet_combo.addItems(['coif{}'.format(i) for i in range(1, 6)]) else: types = ['1.1', '1.3', '1.5', '2.2', '2.4', '2.6', '2.8', '3.1', '3.3', '3.5', '3.7', '3.9', '4.4', '5.5', '6.8'] self.wavelet_combo.addItems(['bior{}'.format(t) for t in types]) self.update_level() def update_level(self): wavelet = self.wavelet_combo.currentText() max_level = pywt.dwtn_max_level(self.image.shape[:-1], wavelet) self.level_spin.blockSignals(True) self.level_spin.setRange(1, max_level) self.level_spin.setValue(max_level // 2) self.level_spin.blockSignals(False) self.compute_dwt() def compute_dwt(self): wavelet = self.wavelet_combo.currentText() self.coeffs = pywt.wavedec2(self.image[:, :, 0], wavelet) self.compute_idwt() def compute_idwt(self): thr = self.threshold_spin.value() if thr > 0: level = self.level_spin.value() coeffs = deepcopy(self.coeffs) threshold = self.threshold_spin.value() / 100 mode = self.mode_combo.currentText().lower() for i in range(1, level + 1): octave = [None]*3 for j in range(3): plane = coeffs[-i][j] t = threshold * np.max(np.abs(plane)) octave[j] = pywt.threshold(plane, t, mode) coeffs[-i] = tuple(octave) else: coeffs = self.coeffs wavelet = self.wavelet_combo.currentText() image = cv.cvtColor(pywt.waverec2(coeffs, wavelet).astype(np.uint8), cv.COLOR_GRAY2BGR) self.viewer.update_processed(image)
class SpecifySearchConditionsDialog(BaseDialog): def __init__(self, parent, entity): super().__init__(parent, _("Search criteria"), _("&Start search"), _("&Cancel")) self._entity = entity self._value_widget = None self._value_label = None self._search_expression_parts = [] self._added_condition = False self._populate_fields_tree(self._entity) def create_ui(self): fields_label = QLabel(_("Class fields"), self) self.layout.addWidget(fields_label, 0, 0) self._fields_tree = QTreeWidget(self) fields_label.setBuddy(self._fields_tree) self._fields_tree.currentItemChanged.connect( self.on_fields_tree_sel_changed) self.layout.addWidget(self._fields_tree, 1, 0) operator_label = QLabel(_("Operator"), self) self.layout.addWidget(operator_label, 0, 1) self._operator = QListWidget(self) operator_label.setBuddy(self._operator) self.layout.addWidget(self._operator, 1, 1) self._operator.currentRowChanged.connect(self.on_operator_choice) self._operator.setCurrentRow(0) add_button = QPushButton(_("&Add condition"), self) add_button.clicked.connect(self.on_add_clicked) self.layout.addWidget(add_button, 2, 0, 1, 3) criteria_label = QLabel(_("Current search criteria"), self) self.layout.addWidget(criteria_label, 3, 0, 1, 3) self._criteria_list = QListWidget(self) criteria_label.setBuddy(self._criteria_list) self.layout.addWidget(self._criteria_list, 4, 0, 1, 3) remove_button = QPushButton(_("&Remove condition"), self) remove_button.clicked.connect(self.on_remove_clicked) self.layout.addWidget(remove_button, 5, 0, 1, 3) distance_label = QLabel(_("Search objects to distance"), self) self.layout.addWidget(distance_label, 6, 0) self._distance_field = QSpinBox(self) self._distance_field.setMaximum(100000) self._distance_field.setSuffix(" " + _("meters")) self._distance_field.setSpecialValueText(_("No limit")) distance_label.setBuddy(self._distance_field) self.layout.addWidget(self._distance_field, 6, 1) def _populate_fields_tree(self, entity, parent=None): if parent is None: parent = self._fields_tree.invisibleRootItem() metadata = EntityMetadata.for_discriminator(entity) for field_name, field in sorted( metadata.all_fields.items(), key=lambda i: underscored_to_words(i[0])): child_metadata = None try: child_metadata = EntityMetadata.for_discriminator( field.type_name) except KeyError: pass if child_metadata: name = get_class_display_name(field.type_name) subparent = QTreeWidgetItem([name]) subparent.setData(0, Qt.UserRole, field_name) parent.addChild(subparent) self._populate_fields_tree(field.type_name, subparent) else: item = QTreeWidgetItem([underscored_to_words(field_name)]) item.setData(0, Qt.UserRole, (field_name, field)) parent.addChild(item) def on_fields_tree_sel_changed(self, item): data = item.data(0, Qt.UserRole) if data is not None and not isinstance(data, str): self._field_name = data[0] self._field = data[1] self._operators = operators_for_column_class(self._field.type_name) self._operator.clear() self._operator.addItems([o.label for o in self._operators]) self._added_condition = False def on_operator_choice(self, index): self._added_condition = False if self._value_widget: self.layout.removeWidget(self._value_widget) self._value_widget.deleteLater() if self._value_label: self.layout.removeWidget(self._value_label) self._value_label.deleteLater() self._value_label = None operator = self._operators[self._operator.currentRow()] value_label = self._create_value_label( operator.get_value_label(self._field)) self._value_widget = operator.get_value_widget(self, self._field) if not self._value_widget: return QWidget.setTabOrder(self._operator, self._value_widget) self._value_label = value_label if self._value_label: self._value_label.setBuddy(self._value_widget) self.layout.addWidget(self._value_label, 0, 2) self.layout.addWidget(self._value_widget, 1, 2) def _create_value_label(self, label): if not label: return label = QLabel(label, self) return label def on_add_clicked(self, evt): if not hasattr(self, "_field_name"): return self._added_condition = True json_path = [] parent_item = self._fields_tree.currentItem().parent() parent_data = parent_item.data(0, Qt.UserRole) if parent_item else None if isinstance(parent_data, str): json_path.append(parent_data) json_path.append(self._field_name) json_path = ".".join(json_path) operator_obj = self._operators[self._operator.currentRow()] expression = operator_obj.get_comparison_expression( self._field, FieldNamed(json_path), self._value_widget) self._search_expression_parts.append(expression) self._criteria_list.addItem( f"{underscored_to_words(self._field_name)} {operator_obj.label} {operator_obj.get_value_as_string(self._field, self._value_widget)}" ) @property def distance(self): return self._distance_field.value() def create_conditions(self): conditions = [] if self._search_expression_parts: for part in self._search_expression_parts: conditions.append(part) return conditions def on_remove_clicked(self, evt): selection = self._criteria_list.currentRow() if selection < 0: return del self._search_expression_parts[selection] self._criteria_list.removeItemWidget(self._criteria_list.currentItem()) def ok_clicked(self): if not self._added_condition: if QMessageBox.question( self, _("Question"), _("It appears that you forgot to add the current condition to the conditions list. Do you want to add it before starting the search?" )): self.on_add_clicked(None) super().ok_clicked()
class FrequencyWidget(ToolWidget): def __init__(self, image, parent=None): super(FrequencyWidget, self).__init__(parent) self.split_spin = QSpinBox() self.split_spin.setRange(0, 100) self.split_spin.setValue(15) self.split_spin.setSuffix(self.tr(" %")) self.smooth_spin = QSpinBox() self.smooth_spin.setRange(0, 100) self.smooth_spin.setValue(25) self.smooth_spin.setSuffix(self.tr(" %")) self.smooth_spin.setSpecialValueText(self.tr("Off")) self.thr_spin = QSpinBox() self.thr_spin.setRange(0, 100) self.thr_spin.setValue(0) self.thr_spin.setSuffix(self.tr(" %")) self.thr_spin.setSpecialValueText(self.tr("Off")) self.zero_label = QLabel() modify_font(self.zero_label, italic=True) self.filter_spin = QSpinBox() self.filter_spin.setRange(0, 15) self.filter_spin.setValue(0) self.filter_spin.setSuffix(self.tr(" px")) self.filter_spin.setSpecialValueText(self.tr("Off")) self.split_spin.valueChanged.connect(self.process) self.smooth_spin.valueChanged.connect(self.process) self.thr_spin.valueChanged.connect(self.process) self.filter_spin.valueChanged.connect(self.postprocess) self.image = image gray = cv.cvtColor(self.image, cv.COLOR_BGR2GRAY) rows, cols = gray.shape height = cv.getOptimalDFTSize(rows) width = cv.getOptimalDFTSize(cols) padded = cv.copyMakeBorder(gray, 0, height - rows, 0, width - cols, cv.BORDER_CONSTANT) self.dft = np.fft.fftshift(cv.dft(padded.astype(np.float32), flags=cv.DFT_COMPLEX_OUTPUT)) self.magnitude0, self.phase0 = cv.cartToPolar(self.dft[:, :, 0], self.dft[:, :, 1]) self.magnitude0 = cv.normalize(cv.log(self.magnitude0), None, 0, 255, cv.NORM_MINMAX) self.phase0 = cv.normalize(self.phase0, None, 0, 255, cv.NORM_MINMAX) self.magnitude = self.phase = None self.low_viewer = ImageViewer(self.image, self.image, self.tr("Low frequency"), export=True) self.high_viewer = ImageViewer(self.image, self.image, self.tr("High frequency"), export=True) self.mag_viewer = ImageViewer(self.image, None, self.tr("DFT Magnitude"), export=True) self.phase_viewer = ImageViewer(self.image, None, self.tr("DFT Phase"), export=True) self.process() self.low_viewer.viewChanged.connect(self.high_viewer.changeView) self.high_viewer.viewChanged.connect(self.low_viewer.changeView) self.mag_viewer.viewChanged.connect(self.phase_viewer.changeView) self.phase_viewer.viewChanged.connect(self.mag_viewer.changeView) top_layout = QHBoxLayout() top_layout.addWidget(QLabel(self.tr("Separation:"))) top_layout.addWidget(self.split_spin) top_layout.addWidget(QLabel(self.tr("Smooth:"))) top_layout.addWidget(self.smooth_spin) top_layout.addWidget(QLabel(self.tr("Threshold:"))) top_layout.addWidget(self.thr_spin) top_layout.addWidget(QLabel(self.tr("Filter:"))) top_layout.addWidget(self.filter_spin) top_layout.addWidget(self.zero_label) top_layout.addStretch() center_layout = QGridLayout() center_layout.addWidget(self.low_viewer, 0, 0) center_layout.addWidget(self.high_viewer, 0, 1) center_layout.addWidget(self.mag_viewer, 1, 0) center_layout.addWidget(self.phase_viewer, 1, 1) main_layout = QVBoxLayout() main_layout.addLayout(top_layout) main_layout.addLayout(center_layout) self.setLayout(main_layout) def process(self): start = time() rows, cols, _ = self.dft.shape mask = np.zeros((rows, cols), np.float32) half = np.sqrt(rows ** 2 + cols ** 2) / 2 radius = int(half * self.split_spin.value() / 100) mask = cv.circle(mask, (cols // 2, rows // 2), radius, 1, cv.FILLED) kernel = 2 * int(half * self.smooth_spin.value() / 100) + 1 mask = cv.GaussianBlur(mask, (kernel, kernel), 0) mask /= np.max(mask) threshold = int(self.thr_spin.value() / 100 * 255) if threshold > 0: mask[self.magnitude0 < threshold] = 0 zeros = (mask.size - np.count_nonzero(mask)) / mask.size * 100 else: zeros = 0 self.zero_label.setText(self.tr("(zeroed coefficients = {:.2f}%)").format(zeros)) mask2 = np.repeat(mask[:, :, np.newaxis], 2, axis=2) rows0, cols0, _ = self.image.shape low = cv.idft(np.fft.ifftshift(self.dft * mask2), flags=cv.DFT_SCALE) low = norm_mat(cv.magnitude(low[:, :, 0], low[:, :, 1])[:rows0, :cols0], to_bgr=True) self.low_viewer.update_processed(low) high = cv.idft(np.fft.ifftshift(self.dft * (1 - mask2)), flags=cv.DFT_SCALE) high = norm_mat(cv.magnitude(high[:, :, 0], high[:, :, 1]), to_bgr=True) self.high_viewer.update_processed(np.copy(high[: self.image.shape[0], : self.image.shape[1]])) self.magnitude = (self.magnitude0 * mask).astype(np.uint8) self.phase = (self.phase0 * mask).astype(np.uint8) self.postprocess() self.info_message.emit(self.tr(f"Frequency Split = {elapsed_time(start)}")) def postprocess(self): kernel = 2 * self.filter_spin.value() + 1 if kernel >= 3: magnitude = cv.GaussianBlur(self.magnitude, (kernel, kernel), 0) phase = cv.GaussianBlur(self.phase, (kernel, kernel), 0) # phase = cv.medianBlur(self.phase, kernel) else: magnitude = self.magnitude phase = self.phase self.mag_viewer.update_original(cv.cvtColor(magnitude, cv.COLOR_GRAY2BGR)) self.phase_viewer.update_original(cv.cvtColor(phase, cv.COLOR_GRAY2BGR))
class PlotsWidget(ToolWidget): def __init__(self, image, parent=None): super(PlotsWidget, self).__init__(parent) choices = ['Red', 'Green', 'Blue', 'Hue', 'Saturation', 'Value'] self.xaxis_combo = QComboBox() self.xaxis_combo.addItems(choices) self.xaxis_combo.setCurrentIndex(3) self.yaxis_combo = QComboBox() self.yaxis_combo.addItems(choices) self.yaxis_combo.setCurrentIndex(4) self.sampling_spin = QSpinBox() levels = int(np.log2(min(image.shape[:-1]))) self.sampling_spin.setRange(0, levels) self.sampling_spin.setSpecialValueText(self.tr('Off')) self.sampling_spin.setValue(1) self.sampling_spin.setSuffix(self.tr(' level(s)')) self.size_spin = QSpinBox() self.size_spin.setRange(1, 10) self.size_spin.setValue(2) self.size_spin.setSuffix(self.tr(' pt')) self.style_combo = QComboBox() self.markers = [ ',', '.', 'o', '8', 's', 'p', 'P', '*', 'h', 'H', 'X', 'D' ] self.style_combo.addItems([ 'pixel', 'point', 'circle', 'octa', 'square', 'penta', 'plus', 'star', 'hexa1', 'hexa2', 'cross', 'diamond' ]) self.alpha_spin = QDoubleSpinBox() self.alpha_spin.setRange(0, 1) self.alpha_spin.setDecimals(2) self.alpha_spin.setSingleStep(0.05) self.alpha_spin.setValue(1) self.colors_check = QCheckBox(self.tr('Show colors')) self.grid_check = QCheckBox(self.tr('Show grid')) self.norm_check = QCheckBox(self.tr('Normalized')) self.total_label = QLabel() img = np.copy(image) self.colors = [None] * (levels + 1) for scale in range(levels + 1): rgb = cv.cvtColor(img.astype(np.float32) / 255, cv.COLOR_BGR2RGB) hsv = cv.cvtColor(rgb, cv.COLOR_RGB2HSV) hsv[:, :, 0] /= 360 shape = (img.shape[0] * img.shape[1], img.shape[2]) self.colors[scale] = np.concatenate( (np.reshape(rgb, shape), np.reshape(hsv, shape)), axis=1) img = cv.pyrDown(img) figure = Figure() plot_canvas = FigureCanvas(figure) self.axes = plot_canvas.figure.subplots() self.redraw() figure.set_tight_layout(True) self.xaxis_combo.currentIndexChanged.connect(self.redraw) self.yaxis_combo.currentIndexChanged.connect(self.redraw) self.sampling_spin.valueChanged.connect(self.redraw) self.size_spin.valueChanged.connect(self.redraw) self.style_combo.currentIndexChanged.connect(self.redraw) self.alpha_spin.valueChanged.connect(self.redraw) self.colors_check.stateChanged.connect(self.redraw) self.grid_check.stateChanged.connect(self.redraw) self.norm_check.stateChanged.connect(self.redraw) bottom_layout = QGridLayout() bottom_layout.addWidget(NavigationToolbar(plot_canvas, self), 0, 0, 1, 5) bottom_layout.addWidget(QLabel(self.tr('X axis:')), 1, 0) bottom_layout.addWidget(self.xaxis_combo, 1, 1) bottom_layout.addWidget(QLabel(self.tr('Y Axis:')), 2, 0) bottom_layout.addWidget(self.yaxis_combo, 2, 1) bottom_layout.addWidget(QLabel(self.tr('Subsampling:')), 3, 0) bottom_layout.addWidget(self.sampling_spin, 3, 1) bottom_layout.addWidget(QLabel(self.tr('Point size:')), 1, 2) bottom_layout.addWidget(self.size_spin, 1, 3) bottom_layout.addWidget(QLabel(self.tr('Point style:')), 2, 2) bottom_layout.addWidget(self.style_combo, 2, 3) bottom_layout.addWidget(QLabel(self.tr('Point alpha:')), 3, 2) bottom_layout.addWidget(self.alpha_spin, 3, 3) bottom_layout.addWidget(self.colors_check, 1, 4) bottom_layout.addWidget(self.grid_check, 2, 4) bottom_layout.addWidget(self.total_label, 3, 4) main_layout = QVBoxLayout() main_layout.addWidget(plot_canvas) main_layout.addLayout(bottom_layout) self.setLayout(main_layout) def redraw(self): start = time() v = self.sampling_spin.value() x = self.colors[v][:, self.xaxis_combo.currentIndex()] y = self.colors[v][:, self.yaxis_combo.currentIndex()] xlim = self.axes.get_xlim() ylim = self.axes.get_ylim() s = self.size_spin.value()**2 c = None if not self.colors_check.isChecked( ) else self.colors[v][:, :3] a = self.alpha_spin.value() m = self.markers[self.style_combo.currentIndex()] self.axes.clear() self.axes.set_facecolor([0.5] * 3 if c is not None else [1.0] * 3) self.axes.scatter(x, y, s, c, m, alpha=a) self.axes.set_xlabel(self.xaxis_combo.currentText()) self.axes.set_ylabel(self.yaxis_combo.currentText()) self.axes.grid(self.grid_check.isChecked(), which='both') self.axes.set_xlim(xlim) self.axes.set_ylim(ylim) self.axes.figure.canvas.draw() self.total_label.setText(self.tr('[{} points]'.format(len(x)))) self.info_message.emit('Plot redraw = {}'.format(elapsed_time(start)))