Beispiel #1
0
class DictationOverlay(QWidget):
    """Frameless window showing the partial dictation results
    """

    app = property(appref.app)

    DEFAULT_FLAGS = (
        Qt.WindowStaysOnTopHint
        | Qt.FramelessWindowHint
        | Qt.WindowTransparentForInput
        | Qt.Tool
        | Qt.WindowDoesNotAcceptFocus
    )
    REPOSITION_FLAGS = Qt.WindowStaysOnTopHint | Qt.Tool

    def __init__(self, *args, **named):
        super(DictationOverlay, self).__init__(*args, **named)
        self.setWindowTitle('%s Text Preview' % (defaults.APP_NAME_SHORT))
        self.setWindowFlags(self.DEFAULT_FLAGS)
        # self.setAttribute(Qt.WA_TranslucentBackground, True)
        # self.setAttribute(Qt.WA_NoSystemBackground, True)
        self.setAttribute(Qt.WA_ShowWithoutActivating, True)
        # self.setMaximumHeight(40)
        # self.setMinimumWidth(400)
        self.setWindowOpacity(0.8)
        self.label = QPushButton(defaults.APP_NAME_HUMAN, self)
        self.label.setFlat(True)
        self.label.connect(self.label, SIGNAL('clicked()'), self.save_new_position)
        # self.setCentralWidget(self.label)
        self.timer = QTimer(self)
        self.timer.connect(self.timer, SIGNAL('timeout()'), self.on_timer_finished)

    def show_for_reposition(self):
        """Allow the user to reposition"""
        self.setWindowFlags(Qt.WindowStaysOnTopHint)
        self.set_text('Drag, then click here', 0)

    GEOMETRY_SAVE_KEY = 'overlay.geometry'

    def save_new_position(self, *args):
        """On close during reposition, save geometry and hide"""
        log.info("Saving new position: %s", self.geometry())
        self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
        self.app.settings.setValue(self.GEOMETRY_SAVE_KEY, self.saveGeometry())
        self.hide()
        self.disconnect(SIGNAL('click()'), self.save_new_position)

    def set_text(self, text, timeout=500):
        log.info("Setting text: %s", text)
        self.label.setText(text)
        self.label.adjustSize()
        self.adjustSize()
        self.show()
        if timeout:
            self.timer.start(500)

    def on_timer_finished(self, evt=None):
        """When the timer finishes without any interruption/reset, hide the window"""
        self.hide()
        self.timer.stop()
Beispiel #2
0
    def __init__(self,
                 targetImage=None,
                 axeSize=500,
                 layer=None,
                 parent=None,
                 mainForm=None,
                 curveType='quadric'):
        super().__init__(targetImage=targetImage,
                         axeSize=axeSize,
                         layer=layer,
                         parent=parent,
                         mainForm=mainForm)
        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 on it.<br>
