def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None): super().__init__(layer=layer, targetImage=targetImage, parent=parent) # options optionList, optionNames = ['Auto'], ['Auto Orange Mask Removing'] self.listWidget1 = optionsWidget( options=optionList, optionNames=optionNames, exclusive=False, changed=lambda: self.dataChanged.emit()) self.listWidget1.checkOption(self.listWidget1.intNames[0]) self.options = self.listWidget1.options # layout vl = QVBoxLayout() vl.addWidget(self.listWidget1) self.setLayout(vl) self.setDefaults() self.setWhatsThis(""" <b> Negative Inversion</b><br> Negative films show an orange mask that must be corrected.<br> Automatic correction try to sample the mask color from an unexposed area of the negative film.<br> To do a <b>manual correction</b>, uncheck 'Auto Orange Mask Removing' and, next, Ctrl+Click the dark border of the image or, otherwise, a dark gray area.<br> """) # end of setWhatsThis
def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None): super().__init__(layer=layer, targetImage=targetImage, parent=parent) # options optionList1, optionNames1 = [ 'Free', 'Rotation', 'Translation', 'Align' ], ['Free Transformation', 'Rotation', 'Translation', 'Align'] self.listWidget1 = optionsWidget(options=optionList1, optionNames=optionNames1, exclusive=True, changed=self.dataChanged) optionList2, optionNames2 = ['Transparent' ], ['Set Transparent Pixels To Black'] self.listWidget2 = optionsWidget(options=optionList2, optionNames=optionNames2, exclusive=False, changed=self.dataChanged) self.options = UDict( (self.listWidget1.options, self.listWidget2.options)) # set initial selection to Perspective self.listWidget1.checkOption(optionList1[0]) pushButton1 = QPushButton(' Reset Transformation ') pushButton1.adjustSize() pushButton1.clicked.connect(self.reset) # layout l = QVBoxLayout() l.setAlignment(Qt.AlignTop) l.addWidget(self.listWidget1) l.addWidget(self.listWidget2) hl = QHBoxLayout() hl.setAlignment(Qt.AlignHCenter) hl.addWidget(pushButton1) l.addLayout(hl) self.setLayout(l) self.adjustSize() self.setDefaults() self.setWhatsThis(""" <b>Geometric transformation :</b><br> Choose a transformation type and drag either corner of the image using the small square red buttons.<br> Ctrl+Alt+Drag to change the <b>initial positions</b> of buttons.<br> Check <i>Align</i> to align the image with the underlying layers (for example to blend bracketed images). Note that this option may modify or cancel the current transformation. """) # end of setWhatsThis
def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None): super().__init__(parent=parent, targetImage=targetImage, layer=layer) self.setMinimumSize(axeSize, axeSize + 80) # color wheel size self.cwSize = axeSize * 0.95 self.setAttribute(Qt.WA_DeleteOnClose) # options optionList = ['Monochrome'] listWidget1 = optionsWidget(options=optionList, exclusive=False, changed=self.dataChanged) listWidget1.setMinimumWidth(listWidget1.sizeHintForColumn(0) + 5) listWidget1.setMinimumHeight(listWidget1.sizeHintForRow(0) * len(optionList) + 5) self.options = listWidget1.options # barycentric coordinate basis : the 3 base points form an equilateral triangle h = self.cwSize - 50 s = h * 2 / np.sqrt(3) self.R, self.G, self.B = QPointF(10, h + 20), QPointF(10 + s, h + 20), QPointF(10 + s / 2, 20) # Conversion matrix from cartesian coordinates (x, y, 1) to barycentric coordinates (alpha, beta, gamma) self.M = np.array([[self.R.x(), self.G.x(), self.B.x()], [self.R.y(), self.G.y(), self.B.y()], [1, 1, 1 ]]) self.invM = np.linalg.inv(self.M) self.setBackgroundImage() # active points self.rPoint = activeMixerPoint(self.R.x(), self.R.y(), color=Qt.red, fillColor=Qt.white) self.rPoint.source = self.R self.gPoint = activeMixerPoint(self.G.x(), self.G.y(), color=Qt.green, fillColor=Qt.white) self.gPoint.source = self.G self.bPoint = activeMixerPoint(self.B.x(), self.B.y(), color=Qt.blue, fillColor=Qt.white) self.bPoint.source = self.B graphicsScene = self.scene() for point in [self.rPoint, self.gPoint, self.bPoint]: graphicsScene.addItem(point) gl = QGridLayout() gl.addWidget(listWidget1, 0, 0, 2, 2) self.addCommandLayout(gl) self.setDefaults() self.setWhatsThis( """<b>Channel Mixer</b><br> To <b>mix the R, G, B channels</b>, drag the 3 control points inside the triangle.<br> The triangle vertices and the control points correspond to channels. The closer a control point is to a vertex, the greater the corresponding channel contribution. """ ) # end of setWhatsThis
def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None): super().__init__(layer=layer, targetImage=targetImage, parent=parent) self.setMinimumSize(axeSize, axeSize+100) # contrast spline viewer self.contrastForm = None # options optionList1, optionNames1 = ['Multi-Mode', 'CLAHE'], ['Multi-Mode', 'CLAHE'] self.listWidget1 = optionsWidget(options=optionList1, optionNames=optionNames1, exclusive=True, changed=lambda: self.dataChanged.emit()) self.listWidget1.checkOption(self.listWidget1.intNames[0]) self.listWidget1.setStyleSheet("QListWidget {border: 0px;} QListWidget::item {border: 0px; padding-left: 0px;}") optionList2, optionNames2 = ['High', 'manualCurve'], ['Preserve Highlights', 'Show Contrast Curve'] def optionList2Change(item): if item.internalName == 'High': # force to recalculate the spline self.layer.autoSpline = True self.dataChanged.emit() self.listWidget2 = optionsWidget(options=optionList2, optionNames=optionNames2, exclusive=False, changed=optionList2Change) self.listWidget2.checkOption(self.listWidget2.intNames[0]) self.listWidget2.setStyleSheet("QListWidget {border: 0px;} QListWidget::item {border: 0px; padding-left: 0px;}") self.options = UDict((self.listWidget1.options, self.listWidget2.options)) # contrast slider self.sliderContrast = QbLUeSlider(Qt.Horizontal) self.sliderContrast.setStyleSheet(QbLUeSlider.bLueSliderDefaultIBWStylesheet) self.sliderContrast.setRange(0, 10) self.sliderContrast.setSingleStep(1) contrastLabel = QbLUeLabel() contrastLabel.setMaximumSize(150, 30) contrastLabel.setText("Contrast Level") contrastLabel.doubleClicked.connect(lambda: self.sliderContrast.setValue(self.contrast2Slider(self.contrastDefault))) self.contrastValue = QLabel() font = self.contrastValue.font() metrics = QFontMetrics(font) w = metrics.width("100") h = metrics.height() self.contrastValue.setMinimumSize(w, h) self.contrastValue.setMaximumSize(w, h) self.contrastValue.setText(str("{:d}".format(self.sliderContrast.value()))) # contrast changed event handler. def contrastUpdate(value): self.contrastValue.setText(str("{:d}".format(self.sliderContrast.value()))) # move not yet terminated or value not modified if self.sliderContrast.isSliderDown() or self.slider2Contrast(value) == self.contrastCorrection: return self.sliderContrast.valueChanged.disconnect() self.sliderContrast.sliderReleased.disconnect() self.contrastCorrection = self.slider2Contrast(self.sliderContrast.value()) # force to recalculate the spline self.layer.autoSpline = True self.dataChanged.emit() self.sliderContrast.valueChanged.connect(contrastUpdate) self.sliderContrast.sliderReleased.connect(lambda: contrastUpdate(self.sliderContrast.value())) self.sliderContrast.valueChanged.connect(contrastUpdate) self.sliderContrast.sliderReleased.connect(lambda: contrastUpdate(self.sliderContrast.value())) # saturation slider self.sliderSaturation = QbLUeSlider(Qt.Horizontal) self.sliderSaturation.setStyleSheet(QbLUeSlider.bLueSliderDefaultColorStylesheet) self.sliderSaturation.setRange(0, 100) self.sliderSaturation.setSingleStep(1) saturationLabel = QbLUeLabel() saturationLabel.setMaximumSize(150, 30) saturationLabel.setText("Saturation") saturationLabel.doubleClicked.connect(lambda: self.sliderSaturation.setValue(self.saturation2Slider(self.saturationDefault))) self.saturationValue = QLabel() font = self.saturationValue.font() metrics = QFontMetrics(font) w = metrics.width("100") h = metrics.height() self.saturationValue.setMinimumSize(w, h) self.saturationValue.setMaximumSize(w, h) self.saturationValue.setText(str("{:+d}".format(self.sliderContrast.value()))) # saturation changed event handler def saturationUpdate(value): self.saturationValue.setText(str("{:+d}".format(int(self.slidersaturation2User(self.sliderSaturation.value()))))) # move not yet terminated or value not modified if self.sliderSaturation.isSliderDown() or self.slider2Saturation(value) == self.satCorrection: return self.sliderSaturation.valueChanged.disconnect() self.sliderSaturation.sliderReleased.disconnect() self.satCorrection = self.slider2Saturation(self.sliderSaturation.value()) self.dataChanged.emit() self.sliderSaturation.valueChanged.connect(saturationUpdate) self.sliderSaturation.sliderReleased.connect(lambda: saturationUpdate(self.sliderSaturation.value())) self.sliderSaturation.valueChanged.connect(saturationUpdate) self.sliderSaturation.sliderReleased.connect(lambda: saturationUpdate(self.sliderSaturation.value())) # brightness slider self.sliderBrightness = QbLUeSlider(Qt.Horizontal) self.sliderBrightness.setStyleSheet(QbLUeSlider.bLueSliderDefaultBWStylesheet) self.sliderBrightness.setRange(0, 100) self.sliderBrightness.setSingleStep(1) brightnessLabel = QbLUeLabel() brightnessLabel.setMaximumSize(150, 30) brightnessLabel.setText("Brightness") brightnessLabel.doubleClicked.connect(lambda: self.sliderBrightness.setValue(self.brightness2Slider(self.brightnessDefault))) self.brightnessValue = QLabel() font = self.brightnessValue.font() metrics = QFontMetrics(font) w = metrics.width("100") h = metrics.height() self.brightnessValue.setMinimumSize(w, h) self.brightnessValue.setMaximumSize(w, h) self.brightnessValue.setText(str("{:+d}".format(self.sliderContrast.value()))) # brightness changed event handler def brightnessUpdate(value): self.brightnessValue.setText(str("{:+d}".format(int(self.sliderBrightness2User(self.sliderBrightness.value()))))) # move not yet terminated or value not modified if self.sliderBrightness.isSliderDown() or self.slider2Brightness(value) == self.brightnessCorrection: return self.sliderBrightness.valueChanged.disconnect() self.sliderBrightness.sliderReleased.disconnect() self.brightnessCorrection = self.slider2Brightness(self.sliderBrightness.value()) self.dataChanged.emit() self.sliderBrightness.valueChanged.connect(brightnessUpdate) self.sliderBrightness.sliderReleased.connect(lambda: brightnessUpdate(self.sliderBrightness.value())) self.sliderBrightness.valueChanged.connect(brightnessUpdate) self.sliderBrightness.sliderReleased.connect(lambda: brightnessUpdate(self.sliderBrightness.value())) # attributes initialized in setDefaults, declared here # for the sake of correctness self.contrastCorrection = None # range self.satCorrection = None # range -0.5..0.5 self.brightnessCorrection = None # range -0.5..0.5 # layout l = QVBoxLayout() l.setAlignment(Qt.AlignTop) gb1 = QGroupBox() gb1.setStyleSheet("QGroupBox {border: 1px solid gray; border-radius: 4px}") l1 = QVBoxLayout() ct = QLabel() ct.setText('Contrast') l.setAlignment(Qt.AlignTop) l1.addWidget(ct) l1.addWidget(self.listWidget1) gb1.setLayout(l1) l.addWidget(gb1) l.addWidget(self.listWidget2) l.addWidget(contrastLabel) hl = QHBoxLayout() hl.addWidget(self.contrastValue) hl.addWidget(self.sliderContrast) l.addLayout(hl) l.addWidget(brightnessLabel) hl3 = QHBoxLayout() hl3.addWidget(self.brightnessValue) hl3.addWidget(self.sliderBrightness) l.addLayout(hl3) l.addWidget(saturationLabel) hl2 = QHBoxLayout() hl2.addWidget(self.saturationValue) hl2.addWidget(self.sliderSaturation) l.addLayout(hl2) self.setLayout(l) self.adjustSize() self.setStyleSheet("QListWidget, QLabel {font : 7pt;}") self.setDefaults() self.setWhatsThis( """<b>Contrast Brightness Saturation</b><br> <b>Contrast</b> is enhanced using one of these two methods:<br> - <b>CLAHE</b> : increases the local contrast.<br> - <b>Multi-Mode</b> : increases the local contrast and the contrast between regions of the image.<br> For both methods the contrast slider controls the level of the correction.<br> With Multi-Mode enabled, use the option <b>Show Contrast Curve</b> to edit the correction curve and check <b>Preserve Highlights</b> for softer highlights.<br> <b>Brightness</b> and <b>Saturation</b> corrections are non linear to limit clipping.<br> Sliders are <b>reset</b> to their default value by double clicking the name of the slider.<br> """ ) # end setWhatsThis
def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None, colorModel='HSV'): super().__init__(targetImage=targetImage, axeSize=axeSize, layer=layer, parent=parent) graphicsScene = self.scene() graphicsScene.colorModel = colorModel # hue curve init. cubic = activeCubicSpline(axeSize) graphicsScene.addItem(cubic) graphicsScene.cubicR = cubic cubic.channel = channelValues.Hue cubic.initFixedPoints() # sat curve init. cubic = activeCubicSpline(axeSize) graphicsScene.addItem(cubic) graphicsScene.cubicG = cubic cubic.channel = channelValues.Sat cubic.initFixedPoints() # brightness curve init. cubic = activeCubicSpline(axeSize) graphicsScene.addItem(cubic) graphicsScene.cubicB = cubic cubic.channel = channelValues.Br cubic.initFixedPoints() # init histograms self.updateHists() # set current curve to sat graphicsScene.cubicItem = graphicsScene.cubicG graphicsScene.cubicItem.setVisible(True) # buttons pushButton1 = QPushButton("Reset Current") pushButton1.clicked.connect(self.resetCurve) pushButton2 = QPushButton("Reset All") pushButton2.clicked.connect(self.resetAllCurves) # options options = ['H', 'S', 'B'] self.listWidget1 = optionsWidget(options=options, exclusive=True) self.listWidget1.setGeometry( 0, 0, self.listWidget1.sizeHintForColumn(0) + 5, self.listWidget1.sizeHintForRow(0) * len(options) + 5) # selection changed handler curves = [ graphicsScene.cubicR, graphicsScene.cubicG, graphicsScene.cubicB ] curveDict = dict(zip(options, curves)) def onSelect1(item): self.scene().cubicItem.setVisible(False) self.scene().cubicItem = curveDict[item.text()] self.scene().cubicItem.setVisible(True) # draw histogram self.scene().invalidate( QRectF(0.0, -self.scene().axeSize, self.scene().axeSize, self.scene().axeSize), QGraphicsScene.BackgroundLayer) self.listWidget1.onSelect = onSelect1 # set initial selection to Saturation item = self.listWidget1.items[options[1]] item.setCheckState(Qt.Checked) self.listWidget1.select(item) # layout gl = QGridLayout() gl.addWidget(self.listWidget1, 0, 0, 2, 1) for i, button in enumerate([pushButton1, pushButton2]): gl.addWidget(button, i, 1) self.addCommandLayout(gl) self.setWhatsThis("""<b>HSV curves</b><br>""" + self.whatsThis()) def f(): layer = graphicsScene.layer layer.applyToStack() layer.parentImage.onImageChanged() self.scene().cubicR.curveChanged.sig.connect(f) self.scene().cubicG.curveChanged.sig.connect(f) self.scene().cubicB.curveChanged.sig.connect(f)
def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None): super().__init__(parent=parent, targetImage=targetImage, layer=layer) self.setMinimumSize(axeSize, axeSize + 100) # color wheel size self.cwSize = axeSize * 0.95 self.setAttribute(Qt.WA_DeleteOnClose) # options optionList = ['Monochrome', 'Luminosity'] listWidget1 = optionsWidget(options=optionList, exclusive=False, changed=self.dataChanged, flow=optionsWidget.LeftToRight) listWidget1.setMaximumHeight( listWidget1.sizeHintForRow(0) + 5) # mandatory although sizePolicy is set to minimum ! self.options = listWidget1.options # barycentric coordinate basis : the 3 base points form an equilateral triangle h = self.cwSize - 50 s = h * 2 / np.sqrt(3) self.R, self.G, self.B = QPointF(10, h + 20), QPointF( 10 + s, h + 20), QPointF(10 + s / 2, 20) # Conversion matrix from cartesian coordinates (x, y, 1) to barycentric coordinates (alpha, beta, gamma) self.M = np.array([[self.R.x(), self.G.x(), self.B.x()], [self.R.y(), self.G.y(), self.B.y()], [1, 1, 1]]) self.invM = np.linalg.inv(self.M) self.setBackgroundImage() # active points self.rPoint = activeMixerPoint(self.R.x(), self.R.y(), color=Qt.red, fillColor=Qt.white, grForm=self) self.rPoint.source = self.R self.gPoint = activeMixerPoint(self.G.x(), self.G.y(), color=Qt.green, fillColor=Qt.white, grForm=self) self.gPoint.source = self.G self.bPoint = activeMixerPoint(self.B.x(), self.B.y(), color=Qt.blue, fillColor=Qt.white, grForm=self) self.bPoint.source = self.B graphicsScene = self.scene() for point in [self.rPoint, self.gPoint, self.bPoint]: graphicsScene.addItem(point) gl = QVBoxLayout() gl.setAlignment(Qt.AlignTop) container = self.addCommandLayout(gl) self.values = QLabel() vh = QFontMetrics(self.values.font()).height() self.values.setMaximumSize(150, vh * 4) # 4 lines gl.addWidget(self.values) gl.addWidget(listWidget1) # don't commute the 3 next lines ! self.setDefaults() self.adjustSize() self.setViewportMargins(0, 0, 0, container.height()) self.setWhatsThis("""<b>Channel Mixer</b><br> The triangle vertices and the three control points correspond to the R, G, B channels.<br> To <b>mix the channels</b>, drag the 3 control points inside the triangle. The closer a control point is to a vertex, the greater the corresponding channel contribution. <br> To obtain <b>monochrome images</b> only, check the option <i>Monochrome.</i><br> To modify the <b>luminosity channel</b> only (volume mode), check the option <i>Luminosity.</i><br> """) # end of setWhatsThis
def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None): super().__init__(layer=layer, targetImage=targetImage, parent=parent) ####################################### # Libraw correspondences: # rgb_xyz_matrix is libraw cam_xyz # camera_whitebalance is libraw cam_mul # daylight_whitebalance is libraw pre_mul # dng correspondences: # ASSHOTNEUTRAL tag value is (X,Y,Z) = 1 / rawpyObj.camera_whitebalance ########################################## rawpyObj = layer.parentImage.rawImage # constants and as shot values self.XYZ2CameraMatrix = rawpyObj.rgb_xyz_matrix[:3, :] self.XYZ2CameraInverseMatrix = np.linalg.inv(self.XYZ2CameraMatrix) # initial post processing multipliers (as shot) m1, m2, m3, m4 = rawpyObj.camera_whitebalance self.asShotMultipliers = ( m1 / m2, 1.0, m3 / m2, m4 / m2 ) # normalization is mandatory : for nef files white balance is around 256 self.asShotTemp, self.asShotTint = multipliers2TemperatureAndTint( *1 / np.array(self.asShotMultipliers[:3]), self.XYZ2CameraMatrix) self.rawMultipliers = self.asShotMultipliers # rawpyObj.camera_whitebalance # = 1/(dng ASSHOTNEUTRAL tag value) self.sampleMultipliers = False self.samples = [] ######################################## # XYZ-->Camera conversion matrix: # Last row is zero for RGB cameras (cf. rawpy and libraw docs). # type ndarray, shape (4,3) ######################################### # attributes initialized in setDefaults, declared here for the sake of correctness self.tempCorrection, self.tintCorrection, self.expCorrection, self.highCorrection,\ self.contCorrection, self.satCorrection, self.brCorrection = [None] * 7 # contrast spline vie (initialized by setContrastSpline) self.contrastForm = None # tone spline view (initialized by setToneSpline) self.toneForm = None # dock containers for contrast and tome forms self.dockC, self.dockT = None, None # options optionList0, optionNames0 = ['Auto Brightness', 'Preserve Highlights' ], ['Auto Expose', 'Preserve Highlights'] optionList1, optionNames1 = ['Auto WB', 'Camera WB', 'User WB' ], ['Auto', 'Camera (As Shot)', 'User'] optionList2, optionNames2 = [ 'cpLookTable', 'cpToneCurve', 'manualCurve' ], [ 'Use Camera Profile Look Table', 'Show Tone Curves', 'Show Contrast Curve' ] self.listWidget1 = optionsWidget( options=optionList0, optionNames=optionNames0, exclusive=False, changed=lambda: self.dataChanged.emit(1)) self.listWidget2 = optionsWidget( options=optionList1, optionNames=optionNames1, exclusive=True, changed=lambda: self.dataChanged.emit(1)) self.listWidget3 = optionsWidget( options=optionList2, optionNames=optionNames2, exclusive=False, changed=lambda: self.dataChanged.emit(2)) self.options = UDict( (self.listWidget1.options, self.listWidget2.options, self.listWidget3.options)) # display the 'as shot' temperature item = self.listWidget2.item(1) item.setText(item.text() + ' : %d' % self.asShotTemp) # temperature slider self.sliderTemp = QbLUeSlider(Qt.Horizontal) self.sliderTemp.setStyleSheet( QbLUeSlider.bLueSliderDefaultColorStylesheet) self.sliderTemp.setRange(0, 100) self.sliderTemp.setSingleStep(1) self.tempLabel = QLabel() self.tempLabel.setText("Temp") self.tempValue = QLabel() font = self.tempValue.font() metrics = QFontMetrics(font) w = metrics.width("10000") h = metrics.height() self.tempValue.setMinimumSize(w, h) self.tempValue.setMaximumSize(w, h) self.tempValue.setText( str("{:.0f}".format(self.slider2Temp(self.sliderTemp.value())))) self.sliderTemp.valueChanged.connect( self.tempUpdate) # signal send new value as parameter self.sliderTemp.sliderReleased.connect(lambda: self.tempUpdate( self.sliderTemp.value())) # signal pass no parameter # tint slider self.sliderTint = QbLUeSlider(Qt.Horizontal) self.sliderTint.setStyleSheet( QbLUeSlider.bLueSliderDefaultIMGColorStylesheet) self.sliderTint.setRange(0, 150) self.sliderTint.setSingleStep(1) self.tintLabel = QLabel() self.tintLabel.setText("Tint") self.tintValue = QLabel() font = self.tempValue.font() metrics = QFontMetrics(font) w = metrics.width("100") h = metrics.height() self.tintValue.setMinimumSize(w, h) self.tintValue.setMaximumSize(w, h) self.tintValue.setText( str("{:.0f}".format(self.sliderTint2User( self.sliderTint.value())))) self.sliderTint.valueChanged.connect(self.tintUpdate) self.sliderTint.sliderReleased.connect(lambda: self.tintUpdate( self.sliderTint.value())) # signal pass no parameter) ###################### # From libraw and dcraw sources: # Exposure and brightness are curve transformations. # Exposure curve is y = alpha*x, with cubic root ending; it is applied before demosaicing. # Brightness is (similar to) y = x**alpha and part of gamma transformation from linear sRGB to RGB. # Exposure and brightness both dilate the histogram towards highlights. # Exposure dilatation is uniform (homothety), brightness dilataion is # maximum for the midtones and the highlghts are preserved. # As a consequence, normal workflow begins with the adjustment of exposure, # to fill the entire range of the histogram and to adjust the highlights. Next, # one adjusts the brightness to put the midtones at the level we want them to be. # Cf. https://www.cambridgeincolour.com/forums/thread653.htm ##################### # profile combo self.dngDict = self.setCameraProfilesCombo() # cameraProfilesCombo index changed event handler def cameraProfileUpdate(value): self.dngDict = self.cameraProfilesCombo.itemData(value) if self.options['cpToneCurve']: toneCurve = dngProfileToneCurve( self.dngDict.get('ProfileToneCurve', [])) self.toneForm.baseCurve = [ QPointF(x * axeSize, -y * axeSize) for x, y in zip(toneCurve.dataX, toneCurve.dataY) ] self.toneForm.update() # recompute as shot temp and tint using new profile self.asShotTemp, self.asShotTint = multipliers2TemperatureAndTint( *1 / np.array(self.asShotMultipliers[:3]), self.XYZ2CameraMatrix, dngDict=self.dngDict ) # TODO 6/12/19 added keyword dngDict validate # display updated as shot temp item = self.listWidget2.item(1) item.setText(item.text().split(":")[0] + ': %d' % self.asShotTemp) # invalidate cache self.layer.bufCache_HSV_CV32 = None self.dataChanged.emit(2) # 2 = no postprocessing self.cameraProfilesCombo.currentIndexChanged.connect( cameraProfileUpdate) # denoising combo self.denoiseCombo = QComboBox() items = OrderedDict([('Off', 0), ('Medium', 1), ('Full', 2)]) for key in items: self.denoiseCombo.addItem(key, items[key]) # denoiseCombo index changed event handler def denoiseUpdate(value): self.denoiseValue = self.denoiseCombo.itemData(value) self.dataChanged.emit(1) self.denoiseCombo.currentIndexChanged.connect(denoiseUpdate) # overexposed area restoration self.overexpCombo = QComboBox() items = OrderedDict([('Clip', 0), ('Ignore', 1), ('Blend', 2), ('Reconstruct', 3)]) for key in items: self.overexpCombo.addItem(key, items[key]) # overexpCombo index changed event handler def overexpUpdate(value): self.overexpValue = self.overexpCombo.itemData(value) self.dataChanged.emit(1) self.overexpCombo.currentIndexChanged.connect(overexpUpdate) # exp slider self.sliderExp = QbLUeSlider(Qt.Horizontal) self.sliderExp.setStyleSheet(QbLUeSlider.bLueSliderDefaultBWStylesheet) self.sliderExp.setRange(0, 100) self.sliderExp.setSingleStep(1) self.expLabel = QLabel() self.expLabel.setText("Exp.") self.expValue = QLabel() font = self.expValue.font() metrics = QFontMetrics(font) w = metrics.width("+1.0") h = metrics.height() self.expValue.setMinimumSize(w, h) self.expValue.setMaximumSize(w, h) self.expValue.setText( str("{:.1f}".format(self.slider2Exp(self.sliderExp.value())))) # exp done event handler def expUpdate(value): self.expValue.setText( str("{:+.1f}".format( self.sliderExp2User(self.sliderExp.value())))) # move not yet terminated or value not modified if self.sliderExp.isSliderDown() or self.slider2Exp( value) == self.expCorrection: return try: self.sliderExp.valueChanged.disconnect() self.sliderExp.sliderReleased.disconnect() except RuntimeError: pass # rawpy: expCorrection range is -2.0...3.0, boiling down to exp_shift range 2**(-2)=0.25...2**3=8.0 self.expCorrection = self.slider2Exp(self.sliderExp.value()) self.dataChanged.emit(1) self.sliderExp.valueChanged.connect( expUpdate) # send new value as parameter self.sliderExp.sliderReleased.connect(lambda: expUpdate( self.sliderExp.value())) # signal pass no parameter self.sliderExp.valueChanged.connect( expUpdate) # send new value as parameter self.sliderExp.sliderReleased.connect(lambda: expUpdate( self.sliderExp.value())) # signal pass no parameter # brightness slider brSlider = QbLUeSlider(Qt.Horizontal) brSlider.setRange(1, 101) self.sliderExp.setSingleStep(1) brSlider.setStyleSheet(QbLUeSlider.bLueSliderDefaultBWStylesheet) self.sliderBrightness = brSlider brLabel = QLabel() brLabel.setText("Bright.") self.brValue = QLabel() font = self.expValue.font() metrics = QFontMetrics(font) w = metrics.width("+99") h = metrics.height() self.brValue.setMinimumSize(w, h) self.brValue.setMaximumSize(w, h) self.brValue.setText( str("{:+d}".format( int(self.brSlider2User(self.sliderBrightness.value()))))) # brightness done event handler def brUpdate(value): self.brValue.setText( str("{:+d}".format( int(self.brSlider2User(self.sliderBrightness.value()))))) # move not yet terminated or value not modified if self.sliderBrightness.isSliderDown() or self.slider2Br( value) == self.brCorrection: return try: self.sliderBrightness.valueChanged.disconnect() self.sliderBrightness.sliderReleased.disconnect() except RuntimeError: pass self.brCorrection = self.slider2Br(self.sliderBrightness.value()) self.dataChanged.emit(1) self.sliderBrightness.sliderReleased.connect( lambda: brUpdate(self.sliderBrightness.value())) self.sliderBrightness.valueChanged.connect( brUpdate) # send new value as parameter self.sliderBrightness.valueChanged.connect( brUpdate) # send new value as parameter self.sliderBrightness.sliderReleased.connect( lambda: brUpdate(self.sliderBrightness.value())) # contrast slider self.sliderCont = QbLUeSlider(Qt.Horizontal) self.sliderCont.setStyleSheet( QbLUeSlider.bLueSliderDefaultBWStylesheet) self.sliderCont.setRange(0, 20) self.sliderCont.setSingleStep(1) self.contLabel = QLabel() self.contLabel.setText("Cont.") self.contValue = QLabel() font = self.contValue.font() metrics = QFontMetrics(font) w = metrics.width("100") h = metrics.height() self.contValue.setMinimumSize(w, h) self.contValue.setMaximumSize(w, h) self.contValue.setText( str("{:.0f}".format(self.slider2Cont(self.sliderCont.value())))) # cont done event handler def contUpdate(value): self.contValue.setText( str("{:.0f}".format(self.slider2Cont( self.sliderCont.value())))) # move not yet terminated or value not modified if self.sliderCont.isSliderDown() or self.slider2Cont( value) == self.tempCorrection: return try: self.sliderCont.valueChanged.disconnect() self.sliderCont.sliderReleased.disconnect() except RuntimeError: pass self.contCorrection = self.slider2Cont(self.sliderCont.value()) self.contValue.setText(str("{:+d}".format(self.contCorrection))) # force to recalculate the spline self.layer.autoSpline = True self.dataChanged.emit( 3) # no postprocessing and no camera profile stuff self.sliderCont.valueChanged.connect( contUpdate) # send new value as parameter self.sliderCont.sliderReleased.connect(lambda: contUpdate( self.sliderCont.value())) # signal has no parameter self.sliderCont.valueChanged.connect( contUpdate) # send new value as parameter self.sliderCont.sliderReleased.connect(lambda: contUpdate( self.sliderCont.value())) # signal has no parameter # saturation slider self.sliderSat = QbLUeSlider(Qt.Horizontal) self.sliderSat.setStyleSheet( QbLUeSlider.bLueSliderDefaultColorStylesheet) self.sliderSat.setRange(0, 100) self.sliderSat.setSingleStep(1) satLabel = QLabel() satLabel.setText("Sat.") self.satValue = QLabel() font = self.satValue.font() metrics = QFontMetrics(font) w = metrics.width("+10") h = metrics.height() self.satValue.setMinimumSize(w, h) self.satValue.setMaximumSize(w, h) self.satValue.setText( str("{:+d}".format(self.slider2Sat(self.sliderSat.value())))) """sat done event handler""" def satUpdate(value): self.satValue.setText( str("{:+d}".format(self.slider2Sat(self.sliderSat.value())))) # move not yet terminated or value not modified if self.sliderSat.isSliderDown() or self.slider2Sat( value) == self.satCorrection: return try: self.sliderSat.valueChanged.disconnect() self.sliderSat.sliderReleased.disconnect() except RuntimeError: pass self.satCorrection = self.slider2Sat(self.sliderSat.value()) self.dataChanged.emit( 3) # no post processing and no camera profile stuff self.sliderSat.valueChanged.connect( satUpdate) # send new value as parameter self.sliderSat.sliderReleased.connect(lambda: satUpdate( self.sliderSat.value())) # signal has no parameter self.sliderSat.valueChanged.connect( satUpdate) # send new value as parameter self.sliderSat.sliderReleased.connect(lambda: satUpdate( self.sliderSat.value())) # signal has no parameter # layout l = QVBoxLayout() l.addWidget(self.listWidget3) hl01 = QHBoxLayout() hl01.addWidget(QLabel('Camera Profile')) hl01.addWidget(self.cameraProfilesCombo) l.addLayout(hl01) hl0 = QHBoxLayout() hl0.addWidget(QLabel('Denoising')) hl0.addWidget(self.denoiseCombo) l.addLayout(hl0) hl00 = QHBoxLayout() hl00.addWidget(QLabel('Overexp. Restoration')) hl00.addWidget(self.overexpCombo) l.addLayout(hl00) hl1 = QHBoxLayout() hl1.addWidget(self.expLabel) hl1.addWidget(self.expValue) hl1.addWidget(self.sliderExp) l.addLayout(hl1) hl8 = QHBoxLayout() hl8.addWidget(brLabel) hl8.addWidget(self.brValue) hl8.addWidget(self.sliderBrightness) l.addLayout(hl8) l.addWidget(self.listWidget1) vl1 = QVBoxLayout() vl1.addWidget(self.listWidget2) gb1 = QGroupBox() gb1.setTitle('White Balance') hl2 = QHBoxLayout() hl2.addWidget(self.tempLabel) hl2.addWidget(self.tempValue) hl2.addWidget(self.sliderTemp) hl3 = QHBoxLayout() hl3.addWidget(self.tintLabel) hl3.addWidget(self.tintValue) hl3.addWidget(self.sliderTint) vl1.addLayout(hl2) vl1.addLayout(hl3) gb1.setLayout(vl1) l.addWidget(gb1) hl4 = QHBoxLayout() hl4.addWidget(self.contLabel) hl4.addWidget(self.contValue) hl4.addWidget(self.sliderCont) hl7 = QHBoxLayout() hl7.addWidget(satLabel) hl7.addWidget(self.satValue) hl7.addWidget(self.sliderSat) # separator sep = QFrame() sep.setFrameShape(QFrame.HLine) sep.setFrameShadow(QFrame.Sunken) l.addWidget(sep) l.addLayout(hl4) l.addLayout(hl7) l.addStretch(1) self.setLayout(l) self.adjustSize() self.setDefaults() self.setWhatsThis("""<b>Development of raw files</b><br> <b>Default settings</b> are a good starting point.<br> A <b>Tone Curve</b> is applied to the raw image prior to postprocessing.<br> The cuvre can be edited by checking the option <b>Show Tone Curve</b>; this option works best with manual exposure.<br> <b>Contrast</b> correction is based on an automatic algorithm well suited to multi-mode histograms.<br> <b>Brightness, Contrast</b> and <b>Saturation</b> levels</b> are adjustable with the correponding sliders.<br> The <b>Contrast Curve</b> can be edited manually by checking the option <b>Show Contrast Curve</b>.<br> Uncheck <b>Auto Expose</b> to adjust the exposure manually.<br> The <b>OverExp. Rest.</b> slider controls the mode of restoration of overexposed areas. Valid values are 0 to 3 (0=clip;1=unclip;2=blend;3=rebuild); (with Auto Exposed checked the mode is clip).<br> """) # end of setWhatsThis
def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None): super().__init__(targetImage=targetImage, axeSize=axeSize, layer=layer, parent=parent) # Brightness curve cubic = activeCubicSpline(axeSize) graphicsScene = self.scene() graphicsScene.addItem(cubic) graphicsScene.cubicRGB = cubic cubic.channel = channelValues.RGB cubic.histImg = self.scene().layer.inputImg().histogram( size=graphicsScene.axeSize, bgColor=graphicsScene.bgColor, chans=[], mode='Luminosity') cubic.initFixedPoints() # Red curve cubic = activeCubicSpline(axeSize) graphicsScene.addItem(cubic) graphicsScene.cubicR = cubic cubic.channel = channelValues.Red cubic.histImg = self.scene().layer.inputImg().histogram( size=graphicsScene.axeSize, bgColor=graphicsScene.bgColor, chans=channelValues.Red) cubic.initFixedPoints() # Green curve cubic = activeCubicSpline(axeSize) graphicsScene.addItem(cubic) graphicsScene.cubicG = cubic cubic.channel = channelValues.Green cubic.histImg = self.scene().layer.inputImg().histogram( size=graphicsScene.axeSize, bgColor=graphicsScene.bgColor, chans=channelValues.Green) cubic.initFixedPoints() # Blue curve cubic = activeCubicSpline(axeSize) graphicsScene.addItem(cubic) graphicsScene.cubicB = cubic cubic.channel = channelValues.Blue cubic.histImg = self.scene().layer.inputImg().histogram( size=graphicsScene.axeSize, bgColor=graphicsScene.bgColor, chans=channelValues.Blue) cubic.initFixedPoints() # set current curve to brightness graphicsScene.cubicItem = graphicsScene.cubicRGB graphicsScene.cubicItem.setVisible(True) # buttons pushButton1 = QbLUePushButton("Reset Current") pushButton1.clicked.connect(self.resetCurve) pushButton2 = QbLUePushButton("Reset R,G,B") pushButton2.clicked.connect(self.resetAllCurves) # options options1 = ['RGB', 'Red', 'Green', 'Blue'] self.listWidget1 = optionsWidget(options=options1) """ self.listWidget1.setGeometry(0, 0, self.listWidget1.sizeHintForColumn(0) + 5, self.listWidget1.sizeHintForRow(0)*len(options1) + 5) """ options2 = ['Luminosity'] self.listWidget2 = optionsWidget(options=options2, exclusive=False) self.graphicsScene.options = UDict( (self.listWidget1.options, self.listWidget2.options)) # selection changed handler curves = [ graphicsScene.cubicRGB, graphicsScene.cubicR, graphicsScene.cubicG, graphicsScene.cubicB ] curveDict = dict(zip(options1, curves)) def onSelect1(item): self.scene().cubicItem.setVisible(False) self.scene().cubicItem = curveDict[item.text()] pushButton2.setEnabled(item.text() != 'RGB') self.scene().cubicItem.setVisible(True) l = self.scene().layer l.applyToStack() l.parentImage.onImageChanged() # Force redraw histogram self.scene().invalidate( QRectF(0.0, -self.scene().axeSize, self.scene().axeSize, self.scene().axeSize), QGraphicsScene.BackgroundLayer) self.listWidget1.onSelect = onSelect1 def onSelect2(item): itemRGB = self.listWidget1.items[options1[0]] if item.isChecked(): if itemRGB.isChecked(): self.listWidget1.unCheckAll() self.listWidget1.checkOption(self.listWidget1.intNames[1]) itemRGB.setFlags(itemRGB.flags() & ~Qt.ItemIsEnabled) else: itemRGB.setFlags(itemRGB.flags() | Qt.ItemIsEnabled) l = self.scene().layer l.applyToStack() l.parentImage.onImageChanged() self.listWidget2.onSelect = onSelect2 # set initial selection to RGB item = self.listWidget1.items[options1[0]] item.setCheckState(Qt.Checked) self.listWidget1.select(item) # layout gl = QGridLayout() container = self.addCommandLayout(gl) gl.addWidget(self.listWidget1, 0, 0, 2, 1) gl.addWidget(self.listWidget2, 0, 1) for i, button in enumerate([pushButton1, pushButton2]): gl.addWidget(button, i, 2) self.adjustSize() self.setViewportMargins(0, 0, 0, container.height() + 15) self.setWhatsThis("""<b>RGB curves</b><br>""" + self.whatsThis()) def f(): l = self.scene().layer l.applyToStack() l.parentImage.onImageChanged() self.scene().cubicRGB.curveChanged.sig.connect(f) self.scene().cubicR.curveChanged.sig.connect(f) self.scene().cubicG.curveChanged.sig.connect(f) self.scene().cubicB.curveChanged.sig.connect(f)
def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None): super().__init__(layer=layer, targetImage=targetImage, parent=parent) # positioning window self.widgetImg = QLabel(parent=parent) # stay on top and center self.widgetImg.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.Dialog) # source pixmap self.sourcePixmap = None self.sourcePixmapThumb = None # flag indicating where source pixmap come from self.sourceFromFile = False # opencv flags cv2Flag_dict = { 'Normal Clone': cv2.NORMAL_CLONE, 'Mixed Clone': cv2.MIXED_CLONE, 'Monochrome Transfer': cv2.MONOCHROME_TRANSFER } cv2Flags = list(cv2Flag_dict.keys()) self.layer.cloningMethod = cv2Flag_dict['Normal Clone'] self.layer.maskIsEnabled = True self.layer.maskIsSelected = True # mask all pixels, use a semi transparent mask self.layer.resetMask(maskAll=True, alpha=128) self.layer.autoclone = True self.listWidget1 = optionsWidget(options=cv2Flags, exclusive=True, changed=self.dataChanged) # init flags for i in range(self.listWidget1.count()): item = self.listWidget1.item(i) item.setData(Qt.UserRole, cv2Flag_dict[item.text()]) self.options = self.listWidget1.options self.listWidget1.checkOption(self.listWidget1.intNames[0]) optionList2, optionListNames2 = ['opencv', 'blue' ], ['OpenCV Cloning', 'bLUe Cloning'] self.listWidget2 = optionsWidget(options=optionList2, optionNames=optionListNames2, exclusive=True, changed=self.dataChanged) self.listWidget2.checkOption(self.listWidget2.intNames[1]) self.options = UDict((self.options, self.listWidget2.options)) pushButton1 = QPushButton('Load Image From File') # Load Image button clicked slot def f(): from bLUeTop.QtGui1 import window lastDir = str(window.settings.value('paths/dlgdir', '.')) dlg = QFileDialog(window, "select", lastDir, " *".join(IMAGE_FILE_EXTENSIONS)) if dlg.exec_(): filenames = dlg.selectedFiles() newDir = dlg.directory().absolutePath() window.settings.setValue('paths/dlgdir', newDir) img = QImage(filenames[0]) # scale img while keeping its aspect ratio # into a QPixmap having the same size as self layer sourcePixmap = QPixmap.fromImage(img).scaled( self.layer.size(), Qt.KeepAspectRatio) self.sourcePixmap = QPixmap(self.layer.size()) self.sourcePixmap.fill(Qt.black) qp = QPainter(self.sourcePixmap) qp.drawPixmap( QRect(0, 0, sourcePixmap.width(), sourcePixmap.height()), sourcePixmap) qp.end() self.sourcePixmapThumb = self.sourcePixmap.scaled( self.pwSize, self.pwSize, aspectMode=Qt.KeepAspectRatio) self.widgetImg.setPixmap(self.sourcePixmapThumb) self.widgetImg.setFixedSize(self.sourcePixmapThumb.size()) self.sourceFromFile = True self.widgetImg.show() pushButton1.clicked.connect(f) pushButton2 = QPushButton('Reset') # reset button clicked slot def g(): layer = self.layer # mask all pixels layer.resetMask(maskAll=True, alpha=128) layer.setMaskEnabled(color=True) # reset clone layer layer.xAltOffset, layer.yAltOffset = 0.0, 0.0 layer.AltZoom_coeff = 1.0 layer.applyCloning(seamless=False, showTranslated=True) layer.parentImage.onImageChanged() pushButton2.clicked.connect(g) # layout layout = QVBoxLayout() layout.setContentsMargins(20, 0, 20, 25) # left, top, right, bottom self.setLayout(layout) layout.addWidget(self.listWidget2) layout.addWidget(self.listWidget1) hl = QHBoxLayout() hl.addWidget(pushButton1) hl.addWidget(pushButton2) layout.addLayout(hl) self.setDefaults() self.setWhatsThis(""" <b>Cloning</b> : Seamless replacement of a region of the image by another region from the same image or by another image (e.g. to erase an object):<br> 1) <b> make sure that the cloning layer is the topmost visible layer</b><br> 2) Select the Unmask/FG tool and paint the pixels to erase (use the Mask/BG tool to adjust the mask if needed); <br> 3) Select the drag tool and while pressing <b>Ctrl-Alt</b> use the mouse to drag or zoom the image shown in the painted region;<br> Eventually use <b>Mask Erode</b> from the layer context menu to smooth mask edges.<br> """) # end of setWhatsthis
def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None): super().__init__(layer=layer, targetImage=targetImage, parent=parent) self.tempCorrection = 6500 self.tintCorrection = 1.0 self.filterColor = QColor(255, 255, 255) self.defaultTemp = sRGBWP # ref temperature D65 self.defaultTint = 0 # options optionList, optionNames = [ 'Color Filter', 'Photo Filter', 'Chromatic Adaptation' ], ['Color Filter', 'Photo Filter', 'Chromatic Adaptation'] self.listWidget1 = optionsWidget( options=optionList, optionNames=optionNames, exclusive=True, changed=lambda: self.dataChanged.emit()) self.listWidget1.checkOption(self.listWidget1.intNames[0]) self.options = self.listWidget1.options # link to app color dialog self.colorChooser = self.parent().colorChooser # color viewer self.colorLabel = QLabel() self.colorLabel.setMaximumSize(50, 50) # color chooser button self.colorChooserBtn = QbLUePushButton('Select Filter Color') self.colorChooserBtn.clicked.connect(self.showColorChooser) # temp slider self.sliderTemp = QbLUeSlider(Qt.Horizontal) self.sliderTemp.setStyleSheet( QbLUeSlider.bLueSliderDefaultIColorStylesheet) self.sliderTemp.setRange( 17, 100 ) # 250) # valid range for spline approximation is 1667..25000, cf. colorConv.temperature2xyWP self.sliderTemp.setSingleStep(1) self.tempLabel = QbLUeLabel() self.tempLabel.setMaximumSize(150, 30) self.tempLabel.setText("Filter Temperature") self.tempLabel.doubleClicked.connect(lambda: self.sliderTemp.setValue( self.temp2Slider(self.defaultTemp))) self.tempValue = QLabel() font = self.tempValue.font() metrics = QFontMetrics(font) w = metrics.width("00000") h = metrics.height() self.tempValue.setMinimumSize(w, h) self.tempValue.setMaximumSize(w, h) self.tempValue.setText( str("{:d}".format(self.sliderTemp2User(self.sliderTemp.value())))) # tint slider self.sliderTint = QbLUeSlider(Qt.Horizontal) self.sliderTint.setStyleSheet( QbLUeSlider.bLueSliderDefaultMGColorStylesheet) self.sliderTint.setRange( 0, 100 ) # 250) # valid range for spline approximation is 1667..25000, cf. colorConv.temperature2xyWP self.sliderTint.setSingleStep(1) self.tintLabel = QbLUeLabel() self.tintLabel.setMaximumSize(150, 30) self.tintLabel.setText("Tint") self.tintLabel.doubleClicked.connect(lambda: self.sliderTint.setValue( self.tint2Slider(self.defaultTint))) self.tintValue = QLabel() font = self.tintValue.font() metrics = QFontMetrics(font) w = metrics.width("0000") h = metrics.height() self.tintValue.setMinimumSize(w, h) self.tintValue.setMaximumSize(w, h) self.tintValue.setText( str("{:d}".format(self.sliderTint2User(self.sliderTint.value())))) self.sliderTemp.valueChanged.connect(self.tempUpdate) self.sliderTemp.sliderReleased.connect( lambda: self.tempUpdate(self.sliderTemp.value())) self.sliderTint.valueChanged.connect(self.tintUpdate) self.sliderTint.sliderReleased.connect( lambda: self.tintUpdate(self.sliderTint.value())) # layout l = QVBoxLayout() l.setAlignment(Qt.AlignTop) l.addWidget(self.listWidget1) hl2 = QHBoxLayout() hl2.addWidget(self.colorLabel) hl2.addWidget(self.colorChooserBtn) l.addLayout(hl2) l.addWidget(self.tempLabel) hl = QHBoxLayout() hl.addWidget(self.tempValue) hl.addWidget(self.sliderTemp) l.addLayout(hl) l.addWidget(self.tintLabel) hl1 = QHBoxLayout() hl1.addWidget(self.tintValue) hl1.addWidget(self.sliderTint) l.addLayout(hl1) self.setLayout(l) self.adjustSize() self.setDefaults() self.setWhatsThis( """<b> Color Filter</b> and <b>Photo Filter</b> use the multiply blending mode to mimic a warming or cooling filter put in front of the camera lens. The luminosity of the resulting image is corrected.<br> <b>Chromatic Adaptation</b> uses multipliers in the linear sRGB color space to adjust <b>temperature</b> and <b>tint</b>. """) # end of setWhatsThis
def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None): super().__init__(layer=layer, targetImage=targetImage, parent=parent) # positioning window self.widgetImg = BWidgetImg(parent=self) self.widgetImg.setWindowTitle("Pointing") # source self.sourceImage = None # keep for eventual rotation self.sourcePixmap = None self.sourcePixmapThumb = None # flag indicating where source pixmap comes from self.layer.sourceFromFile = False # opencv flags cv2Flag_dict = { 'Normal Clone': cv2.NORMAL_CLONE, 'Mixed Clone': cv2.MIXED_CLONE, 'Monochrome Transfer': cv2.MONOCHROME_TRANSFER } cv2Flags = list(cv2Flag_dict.keys()) self.layer.autoclone = True self.listWidget1 = optionsWidget(options=cv2Flags, exclusive=True, changed=self.dataChanged) # init flags for i in range(self.listWidget1.count()): item = self.listWidget1.item(i) item.setData(Qt.UserRole, cv2Flag_dict[item.text()]) self.options = self.listWidget1.options self.listWidget1.checkOption(self.listWidget1.intNames[0]) optionList2, optionListNames2 = ['opencv', 'blue' ], ['OpenCV Cloning', 'bLUe Cloning'] self.listWidget2 = optionsWidget(options=optionList2, optionNames=optionListNames2, exclusive=True, changed=self.dataChanged) self.listWidget2.checkOption(self.listWidget2.intNames[1]) self.options = UDict((self.options, self.listWidget2.options)) pushButton1 = QPushButton('Load Image From File') # Load Image button clicked slot def f(): from bLUeTop.QtGui1 import window lastDir = str(window.settings.value('paths/dlgdir', '.')) filter = "Images ( *" + " *".join(IMAGE_FILE_EXTENSIONS) + ")" dlg = QFileDialog(window, "select", lastDir, filter) if dlg.exec_(): filenames = dlg.selectedFiles() newDir = dlg.directory().absolutePath() window.settings.setValue('paths/dlgdir', newDir) self.sourceImage = QImage(filenames[0]) self.updateSource() """ # scale img while keeping its aspect ratio # into a QPixmap having the same size than self.layer sourcePixmap = QPixmap.fromImage(self.sourceImage).scaled(self.layer.size(), Qt.KeepAspectRatio) self.sourceSize = sourcePixmap.size() self.sourcePixmap = QPixmap(self.layer.size()) self.sourcePixmap.fill(Qt.black) qp = QPainter(self.sourcePixmap) qp.drawPixmap(QPointF(), sourcePixmap) # (QRect(0, 0, sourcePixmap.width(), sourcePixmap.height()), sourcePixmap) qp.end() self.sourcePixmapThumb = self.sourcePixmap.scaled(self.pwSize, self.pwSize, aspectMode=Qt.KeepAspectRatio) self.widgetImg.setPixmap(self.sourcePixmapThumb) self.widgetImg.setFixedSize(self.sourcePixmapThumb.size()) self.layer.sourceFromFile = True """ self.widgetImg.show() pushButton1.clicked.connect(f) pushButton2 = QPushButton('Reset') # reset button clicked slot def g(): layer = self.layer # mask all pixels layer.resetMask(maskAll=True) layer.setMaskEnabled(color=False) # reset cloning layer layer.xAltOffset, layer.yAltOffset = 0.0, 0.0 layer.AltZoom_coeff = 1.0 layer.cloningState = '' layer.applyCloning(seamless=False, showTranslated=True) layer.parentImage.onImageChanged() pushButton2.clicked.connect(g) pushButton3 = QPushButton('Rotate Image') def h(): self.sourceImage = self.sourceImage.transformed( QTransform().rotate(90)) self.updateSource() pushButton3.clicked.connect(h) # layout layout = QVBoxLayout() layout.setContentsMargins(20, 0, 20, 25) # left, top, right, bottom self.setLayout(layout) layout.addWidget(self.listWidget2) layout.addWidget(self.listWidget1) hl = QHBoxLayout() hl.addWidget(pushButton1) hl.addWidget(pushButton3) hl.addWidget(pushButton2) layout.addLayout(hl) self.setDefaults() self.setWhatsThis(""" <b>Cloning/healing brush</b><br> Seamless replacement of a region of the image by another region of the same image or by another image (e.g. to erase an object):<br> 1) <b> Make sure that the cloning layer is the topmost visible layer</b><br> 2) With the <i>Pointer Tool</i> selected, <b>Ctrl+Alt+Click</b> on the layer or the pointing window to mark the source starting point;<br> 3) Select the <i>Unmask/FG Tool</i> and paint the destination region to copy and clone pixels. Use <i>the Mask/BG Tool</i> to adjust the mask if needed. <br> Use <b>Ctrl+Alt+Mouse Wheel</b> to zoom in or out the cloned region.<br> Eventually use <b>Mask Erode</b> from the layer context menu to smooth the contour of the mask.<br> """) # end of setWhatsthis
def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None, curveType='quadric'): super().__init__(targetImage=targetImage, axeSize=axeSize, layer=layer, parent=parent) graphicsScene = self.scene() # init the curve if curveType == 'quadric': curve = activeQuadricSpline(graphicsScene.axeSize) else: curve = activeCubicSpline(graphicsScene.axeSize) graphicsScene.addItem(curve) graphicsScene.quadricB = curve curve.channel = channelValues.Br curve.histImg = graphicsScene.layer.inputImg().histogram( size=graphicsScene.axeSize, bgColor=graphicsScene.bgColor, range=(0, 255), chans=channelValues.Br) # , mode='Luminosity') curve.initFixedPoints() # set current curve graphicsScene.cubicItem = graphicsScene.quadricB graphicsScene.cubicItem.setVisible(True) self.setWhatsThis("""<b>Contrast Curve</b><br> Drag <b>control points</b> and <b>tangents</b> with the mouse.<br> <b>Add</b> a control point by clicking on the curve.<br> <b>Remove</b> a control point by clicking it.<br> <b>Zoom</b> with the mouse wheel.<br> """) def onResetCurve(): """ Reset the selected curve """ self.scene().cubicItem.reset() l = self.scene().layer l.applyToStack() l.parentImage.onImageChanged() # buttons pushButton1 = QbLUePushButton("Reset to Auto Curve") pushButton1.setGeometry(10, 20, 100, 30) # x,y,w,h pushButton1.clicked.connect(onResetCurve) if curveType == 'quadric': optionList1, optionNames1 = ['Show Tangents'], ['Show Tangents'] self.listWidget1 = optionsWidget(options=optionList1, optionNames=optionNames1, exclusive=False) def f(): curve.setTangentsVisible( self.listWidget1.options['Show Tangents']) self.listWidget1.itemClicked.connect(f) # layout gl = QHBoxLayout() container = self.addCommandLayout(gl) if curveType == 'quadric': gl.addWidget(self.listWidget1) gl.addWidget(pushButton1) self.adjustSize() self.setViewportMargins(0, 0, 0, container.height() + 15) graphicsScene.addWidget(pushButton1) self.pushButton = pushButton1
def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None): super().__init__(layer=layer, targetImage=targetImage, parent=parent) self.defaultFilterStart = 0 self.defaultFilterEnd = 99 self.filterStart = self.defaultFilterStart self.filterEnd = self.defaultFilterEnd self.kernelCategory = blendFilterIndex.GRADUALNONE # TODO kernelCategory should be renamed as filterIndex 5/12/18 # options optionList, optionNames = ['Gradual Top', 'Gradual Bottom' ], ['Top To Bottom', 'Bottom To Top'] filters = [blendFilterIndex.GRADUALTB, blendFilterIndex.GRADUALBT ] # , blendFilterIndex.GRADUALNONE] self.filterDict = dict(zip(optionList, filters)) self.listWidget1 = optionsWidget(options=optionList, optionNames=optionNames, exclusive=True, changed=self.dataChanged) # set initial selection to gradual top self.listWidget1.checkOption(optionList[0]) rs = QRangeSlider() rs.setMaximumSize(16000, 10) rs.tail.setStyleSheet( 'background: white; /*qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #222, stop:1 #888); margin 3px;*/' ) rs.handle.setStyleSheet( 'background: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 black, stop:1 white);' ) rs.head.setStyleSheet( 'background: black; /*qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #999, stop:1 #222);*/' ) self.sliderFilterRange = rs frLabel = QLabel('Range') # filter range done event handler def frUpdate(start, end): if self.sliderFilterRange.isSliderDown() or ( start == self.filterStart and end == self.filterEnd): return try: self.sliderFilterRange.startValueChanged.disconnect() self.sliderFilterRange.endValueChanged.disconnect() self.sliderFilterRange.rangeDone.disconnect() except RuntimeError: pass self.filterStart, self.filterEnd = self.sliderFilterRange.getRange( ) self.dataChanged.emit() self.sliderFilterRange.startValueChanged.connect( frUpdate) # send new value as parameter self.sliderFilterRange.endValueChanged.connect( frUpdate) # send new value as parameter self.sliderFilterRange.rangeDone.connect(frUpdate) self.sliderFilterRange.startValueChanged.connect( frUpdate) # send new value as parameter self.sliderFilterRange.endValueChanged.connect( frUpdate) # send new value as parameter self.sliderFilterRange.rangeDone.connect(frUpdate) # layout l = QVBoxLayout() l.addWidget(self.listWidget1) hl8 = QHBoxLayout() hl8.addWidget(frLabel) hl8.addWidget(self.sliderFilterRange) l.addLayout(hl8) l.setContentsMargins(20, 0, 20, 25) # left, top, right, bottom self.setLayout(l) self.setWhatsThis("""<b>Gradual neutral filter.</b><br> It mimics the classical gradual gray filter often used by photographers to darken the sky.<br> To control the regions of maximum and minimum intensities use the Range slider. """) # end setWhatsThis self.setDefaults()
def __init__(self, targetImage=None, size=200, layer=None, parent=None): super().__init__(layer=layer, targetImage=targetImage, parent=parent) self.mode = 'Luminosity' self.chanColors = [Qt.gray] self.setWindowTitle('Histogram') self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self.setMinimumSize(size, 100) self.Label_Hist = QLabel() self.Label_Hist.setScaledContents(True) self.Label_Hist.setFocusPolicy(Qt.ClickFocus) self.setStyleSheet("QListWidget{border: 0px; font-size: 12px}") # options options1, optionNames1 = ['Original Image'], ['Source'] self.listWidget1 = optionsWidget(options=options1, optionNames=optionNames1, exclusive=False) self.listWidget1.setFixedSize( (self.listWidget1.sizeHintForColumn(0) + 15) * len(options1), 20) options2, optionNames2 = ['R', 'G', 'B', 'L'], ['R', 'G', 'B', 'L'] self.listWidget2 = optionsWidget(options=options2, optionNames=optionNames2, exclusive=False, flow=optionsWidget.LeftToRight) #self.listWidget2.setFixedSize((self.listWidget2.sizeHintForRow(0) + 15) * len(options2), 20) self.listWidget2.setFixedSize( (self.listWidget2.sizeHintForRow(0) + 20) * len(options2), 20) # + 20 needed to prevent scroll bar on ubuntu # default: show color hists only for i in range(3): self.listWidget2.checkOption(self.listWidget2.intNames[i]) self.options = UDict( (self.listWidget1.options, self.listWidget2.options)) self.setWhatsThis(""" <b>Histogram</b><br> The histogram shows the initial or final color ditribution of the image, depending on whether the <I>Source</I> option is checked or not. """) def onSelect(item): try: self.targetImage.onImageChanged() self.Label_Hist.update() except AttributeError: return self.listWidget1.onSelect = onSelect self.listWidget2.onSelect = onSelect # layout h = QHBoxLayout() h.setContentsMargins(0, 0, 0, 2) h.addStretch(1) h.addWidget(self.listWidget1) h.addStretch(1) h.addWidget(self.listWidget2) h.addStretch(1) vl = QVBoxLayout() #vl.setAlignment(Qt.AlignTop) prevent the histogram from stretching vertically vl.addWidget(self.Label_Hist) vl.addLayout(h) vl.setContentsMargins(0, 0, 0, 2) # left, top, right, bottom self.setLayout(vl) self.adjustSize()
def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None): super().__init__(targetImage=targetImage, axeSize=axeSize, layer=layer, parent=parent) graphicsScene = self.scene() ######### # L curve ######### cubic = activeCubicSpline(axeSize) graphicsScene.addItem(cubic) graphicsScene.cubicR = cubic cubic.channel = channelValues.L # get histogram as a Qimage cubic.histImg = graphicsScene.layer.inputImg().histogram(size=graphicsScene.axeSize, bgColor=graphicsScene.bgColor, range=(0, 1), chans=channelValues.L, mode='Lab') # L curve use the default axes cubic.axes = graphicsScene.defaultAxes cubic.initFixedPoints() cubic.axes.setVisible(False) cubic.setVisible(False) ########## # a curve (Green--> Magenta axis) ######### cubic = activeCubicSpline(axeSize) graphicsScene.addItem(cubic) graphicsScene.cubicG = cubic cubic.channel = channelValues.a cubic.histImg = graphicsScene.layer.inputImg().histogram(size=graphicsScene.axeSize, bgColor=graphicsScene.bgColor, range=(-100, 100), chans=channelValues.a, mode='Lab') # add specific axes gradient = QRadialGradient() gradient.setCenter(QPoint(0, 1)) gradient.setRadius(axeSize*1.4) gradient.setColorAt(0.0, Qt.green) gradient.setColorAt(1.0, Qt.magenta) cubic.axes = self.drawPlotGrid(axeSize, gradient) graphicsScene.addItem(cubic.axes) cubic.initFixedPoints() cubic.axes.setVisible(False) cubic.setVisible(False) # b curve (Blue-->Yellow axis) cubic = activeCubicSpline(axeSize) graphicsScene.addItem(cubic) graphicsScene.cubicB = cubic cubic.channel = channelValues.b cubic.histImg = graphicsScene.layer.inputImg().histogram(size=graphicsScene.axeSize, bgColor=graphicsScene.bgColor, range=(-100, 100), chans=channelValues.b, mode='Lab') # add specific axes gradient.setColorAt(0.0, Qt.blue) gradient.setColorAt(1.0, Qt.yellow) cubic.axes = self.drawPlotGrid(axeSize, gradient) graphicsScene.addItem(cubic.axes) cubic.initFixedPoints() cubic.axes.setVisible(False) cubic.setVisible(False) # set current to L curve and axes graphicsScene.cubicItem = graphicsScene.cubicR graphicsScene.cubicItem.setVisible(True) graphicsScene.cubicItem.axes.setVisible(True) # buttons pushButton1 = QPushButton("Reset Current") pushButton1.adjustSize() pushButton1.clicked.connect(self.resetCurve) pushButton2 = QPushButton("Reset All") pushButton2.adjustSize() pushButton2.clicked.connect(self.resetAllCurves) # options options = ['L', 'a', 'b'] self.listWidget1 = optionsWidget(options=options, exclusive=True) self.listWidget1.setMinimumSize(self.listWidget1.sizeHintForColumn(0) + 5, self.listWidget1.sizeHintForRow(0) * len(options) + 5) # selection changed handler curves = [graphicsScene.cubicR, graphicsScene.cubicG, graphicsScene.cubicB] curveDict = dict(zip(options, curves)) def onSelect1(item): cubicItem = self.scene().cubicItem cubicItem.setVisible(False) cubicItem.axes.setVisible(False) self.scene().cubicItem = curveDict[item.text()] self.scene().cubicItem.setVisible(True) self.scene().cubicItem.axes.setVisible(True) # Force to redraw histogram self.scene().invalidate(QRectF(0.0, -self.scene().axeSize, self.scene().axeSize, self.scene().axeSize), QGraphicsScene.BackgroundLayer) self.listWidget1.onSelect = onSelect1 # set initial selection to L item = self.listWidget1.items[options[0]] item.setCheckState(Qt.Checked) self.listWidget1.select(item) self.setWhatsThis("""<b>Lab curves</b><br>""" + self.whatsThis()) def f(): l = graphicsScene.layer l.applyToStack() l.parentImage.onImageChanged() self.scene().cubicR.curveChanged.sig.connect(f) self.scene().cubicG.curveChanged.sig.connect(f) self.scene().cubicB.curveChanged.sig.connect(f) # layout gl = QGridLayout() gl.addWidget(self.listWidget1, 0, 0, 2, 1) for i, button in enumerate([pushButton1, pushButton2]): gl.addWidget(button, i, 1) self.addCommandLayout(gl)
def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None): super().__init__(layer=layer, targetImage=targetImage, parent=parent) # connect layer selectionChanged signal self.layer.selectionChanged.sig.connect(self.updateLayer) self.kernelCategory = filterIndex.UNSHARP # options self.optionList = [ 'Unsharp Mask', 'Sharpen', 'Gaussian Blur', 'Surface Blur' ] filters = [ filterIndex.UNSHARP, filterIndex.SHARPEN, filterIndex.BLUR1, filterIndex.SURFACEBLUR ] self.filterDict = dict( zip(self.optionList, filters)) # filters is not a dict: don't use UDict here self.listWidget1 = optionsWidget(options=self.optionList, exclusive=True, changed=self.dataChanged) # set initial selection to unsharp mask self.listWidget1.checkOption(self.optionList[0]) # sliders self.sliderRadius = QbLUeSlider(Qt.Horizontal) self.sliderRadius.setRange(1, 50) self.sliderRadius.setSingleStep(1) self.radiusLabel = QLabel() self.radiusLabel.setMaximumSize(150, 30) self.radiusLabel.setText("Radius") self.radiusValue = QLabel() font = self.radiusValue.font() metrics = QFontMetrics(font) w = metrics.width("1000 ") h = metrics.height() self.radiusValue.setMinimumSize(w, h) self.radiusValue.setMaximumSize(w, h) self.sliderAmount = QbLUeSlider(Qt.Horizontal) self.sliderAmount.setRange(0, 100) self.sliderAmount.setSingleStep(1) self.amountLabel = QLabel() self.amountLabel.setMaximumSize(150, 30) self.amountLabel.setText("Amount") self.amountValue = QLabel() font = self.radiusValue.font() metrics = QFontMetrics(font) w = metrics.width("1000 ") h = metrics.height() self.amountValue.setMinimumSize(w, h) self.amountValue.setMaximumSize(w, h) self.toneValue = QLabel() self.toneLabel = QLabel() self.toneLabel.setMaximumSize(150, 30) self.toneLabel.setText("Sigma") self.sliderTone = QbLUeSlider(Qt.Horizontal) self.sliderTone.setRange(0, 100) self.sliderTone.setSingleStep(1) font = self.radiusValue.font() metrics = QFontMetrics(font) w = metrics.width("1000 ") h = metrics.height() self.toneValue.setMinimumSize(w, h) self.toneValue.setMaximumSize(w, h) # value change/done slot def formUpdate(): self.radiusValue.setText(str('%d ' % self.sliderRadius.value())) self.amountValue.setText(str('%d ' % self.sliderAmount.value())) self.toneValue.setText(str('%d ' % self.sliderTone.value())) if self.sliderRadius.isSliderDown( ) or self.sliderAmount.isSliderDown( ) or self.sliderTone.isSliderDown(): return try: for slider in [ self.sliderRadius, self.sliderAmount, self.sliderTone ]: slider.valueChanged.disconnect() slider.sliderReleased.disconnect() except RuntimeError: pass self.tone = self.sliderTone.value() self.radius = self.sliderRadius.value() self.amount = self.sliderAmount.value() self.dataChanged.emit() for slider in [ self.sliderRadius, self.sliderAmount, self.sliderTone ]: slider.valueChanged.connect(formUpdate) slider.sliderReleased.connect(formUpdate) for slider in [self.sliderRadius, self.sliderAmount, self.sliderTone]: slider.valueChanged.connect(formUpdate) slider.sliderReleased.connect(formUpdate) # layout l = QVBoxLayout() l.addWidget(self.listWidget1) hl = QHBoxLayout() hl.addWidget(self.radiusLabel) hl.addWidget(self.radiusValue) hl.addWidget(self.sliderRadius) l.addLayout(hl) hl = QHBoxLayout() hl.addWidget(self.amountLabel) hl.addWidget(self.amountValue) hl.addWidget(self.sliderAmount) l.addLayout(hl) hl = QHBoxLayout() hl.addWidget(self.toneLabel) hl.addWidget(self.toneValue) hl.addWidget(self.sliderTone) l.addLayout(hl) l.setContentsMargins(20, 0, 20, 25) # left, top, right, bottom self.setLayout(l) self.setDefaults() self.setWhatsThis(""" <b>Unsharp Mask</b> and <b>Sharpen Mask</b> are used to sharpen an image. Unsharp Mask usually gives best results.<br> <b>Gaussian Blur</b> and <b>Surface Blur</b> are used to blur an image.<br> In contrast to Gaussian Blur, Surface Blur preserves edges and reduces noise, but it may be slow.<br> It is possible to <b>limit the effect of a filter to a rectangular region of the image</b> by drawing a selection rectangle on the layer with the marquee (rectangle) tool.<br> Ctrl Click <b>clears the selection</b><br> """) # end setWhatsThis
def __init__(self, targetImage=None, size=200, layer=None, parent=None): super().__init__(layer=layer, targetImage=targetImage, parent=parent) self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self.setMinimumSize(size, 100) self.Label_Hist = QLabel() self.Label_Hist.setFocusPolicy(Qt.ClickFocus) self.Label_Hist.setMaximumSize(140000, 140000) self.Label_Hist.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self.setStyleSheet("QListWidget{border: 0px; font-size: 12px}") # options options1, optionNames1 = ['Original Image'], ['Source Image'] self.listWidget1 = optionsWidget(options=options1, optionNames=optionNames1, exclusive=False) self.listWidget1.setMaximumSize(self.listWidget1.sizeHintForColumn(0) + 5, self.listWidget1.sizeHintForRow(0) * len(options1)) options2 = ['Color Chans'] self.listWidget2 = optionsWidget(options=options2, exclusive=False) self.listWidget2.setMaximumSize(self.listWidget2.sizeHintForColumn(0) + 5, self.listWidget2.sizeHintForRow(0) * len(options2)) # default: show color hists self.listWidget2.item(0).setCheckState(Qt.Checked) self.options = {option: True for option in options1 + options2} self.setWhatsThis(""" <b>Image Histogram</b><br> The histogram shows the color ditributions for the final image unless the <I>Source Image</I> option is checked. """) def onSelect1(item): self.options[options1[0]] = item.checkState() is Qt.Checked try: self.targetImage.onImageChanged() self.Label_Hist.update() except AttributeError: return def onSelect2(item): self.options[options2[0]] = item.checkState() is Qt.Checked try: self.targetImage.onImageChanged() self.Label_Hist.update() except AttributeError: return self.listWidget1.onSelect = onSelect1 self.listWidget2.onSelect = onSelect2 # layout h = QHBoxLayout() h.setContentsMargins(0, 0, 0, 2) h.addWidget(self.listWidget1) h.addWidget(self.listWidget2) vl = QVBoxLayout() vl.setAlignment(Qt.AlignTop) vl.addWidget(self.Label_Hist) vl.addLayout(h) vl.setContentsMargins(0, 0, 0, 2) # left, top, right, bottom self.setLayout(vl) self.adjustSize()
def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None): super().__init__(layer=layer, targetImage=targetImage, parent=parent) self.setWindowTitle('grabcut') button0 = QPushButton('Segment') # button slot def f(): self.layer.noSegment = False self.layer.applyToStack() window.label.img.onImageChanged() # manual segmentation only self.layer.noSegment = True button0.clicked.connect(f) button1 = QPushButton('Reset') button1.clicked.connect(self.reset) self.spBox = QSpinBox() self.spBox.setRange(1, 10) # spBox slot def f2(iterCount): self.nbIter = self.spBox.value() self.dataChanged.emit() self.spBox.valueChanged.connect(f2) spBoxLabel = QLabel() spBoxLabel.setText('Iterations') self.spBox1 = QSpinBox() self.spBox1.setRange(0, 20) spBox1Label = QLabel() spBox1Label.setText('Contour Redo Radius') # spBox1 slot def f1(margin): self.contourMargin = self.spBox1.value() self.dataChanged.emit() self.spBox1.valueChanged.connect(f1) # options optionList1, optionNames1 = ['Clipping Layer'], ['Clipping Layer'] self.listWidget1 = optionsWidget(options=optionList1, optionNames=optionNames1, exclusive=False, changed=lambda: self.dataChanged.emit()) self.options = self.listWidget1.options # option changed slot def g(item): self.layer.isClipping = self.options['Clipping Layer'] self.layer.applyToStack() self.layer.parentImage.onImageChanged() self.listWidget1.onSelect = g # layout hLay = QHBoxLayout() hLay.addWidget(spBoxLabel) hLay.addWidget(self.spBox) hLay.addStretch(1) hLay1 = QHBoxLayout() hLay1.addWidget(spBox1Label) hLay1.addWidget(self.spBox1) hLay1.addStretch(1) h2 = QHBoxLayout() h2.addWidget(self.listWidget1) vLay = QVBoxLayout() vLay.setAlignment(Qt.AlignTop) vLay.setContentsMargins(20, 8, 20, 25) # left, top, right, bottom vLay.addLayout(hLay) vLay.addLayout(hLay1) vLay.addLayout(h2) h3 = QHBoxLayout() h3.addWidget(button0) h3.addWidget(button1) vLay.addLayout(h3) self.setLayout(vLay) self.setDefaults() self.setWhatsThis( """ <b>Segmentation (Object extraction)</b><br> Select the object to extract with the rectangle Marquee Tool. Next, press the <i>Segment</i> button.<br> The background of the segmented image is transparent : to <b>mask the underlying layers</b> check the option <i>Clipping Layer.</i><br> To <b>fix the selection</b>, paint eventual misclassed pixels with the foreground (FG) or background (BG) tools.<br> To <b>redo the segmentation of a region</b> (e.g. a border area) hold down the Ctrl key while painting the area and press again <i>segment.</i><br> To <b>redo the segmentation of the whole contour</b> set <i>Contour Redo Radius</i> to a value >= 1 and press <i>Segment.</i><br> To <b>smooth the contour</b> right click the layer row in the <i>Layers</i> panel and choose <i>Smooth Mask</i> from the context menu.<br> """ ) # end setWhatsThis