<b>Zoom</b> with the mouse wheel.<br>
""")

        def onResetCurve():
            """
            Reset the selected curve
            """
            self.scene().cubicItem.reset()
            #self.scene().onUpdateLUT()
            l = self.scene().layer
            l.applyToStack()
            l.parentImage.onImageChanged()

        # buttons
        pushButton1 = QPushButton("Reset to Auto Curve")
        pushButton1.setGeometry(10, 20, 100, 30)  # x,y,w,h
        pushButton1.adjustSize()
        pushButton1.clicked.connect(onResetCurve)
        graphicsScene.addWidget(pushButton1)
        self.pushButton = pushButton1
Beispiel #3
0
    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, mainForm=None):
        super().__init__(targetImage=targetImage, axeSize=axeSize, layer=layer, parent=parent, mainForm=mainForm)
        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.move(100, 20)
        pushButton1.adjustSize()
        pushButton1.clicked.connect(self.resetCurve)
        graphicsScene.addWidget(pushButton1)
        pushButton2 = QPushButton("Reset All")
        pushButton2.move(100, 50)
        pushButton2.adjustSize()
        pushButton2.clicked.connect(self.resetAllCurves)
        graphicsScene.addWidget(pushButton2)
        # options
        options = ['L', 'a', 'b']
        self.listWidget1 = optionsWidget(options=options, exclusive=True)
        self.listWidget1.setGeometry(0, 10, self.listWidget1.sizeHintForColumn(0) + 5, self.listWidget1.sizeHintForRow(0) * len(options) + 5)
        graphicsScene.addWidget(self.listWidget1)

        # 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)
Beispiel #5
0
class MapWidget(QQuickWidget):
    def __init__(self, data, model):
        super(MapWidget,
              self).__init__(resizeMode=QQuickWidget.SizeRootObjectToView)

        self.data = data
        self.model = model

        self.attribute_button = QPushButton(self)
        self.attribute_button.setStyleSheet("color: black")
        self.menu = QMenu("Pick an attribute", self)

        # Create Menu Options
        self.humidity_attribute = QAction("humidity")
        self.pressure_attribute = QAction("pressure")
        self.temperature_attribute = QAction("temperature")
        self.wind_speed_attribute = QAction("wind_speed")

        # due to frequent access of dates based on index, we store this data separately
        self.uniqueDates = self.data["datetime"].apply(
            lambda x: x.split(' ')[0]).unique().tolist()
        self.aggregation = 1

        self.rootContext().setContextProperty("markermodel", model)
        self.rootContext().setContextProperty("MapWidget", self)
        qml_path = os.path.join(os.path.dirname(__file__), "map.qml")
        self.setSource(QUrl.fromLocalFile(qml_path))

        positions = self.get_positions(self.data)
        names = self.get_names(self.data)

        # get first date of dataset in yy-mm-dd
        self.currentDate = self.uniqueDates[0]

        # Set Labels
        self.date_label = QLabel(
            "selected date: " + str(self.currentDate).replace("-", "."), self)
        self.date_label.setStyleSheet("color: black")

        # Set Previous and Next Buttons
        self.previous_button = QPushButton("Previous", self)
        self.next_button = QPushButton("Next", self)

        self.previous_button.clicked.connect(
            partial(self.on_button_clicked, "previous"))
        self.next_button.clicked.connect(
            partial(self.on_button_clicked, "next"))

        values = self.get_values(self.data, "humidity")
        colors = self.get_colors(self.data, "humidity")

        for i in range(0, len(names)):
            geo_coordinates = QGeoCoordinate(positions[i][0], positions[i][1])
            name = names[i]
            value = values[i]
            color = colors[i]

            model.append_marker({
                "position": geo_coordinates,
                "color": color,
                "name": name,
                "value": value,
                "date": self.currentDate
            })

        self.create_interface()

        return

    def add_attribute_to_menu(self, attribute_action, attribute):
        attribute_action.triggered.connect(lambda: self.clicked(attribute))
        self.menu.addAction(attribute_action)

    def create_date_picker(self, index):
        tmp_time = self.uniqueDates[index]
        time_QFormat = tmp_time.split("-")

        # date is parsed and converted to int to comply with required format of QDate
        date_picker = QDateTimeEdit(
            QDate(int(time_QFormat[0]), int(time_QFormat[1]),
                  int(time_QFormat[2])), self)
        date_picker.setDisplayFormat("yyyy.MM.dd")
        date_picker.setCalendarPopup(True)
        date_picker.setCalendarWidget(QCalendarWidget())
        date_picker.resize(date_picker.width() + 20, date_picker.height())

        return date_picker

    def set_date_pickers(self, slider):
        # Set Date Picker for Start of self.slider
        date_picker_start = self.create_date_picker(0)
        date_picker_start.setToolTip(
            "Select the BEGINNING of the time period from which the data is displayed"
        )
        date_picker_start.move(
            slider.property("x") - date_picker_start.width() - 30,
            slider.property("y"))

        # Set Date Picker for End of self.slider
        date_picker_end = self.create_date_picker(-1)
        date_picker_end.setToolTip(
            "Select the END of the time period from which the data is displayed"
        )
        date_picker_end.move(
            slider.property("x") + slider.property("width") + 30,
            slider.property("y"))

        # Set Date Pickers Boundaries Based on First and Last Date in Given Data
        date_picker_start.setMinimumDate(date_picker_start.date())
        date_picker_end.setMinimumDate(date_picker_start.date())
        date_picker_start.setMaximumDate(date_picker_end.date())
        date_picker_end.setMaximumDate(date_picker_end.date())

        return date_picker_start, date_picker_end

    def create_interface(self):
        self.attribute_button.move(50, 0)

        # Create a Menu Option for Each Attribute
        self.add_attribute_to_menu(self.humidity_attribute, "humidity")
        self.add_attribute_to_menu(self.pressure_attribute, "pressure")
        self.add_attribute_to_menu(self.temperature_attribute, "temperature")
        self.add_attribute_to_menu(self.wind_speed_attribute, "wind_speed")

        self.attribute_button.setMenu(self.menu)
        self.attribute_button.resize(self.menu.width() + 50,
                                     self.attribute_button.height())

        # Get self.slider from QML File
        self.slider = self.rootObject().findChild(QObject, "slider")

        # Set Date Pickers
        self.date_picker_start, self.date_picker_end = self.set_date_pickers(
            self.slider)

        self.date_picker_start.dateChanged.connect(lambda: self.change_date(
            self.slider, self.self.date_picker_start, self.date_picker_end))
        self.date_picker_end.dateChanged.connect(lambda: self.change_date(
            self.slider, self.self.date_picker_start, self.date_picker_end))

        # Label Holding the Current Date Selected by User
        self.date_label.move(
            self.slider.property("x") + (self.slider.width() / 2) - 100,
            self.slider.property("y") + 30)
        self.date_label.adjustSize()

        # Set Buttons Position
        self.previous_button.setStyleSheet("color: black")
        self.previous_button.move(self.slider.property("x"),
                                  self.slider.property("y") + 50)
        self.previous_button.adjustSize()
        self.next_button.setStyleSheet("color: black")
        self.next_button.move(
            self.slider.property("x") + self.slider.width() - 70,
            self.slider.property("y") + 50)
        self.next_button.adjustSize()

        jump_label = QLabel("self.slider jump (in days): ", self)
        jump_label.setStyleSheet("color: black")
        jump_label.move(self.date_label.x(), self.date_label.y() + 40)
        jump_label.adjustSize()

        self.jump_value = QLineEdit(self)
        self.jump_value.move(jump_label.x() + jump_label.width(),
                             jump_label.y() - 5)
        self.jump_value.resize(35, self.jump_value.height())
        self.jump_value.editingFinished.connect(
            lambda: self.slider.setProperty("stepSize", self.jump_value.text()
                                            ))

        agg_label = QLabel(self)
        agg_label.setStyleSheet("color: black")
        agg_label.move(self.date_label.x(), self.jump_value.y() + 40)
        agg_label.setText("mean (in days): ")
        agg_label.adjustSize()

        agg_value = QLineEdit(self)
        agg_value.move(self.jump_value.x(), agg_label.y() - 5)
        agg_value.resize(35, agg_value.height())
        agg_value.editingFinished.connect(
            lambda: self.set_agg(agg_value.text()))

        # Initialize Visualization
        self.humidity_attribute.trigger()
        self.change_date(self.slider, self.date_picker_start,
                         self.date_picker_end)

    def on_button_clicked(self, action):
        jump_value = int(self.jump_value.text())
        slider_value = int(self.slider.property("value"))

        current_date = pandas.to_datetime(self.currentDate)

        start_date = self.date_picker_start.date().toPython()
        end_date = self.date_picker_end.date().toPython()

        if action == "next":
            if current_date + datetime.timedelta(days=jump_value) <= end_date:
                self.slider.setProperty("value", slider_value + jump_value)
                self.update_date(int(self.slider.property("value")))
        elif action == "previous":
            if current_date - datetime.timedelta(
                    days=jump_value) >= start_date:
                self.slider.setProperty("value", slider_value - jump_value)
                self.update_date(int(self.slider.property("value")))

    @Slot(int)
    def update_date(self, value):
        self.currentDate = self.uniqueDates[value - 1]
        self.date_label.setText("selected date: " +
                                str(self.currentDate).replace("-", "."))
        self.clicked(self.attribute_button.text())

    # TODO: visualise time series data, not just int created by aggregation
    # TODO: create setting of visualised time period for user

    # calculates the difference (in days) between start date and end date and rescales the self.slider
    def set_agg(self, value):
        self.aggregation = int(value)
        self.clicked(self.attribute_button.text())

    def change_date(self, slider, date_picker_start, date_picker_end):
        dif = self.uniqueDates\
                  .index(date_picker_end.date().toString("yyyy-MM-dd")) - self.uniqueDates.index(date_picker_start.date().toString("yyyy-MM-dd"))
        slider.setProperty("to", dif + 1)

    # when button is clicked, changes values in all model items to a different attribute
    def clicked(self, attribute):
        self.attribute_button.setText(attribute)
        values = self.get_values(self.data, attribute)

        colors = self.get_colors(self.data, attribute)

        for i in range(0, len(values)):
            self.model.setData(i, values[i], colors[i], MarkerModel.ValueRole)

    @staticmethod
    def get_positions(data):
        tmp = data.drop_duplicates('city').sort_values(by=['city'])
        positions = [[x, y] for x, y in zip(tmp['latitude'], tmp['longitude'])]

        return positions

    @staticmethod
    def get_names(data):
        tmp = data.drop_duplicates('city').sort_values(by=['city'])
        names = tmp['city'].values.tolist()

        return names

    # creates an ordered list of aggregated values of a specified attribute
    def get_values(self, data, attribute):
        data['datetime'] = pandas.to_datetime(data['datetime'])

        start_date = pandas.to_datetime(self.currentDate)
        end_date = start_date + datetime.timedelta(days=self.aggregation)

        tmp = data[data['datetime'] >= start_date]
        tmp = tmp[tmp['datetime'] <= end_date]

        values = tmp.groupby('city').apply(
            lambda x: x[attribute].mean()).values.round(2).tolist()

        return values

    @staticmethod
    def get_colors(data, attribute):
        tmp = data.groupby('city').agg({attribute: 'mean'})

        max_value = round(tmp[attribute].max())
        min_value = round(tmp[attribute].min())

        diff = max_value - min_value
        step = round(1 / 6 * diff)

        if attribute == 'pressure':
            attribute_values = {
                0: [255, 255, 255],
                1: [204, 229, 255],
                2: [102, 178, 255],
                3: [0, 128, 255],
                4: [0, 0, 255],
                5: [0, 0, 102],
                6: [0, 0, 51]
            }
        elif attribute == 'temperature':
            attribute_values = {
                0: [0, 102, 204],
                1: [102, 178, 255],
                2: [204, 229, 255],
                3: [255, 204, 204],
                4: [255, 102, 102],
                5: [204, 0, 0],
                6: [102, 0, 0]
            }

        # TODO: create more suited colors for humidity and wind speed

        elif attribute == 'humidity':
            attribute_values = {
                0: [0, 102, 204],
                1: [102, 178, 255],
                2: [204, 229, 255],
                3: [255, 204, 204],
                4: [255, 102, 102],
                5: [204, 0, 0],
                6: [102, 0, 0]
            }
        elif attribute == 'wind_speed':
            attribute_values = {
                0: [0, 102, 204],
                1: [102, 178, 255],
                2: [204, 229, 255],
                3: [255, 204, 204],
                4: [255, 102, 102],
                5: [204, 0, 0],
                6: [102, 0, 0]
            }

        values = numpy.array([
            min_value, min_value + 1 * step, min_value + 2 * step,
            min_value + 3 * step, min_value + 4 * step, min_value + 5 * step,
            max_value
        ])

        tmp['distances'] = tmp[attribute].apply(lambda x: abs(x - values))
        tmp['index'] = tmp['distances'].apply(lambda x: numpy.argmin(x))
        tmp['color'] = tmp['index'].apply(lambda x: attribute_values.get(x))

        colors = tmp['color'].tolist()
        colors_list = []

        for color_tmp in colors:
            color = QColor(color_tmp[0], color_tmp[1], color_tmp[2], 255)
            colors_list.append(color)

        # returns QJSValue
        return colors_list

    def createAction(self, attribute):
        action = QAction(attribute)
        action.triggered.connect(self.clicked(attribute))
        self.menu.addAction(action)
        return action
Beispiel #6
0
class Page(QWidget):
    call_exit = Signal(
        str
    )  # arg is function name (self.func.__name__) for each page in pages

    def __init__(
        self,
        func=None,
        func_args=[],
        func_kwargs={},
        margin=30,
        vert_spacing=10,
        current_layout="TopBottomLayout",
        description="",
        single_pass: bool = False,
        *args,
        **kwargs
    ):
        super().__init__()

        # static definitions
        self.func = func
        self.args = func_args
        self.kwargs = func_kwargs
        self.widget_list = []
        self.margin = margin
        self.vert_spacing = vert_spacing
        self.single_pass = single_pass
        self.stdout = sys.stdout
        self.stderr = sys.stderr
        self.Current_Layout = layouts.Layout_Dict["TopBottomLayout"]
        if current_layout in layouts.Layout_Dict:
            self.Current_Layout = layouts.Layout_Dict[current_layout](self)
        # Initiallize
        self.canvas = QWidget()
        self.canvas.setObjectName("canvas")

        # create a scroll bar
        self.widget_scroll = QScrollArea(self)
        self.widget_scroll.setObjectName("widget_scroll")

        # use a QFormLayout to place widgets
        self.canvas_layout = QFormLayout()
        self.canvas_layout.setObjectName("canvas_layout")

        # set canvas_layout
        self.canvas_layout.setMargin(self.margin)
        self.canvas_layout.setSpacing(self.vert_spacing)
        self.canvas_layout.setRowWrapPolicy(self.canvas_layout.WrapAllRows)

        # default description is func.__doc__
        self.description = QLabel(
            self.getDescription(description)
        )  # doesn't need to scroll
        self.description.setParent(self)
        self.description.setWordWrap(True)
        self.description.setTextFormat(Qt.MarkdownText)
        self.description.setOpenExternalLinks(True)

        # create a Run button to excute the function
        self.run_button = QPushButton("&Run", self)
        self.run_button.clicked.connect(self.Run)
        self.run_button.adjustSize()

        # allow the user to run the func multiple times to test output
        # but only the last return is delivered (as a confirmed run)
        self.run_thread = RunThread(self.func, self.args, self.kwargs)
        self.run_thread.finished.connect(self.Done)

    def getDescription(self, pre_defined_desc: str = ""):
        """
        returns the description of current page.
        """
        if pre_defined_desc.strip(" \r\n") != "":
            return pre_defined_desc
        if self.func == None:
            log.error("No backend function found")
            return ""
        docstring = self.func.__doc__.strip(" \r\n\t")
        if docstring == "":
            log.warning("Bad docstring for function {0}".format(self.func.__name__))
            return "function {0}".format(self.func.__name__)

        # suppose "------" is the splitor between function description and arguments description
        # in numpy-style docstring, "------" has "Parameter" before it.
        # match it with "[a-zA-Z_]*?[\n\r]*?".
        desc_part = re.match(
            r"([\S\s\n\r]+?)[a-z_]*?[\n\r\s]*?------", docstring, re.M | re.I
        )
        if desc_part == None:
            log.info("whole description:\n{0}".format(docstring))
            return docstring
        log.info("recognized description:\n{0}".format(desc_part.group(1)))
        return desc_part.group(1)

    ### slot functions
    def Run(self):
        """
        Run the function(self.func) in another thread(self.run_thread).
        """
        # load args
        for i in self.widget_list:
            self.kwargs.update({i.objectName(): i.getValue()})

        # redirect standard output
        self.output_dialog = output_dialog.OutputDialog(self.func.__name__, parent=self)
        sys.stdout = self.output_dialog
        sys.stderr = self.output_dialog

        # avoid multiple clicks
        self.run_button.setText("Running...")
        self.run_button.setEnabled(False)
        self.run_thread.start()
        log.info('func "{0}" started'.format(self.func.__name__))

        self.output_dialog.exec_()

    def Done(self):
        """
        Done is called when the function is finished(i.e. self.run_thread.finished is omitted)
        Restore the run button.
        """
        log.info('func "{0}" ended'.format(self.func.__name__))
        self.output_dialog.setWindowTitle(
            "Output of {0} - returns {1}".format(
                self.func.__name__, self.run_thread.ret
            )
        )
        self.output_dialog.revive()
        self.run_button.setText("Run")
        self.run_button.setEnabled(True)
        if self.single_pass:
            self.call_exit.emit(self.func.__name__)
    def __init__(self,
                 targetImage=None,
                 axeSize=500,
                 layer=None,
                 parent=None,
                 colorModel='HSV',
                 mainForm=None):
        super().__init__(targetImage=targetImage,
                         axeSize=axeSize,
                         layer=layer,
                         parent=parent,
                         mainForm=mainForm)
        graphicsScene = self.scene()
        graphicsScene.colorModel = colorModel

        # hue curve init.
        cubic = activeCubicSpline(axeSize)
        graphicsScene.addItem(cubic)
        graphicsScene.cubicR = cubic
        cubic.channel = channelValues.Hue
        """
        cubic.histImg = graphicsScene.layer.histogram(size=axeSize,
                                                       bgColor=graphicsScene.bgColor, range=(0, 360),
                                                       chans=channelValues.Hue, mode='HSpB')
        """
        cubic.initFixedPoints()

        # sat curve init.
        cubic = activeCubicSpline(axeSize)
        graphicsScene.addItem(cubic)
        graphicsScene.cubicG = cubic
        cubic.channel = channelValues.Sat
        """
        cubic.histImg = graphicsScene.layer.histogram(size=axeSize,
                                                      bgColor=graphicsScene.bgColor, range=(0,1),
                                                      chans=channelValues.Sat, mode='HSpB')
        """
        cubic.initFixedPoints()

        # brightness curve init.
        cubic = activeCubicSpline(axeSize)
        graphicsScene.addItem(cubic)
        graphicsScene.cubicB = cubic
        cubic.channel = channelValues.Br
        """
        cubic.histImg = graphicsScene.layer.histogram(size=axeSize,
                                                      bgColor=graphicsScene.bgColor,
                                                      range=(0,1), chans=channelValues.Br, mode='HSpB')
        """
        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.move(100, 20)
        pushButton1.adjustSize()
        pushButton1.clicked.connect(self.resetCurve)
        graphicsScene.addWidget(pushButton1)
        pushButton2 = QPushButton("Reset All")
        pushButton2.move(100, 50)
        pushButton2.adjustSize()
        pushButton2.clicked.connect(self.resetAllCurves)
        graphicsScene.addWidget(pushButton2)
        # options
        options = ['H', 'S', 'B']
        self.listWidget1 = optionsWidget(options=options, exclusive=True)
        self.listWidget1.setGeometry(
            0, 10,
            self.listWidget1.sizeHintForColumn(0) + 5,
            self.listWidget1.sizeHintForRow(0) * len(options) + 5)
        graphicsScene.addWidget(self.listWidget1)

        # 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)
        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__(layer=layer, targetImage=targetImage, parent=parent)
        self.options = None
        pushButton1 = QPushButton(' Undo ')
        pushButton1.adjustSize()
        pushButton2 = QPushButton(' Redo ')
        pushButton2.adjustSize()

        pushButton1.clicked.connect(self.undo)
        pushButton2.clicked.connect(self.redo)

        spacingSlider = QSlider(Qt.Horizontal)
        spacingSlider.setObjectName('spacingSlider')
        spacingSlider.setRange(1,60)
        spacingSlider.setTickPosition(QSlider.TicksBelow)
        spacingSlider.setSliderPosition(10)
        spacingSlider.sliderReleased.connect(self.parent().label.brushUpdate)
        self.spacingSlider = spacingSlider

        jitterSlider = QSlider(Qt.Horizontal)
        jitterSlider.setObjectName('jitterSlider')
        jitterSlider.setRange(0, 100)
        jitterSlider.setTickPosition(QSlider.TicksBelow)
        jitterSlider.setSliderPosition(0)
        jitterSlider.sliderReleased.connect(self.parent().label.brushUpdate)
        self.jitterSlider = jitterSlider

        orientationSlider = QSlider(Qt.Horizontal)
        orientationSlider.setObjectName('orientationSlider')
        orientationSlider.setRange(0, 360)
        orientationSlider.setTickPosition(QSlider.TicksBelow)
        orientationSlider.setSliderPosition(180)
        orientationSlider.sliderReleased.connect(self.parent().label.brushUpdate)
        self.orientationSlider = orientationSlider

        # sample
        self.sample = QLabel()
        #self.sample.setMinimumSize(200, 100)
        pxmp = QPixmap(250,100)
        pxmp.fill(QColor(255, 255, 255, 255))
        self.sample.setPixmap(pxmp)
        qpp = QPainterPath()
        qpp.moveTo(QPointF(20, 50))
        qpp.cubicTo(QPointF(80, 25), QPointF(145, 70), QPointF(230, 60))  # c1, c2, endPoint
        self.samplePoly = qpp.toFillPolygon(QTransform())
        # we want an unclosed polygon
        self.samplePoly.removeLast()

        # layout
        l = QVBoxLayout()
        l.setAlignment(Qt.AlignTop)
        hl = QHBoxLayout()
        hl.setAlignment(Qt.AlignHCenter)
        hl.addWidget(pushButton1)
        hl.addWidget(pushButton2)
        l.addLayout(hl)
        l.addWidget(QLabel('Brush Dynamics'))
        hl1 = QHBoxLayout()
        hl1.addWidget(QLabel('Spacing'))
        hl1.addWidget(spacingSlider)
        l.addLayout(hl1)
        hl2 = QHBoxLayout()
        hl2.addWidget(QLabel('Jitter'))
        hl2.addWidget(jitterSlider)
        l.addLayout(hl2)
        hl3 = QHBoxLayout()
        hl3.addWidget(QLabel('Orientation'))
        hl3.addWidget(self.orientationSlider)
        l.addLayout(hl3)
        l.addWidget(self.sample)
        self.setLayout(l)
        self.adjustSize()

        self.setDefaults()
        self.setWhatsThis(
                        """
                        <b>Drawing :</b><br>
                          Choose a brush family, flow, hardness and opacity.
                        """
                        )  # end of setWhatsThis
    def __init__(self,
                 targetImage=None,
                 axeSize=500,
                 layer=None,
                 parent=None,
                 mainForm=None):
        super(transForm, self).__init__(parent=parent)

        self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
        self.setMinimumSize(axeSize, axeSize)
        self.setAttribute(Qt.WA_DeleteOnClose)
        # back links to image
        self.targetImage = weakProxy(targetImage)
        self.img = weakProxy(targetImage)
        self.layer = weakProxy(layer)
        self.mainForm = mainForm
        # options
        optionList1, optionNames1 = ['Free', 'Rotation', 'Translation'], [
            'Free Transformation', 'Rotation', 'Translation'
        ]
        self.listWidget1 = optionsWidget(options=optionList1,
                                         optionNames=optionNames1,
                                         exclusive=True)
        optionList2, optionNames2 = ['Transparent'
                                     ], ['Set Transparent Pixels To Black']
        self.listWidget2 = optionsWidget(options=optionList2,
                                         optionNames=optionNames2,
                                         exclusive=False)
        self.options = UDict(
            (self.listWidget1.options, self.listWidget2.options))
        # set initial selection to Perspective
        self.listWidget1.checkOption(optionList1[0])

        # option changed handler
        def g(item):
            l = self.layer
            l.tool.setBaseTransform()
            #self.layer.tool.setVisible(True)
            l.applyToStack()
            l.parentImage.onImageChanged()

        self.listWidget1.onSelect = g
        self.listWidget2.onSelect = g
        pushButton1 = QPushButton(' Reset Transformation ')
        pushButton1.adjustSize()

        def f():
            self.layer.tool.resetTrans()

        pushButton1.clicked.connect(f)
        # 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.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.
""")