Beispiel #1
0
class QTiffStackView(QWidget):
    #the view which the user of the videoviewer sees.
    #This class contains relevant 'client side' attributes e.g. buttons to get a frame, a slide bar and a timer. These attributes submit requests to the QTiffStackController to give the next frame etc. The controller returns either the requested frame or an error message

    def __init__(self):
        super(QTiffStackView, self).__init__()
        #add the image display. This is a subclass of QLabel, where paintEvent is overriden.
        self.frame_view = FrameView()

        #self.frame_view.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Expanding)

        #add the slide bar which allows the user to manual flick through

        self.slideBar = QSlider(Qt.Horizontal)
        self.slideBar.setTickPosition(QSlider.TicksAbove)
        self.slideBar.setTracking(True)
        self.slideBar.setTickInterval(100)

        #add a counter which displays the frame which is currently displayed
        self.counter = QSpinBox()
        self.counter.setSingleStep(1)
        self.counter.setRange(self.slideBar.minimum(), self.slideBar.maximum())

        #self explanatory
        self.play = QPushButton('Play')

        #when play button is pressed the timer takes control of the displaying of frames
        self.frametimer = QTimer()

        frame_rate = 30
        self.frametimer.setInterval(frame_rate)

        #Add a sublayout to align the slidebar and frame counter next to eachother
        slidelyt = QHBoxLayout()
        slidelyt.addWidget(self.slideBar)
        slidelyt.addWidget(self.counter)

        #Add the main layout for the widget
        lyt = QVBoxLayout()

        lyt.addWidget(self.frame_view)
        lyt.addLayout(slidelyt)
        lyt.addWidget(self.play)

        self.setLayout(lyt)

    def updateRanges(self, maximum):

        assert type(maximum) == int

        self.slideBar.setMaximum(maximum)
        self.counter.setRange(self.slideBar.minimum(), self.slideBar.maximum())
Beispiel #2
0
class AnimateDialog(QDialog):
    def __init__(self, vpoints: Sequence[VPoint], vlinks: Sequence[VLink],
                 path: _Paths, slider_path: _SliderPaths, monochrome: bool,
                 parent: QWidget):
        super(AnimateDialog, self).__init__(parent)
        self.setWindowTitle("Vector Animation")
        self.setWindowFlags(self.windowFlags() | Qt.WindowMaximizeButtonHint
                            & ~Qt.WindowContextHelpButtonHint)
        self.setMinimumSize(800, 600)
        self.setModal(True)
        main_layout = QVBoxLayout(self)
        self.canvas = _DynamicCanvas(vpoints, vlinks, path, slider_path, self)
        self.canvas.set_monochrome_mode(monochrome)
        self.canvas.update_pos.connect(self.__set_pos)
        layout = QHBoxLayout(self)
        pt_option = QComboBox(self)
        pt_option.addItems([f"P{p}" for p in range(len(vpoints))])
        layout.addWidget(pt_option)
        value_label = QLabel(self)

        @Slot(int)
        def show_values(ind: int):
            vel, vel_deg = self.canvas.get_vel(ind)
            acc, acc_deg = self.canvas.get_acc(ind)
            value_label.setText(
                f"Velocity: {vel:.04f} ({vel_deg:.04f}deg) | "
                f"Acceleration: {acc:.04f} ({acc_deg:.04f}deg)")

        pt_option.currentIndexChanged.connect(show_values)
        layout.addWidget(value_label)
        self.pos_label = QLabel(self)
        layout.addItem(
            QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
        layout.addWidget(self.pos_label)
        main_layout.addLayout(layout)
        main_layout.addWidget(self.canvas)
        layout = QHBoxLayout(self)
        self.play = QPushButton(QIcon(QPixmap(":/icons/play.png")), "", self)
        self.play.setCheckable(True)
        self.play.clicked.connect(self.__play)
        layout.addWidget(self.play)
        self.slider = QSlider(Qt.Horizontal, self)
        self.slider.setMaximum(max(len(p) for p in path) - 1)
        self.slider.valueChanged.connect(self.canvas.set_index)
        layout.addWidget(self.slider)
        layout.addWidget(QLabel("Total times:", self))
        factor = QDoubleSpinBox(self)
        factor.valueChanged.connect(self.canvas.set_factor)
        factor.setSuffix('s')
        factor.setRange(0.01, 999999)
        factor.setValue(10)
        layout.addWidget(factor)
        main_layout.addLayout(layout)
        self.timer = QTimer()
        self.timer.setInterval(10)
        self.timer.timeout.connect(self.__move_ind)

    @Slot()
    def __move_ind(self):
        """Move indicator."""
        value = self.slider.value() + 1
        self.slider.setValue(value)
        if value > self.slider.maximum():
            self.slider.setValue(0)

    @Slot(float, float)
    def __set_pos(self, x: float, y: float) -> None:
        """Set mouse position."""
        self.pos_label.setText(f"({x:.04f}, {y:.04f})")

    @Slot()
    def __play(self):
        """Start playing."""
        if self.play.isChecked():
            self.timer.start()
        else:
            self.timer.stop()
class  livestream(QWidget):
    i = 0
    def __init__(self,qnd,images = None,annotations_on = True,annotate_coords = None,threshold_switch = False):
        QWidget.__init__(self)
        
        
        self.threshold_switch = threshold_switch
        self.video = images #frames buffer
        self.videobox = Label()
        if annotations_on and annotate_coords is not None:
            self.coords = annotate_coords

            self.videobox.switch = annotations_on
            self.videobox.activecoord = self.coords[0]

        
        
        if self.video is not None:
            self.videobox.activeframe = self.video[0]
            
            self.videobox.maxintens = self.video.shape[0]
            
        else:
            self.videobox.activeframe = np.loadtxt(os.getcwd() + '/defaultimage.txt')
            print(self.videobox.activeframe.shape)
            self.videobox.maxintens = np.max(self.videobox.activeframe)


        self.videobox.setGeometry(QtCore.QRect(70, 80, 310, 310))
        self.videobox.h = 310
        self.videobox.w = 310
        
        self.lyt = QVBoxLayout()
        self.lyt.addWidget(self.videobox,5)
        
        
        self.setLayout(self.lyt)
        
        
        
        self.sl = QSlider(Qt.Horizontal)
        
        self.sl.setMinimum(0.0)
        if self.video is not None:
            self.sl.setMaximum(self.video.shape[0])
            self.sl.valueChanged.connect(self.whenslidechanges)
        self.sl.setTickPosition(QSlider.TicksAbove)
        self.sl.setTracking(True)
        self.sl.setTickInterval(100)
        

        
        self.frame_counter = QDoubleSpinBox()
        if images is not None:
            self.frame = images[0]
            self.frame_counter.valueChanged.connect(self.video_time_update)
        self.frame_counter.setSingleStep(1)
        self.frame_counter.setRange(self.sl.minimum(),self.sl.maximum())
        self.frame_counter.valueChanged.connect(self.sl.setValue)

        
        self.video_time = QDoubleSpinBox()
        self.video_time.setSingleStep(30)
        self.video_time.setRange(self.sl.minimum(),30*self.sl.maximum())
        self.frameratetimer = QTimer()
        self.frameratetimer.setInterval(30)
        if self.video is not None:
            self.frameratetimer.timeout.connect(self.update_display)
        
        
        self.play_button = QPushButton('Play Video')
        self.play_button.clicked.connect(self.frameratetimer.start)
        
        self.stop_button = QPushButton('Stop Video')
        self.stop_button.clicked.connect(self.frameratetimer.stop)

        if self.video is not None:
            self.sl.valueChanged.connect(self.whenslidechanges)
       
        self.lyt.addWidget(self.play_button,0)
        self.lyt.addWidget(self.stop_button,1)
        self.lyt.addWidget(self.sl,2)
        self.lyt.addWidget(self.frame_counter,3)
        self.lyt.addWidget(self.video_time,4)
        
        self.show()
    
    def assign_images(self,images,centres = None):
    
        '''#first disconnect signals from eachother so nothing should change whilst video data is being updated
        self.sl.valueChanged.disconnect(self.video_time_update)
        self.frameratetimer.timeout.disconnect(self.update_display)
        self.frame_counter.valueChanged.disconnect(self.whenslidechanges)
        '''
        
        self.video = images
        self.coords = centres
        self.videobox.activeframe = self.video[0]
        if self.coords is not None:
            self.videobox.activecoord = self.coords[0]

        #readjust slider and ticker values to dimensions of video
        
        self.sl.setMaximum(len(self.video)-1)
        self.frame_counter.setRange(self.sl.minimum(),self.sl.maximum())
        self.video_time.setRange(self.sl.minimum(),30*self.sl.maximum())
        
        
        
        
        #connect slider and timer etc.
    
        self.sl.valueChanged.connect(self.whenslidechanges)
        self.frameratetimer.timeout.connect(self.update_display)
        self.frame_counter.valueChanged.connect(self.video_time_update)
        
        self.videobox.maxintens = np.max(self.video)
        self.videobox.update()
        
        
    def update_display(self):
        
        if self.threshold_switch:
            frame = self.video[livestream.i]
            threshold = threshold_otsu(frame)
            
            mask = np.zeros_like(frame)
            mask[frame > threshold] = 1
            self.videobox.maxintens = 1
            self.videobox.activeframe = mask
        else:
            #if threshold switch is off display usual video, so change active frame source and reset maximum intensity for passing to qimage2ndarray
            self.videobox.activeframe = self.video[livestream.i]
            self.videobox.maxintens = np.max(self.video)
            
        try:
            self.videobox.activecoord = self.coords[livestream.i]

            if not self.videobox.switch:
                
            
                self.videobox.switch = True
                
        except:
            self.videobox.activecoord = None
            self.videobox.switch = False
            
            
        self.videobox.update()
        self.frame_counter.setValue(float(livestream.i))
        
        livestream.i+=1
       
    def whenslidechanges(self):
        
        if self.frameratetimer.isActive():
            self.frameratetimer.stop()
            
            livestream.i = self.sl.value()
        
            self.update_display()
            livestream.i -=1
            
            self.frameratetimer.start()
        else:
            
            livestream.i = self.sl.value()
        
            self.update_display()
            livestream.i -=1
    
    def video_time_update(self):
        self.video_time.setValue(30*self.frame_counter.value())
        
        
    def turn_on_threshold(self,threshold_switch):
        self.threshold_switch = threshold_switch
        self.update_display()
Beispiel #4
0
class VideoPlayerWidget(QWidget):
    def __init__(self, **kwargs):
        super(VideoPlayerWidget, self).__init__(**kwargs)

        self._video = None
        self._playing = False
        self._moving_seekbar = False

        self.view = GraphicsView()

        self.play_button = QPushButton()
        self.play_button.setIcon(get_standard_icon(QStyle.SP_MediaPlay))
        self.play_button.clicked.connect(self.on_play_button_clicked)

        self.seekbar = QSlider(Qt.Horizontal)
        self.seekbar.setMinimum(0)
        self.seekbar.setMaximum(0)
        self.seekbar.sliderPressed.connect(self.on_seekbar_sliderPressed)
        self.seekbar.sliderMoved.connect(self.on_seekbar_sliderMoved)
        self.seekbar.sliderReleased.connect(self.on_seekbar_sliderReleased)
        self.seekbar.valueChanged.connect(self.on_seekbar_valueChanged)

        self.nframes_label = QLabel('----- / -----')

        self.update_timer = QTimer(self)
        self.update_timer.timeout.connect(self.fetch_frame)

        layout = vbox([
            self.view,
            hbox([self.play_button, self.seekbar, self.nframes_label])
        ])
        self.setLayout(layout)

    def open_video(self, src_path):
        self._video = cv2.VideoCapture(src_path)

        if self.video_is_opened():
            ret, frame = self._video.read()
            self.view.update_image(frame)
            self.seekbar.setMaximum(self._video.get(cv2.CAP_PROP_FRAME_COUNT))
            return True
        else:
            return False

    def video_is_opened(self):
        return self._video is not None and self._video.isOpened()

    def start_video(self):
        self._playing = True
        self.play_button.setIcon(get_standard_icon(QStyle.SP_MediaPause))

        fps = self._video.get(cv2.CAP_PROP_FPS)
        self.update_timer.start(1000. / fps)

    def stop_video(self):
        self._playing = False
        self.play_button.setIcon(get_standard_icon(QStyle.SP_MediaPlay))
        self.update_timer.stop()

    def fetch_frame(self):

        if self._moving_seekbar:
            return

        ret, frame = self._video.read()

        if not ret:
            self.stop_video()
            return

        self.view.update_image(frame)
        pos = self._video.get(cv2.CAP_PROP_POS_FRAMES)
        self.seekbar.setValue(pos)

    def on_play_button_clicked(self):

        if not self.video_is_opened():
            return

        if self._playing:
            self.stop_video()
        else:
            self.start_video()

    def on_seekbar_sliderPressed(self):

        if not self.video_is_opened():
            return

        self._moving_seekbar = True

    def on_seekbar_sliderMoved(self):
        pass

    def on_seekbar_sliderReleased(self):

        if not self.video_is_opened():
            return

        self._moving_seekbar = False
        pos = self.seekbar.value()
        self._video.set(cv2.CAP_PROP_POS_FRAMES, pos)
        self.fetch_frame()

    def on_seekbar_valueChanged(self):
        curr_pos = self.seekbar.value()
        nframes = self.seekbar.maximum()
        self.nframes_label.setText(f'{curr_pos:05d} / {nframes:05d}')
Beispiel #5
0
class TrapViewer(QWidget):
    i = 0

    def __init__(self, qnd, images, trap_positions=None, labels=None):
        QWidget.__init__(self)

        self.video = images  # This is a file object buffer containing the images

        self.trap_positions = trap_positions
        self.labels = labels
        self.videobox = Label(trap_positions, labels)
        self.videobox.activeframe = images.asarray(key=TrapViewer.i)
        try:
            self.videobox.maxintens = int(images.imagej_metadata['max'])
            self.videobox.maxintens = 15265
            print(images.imagej_metadata)
        except KeyError:
            self.videobox.maxintens = int(np.max(self.videobox.activeframe))

        self.videobox.setGeometry(QtCore.QRect(70, 80, 200, 200))

        self.lyt = QVBoxLayout()
        self.lyt.addWidget(self.videobox, 5)

        self.setLayout(self.lyt)

        self.sl = QSlider(Qt.Horizontal)

        self.sl.setMinimum(0.0)
        self.sl.setMaximum(self.video.imagej_metadata['frames'] - 1)

        self.sl.setTickPosition(QSlider.TicksAbove)
        self.sl.setTracking(True)
        self.sl.setTickInterval(100)

        self.sl.valueChanged.connect(self.whenslidechanges)

        self.frame_counter = QDoubleSpinBox()
        self.frame = self.videobox.activeframe
        self.frame_counter.setSingleStep(1)
        self.frame_counter.setRange(self.sl.minimum(), self.sl.maximum() - 1)
        self.frame_counter.valueChanged.connect(self.sl.setValue)
        self.frame_counter.valueChanged.connect(self.video_time_update)

        self.video_time = QDoubleSpinBox()
        self.video_time.setSingleStep(30)
        self.video_time.setRange(self.sl.minimum(), 30 * self.sl.maximum() - 1)
        self.frameratetimer = QTimer()
        self.frameratetimer.setInterval(50)
        self.frameratetimer.timeout.connect(self.update_display)

        self.play_button = QPushButton('Play Video')
        self.play_button.clicked.connect(self.frameratetimer.start)

        self.stop_button = QPushButton('Stop Video')
        self.stop_button.clicked.connect(self.frameratetimer.stop)
        self.sl.valueChanged.connect(self.whenslidechanges)

        self.lyt.addWidget(self.play_button, 0)
        self.lyt.addWidget(self.stop_button, 1)
        self.lyt.addWidget(self.sl, 2)
        self.lyt.addWidget(self.frame_counter, 3)
        self.lyt.addWidget(self.video_time, 4)

        self.show()

    def update_display(self):

        self.frame = self.video.asarray(key=TrapViewer.i)
        self.videobox.activeframe = self.frame

        self.videobox.update()

        self.frame_counter.setValue(float(TrapViewer.i))

        if TrapViewer.i < self.video.imagej_metadata['frames']:

            TrapViewer.i += 1

    def whenslidechanges(self):

        if self.frameratetimer.isActive():
            self.frameratetimer.stop()

            TrapViewer.i = self.sl.value()

            self.update_display()
            TrapViewer.i -= 1

            self.frameratetimer.start()
        else:

            TrapViewer.i = self.sl.value()

            self.update_display()
            TrapViewer.i -= 1

    def video_time_update(self):
        self.video_time.setValue(30 * self.frame_counter.value())
Beispiel #6
0
class RangeManager(QWidget):
    """Width of the widgets can be set using `setMaximumWidth`. The size policy is set
    so that the widget may shrink if there is not enough space."""

    selection_changed = Signal(float, float, str)

    def __init__(self,
                 *,
                 name="",
                 add_sliders=False,
                 slider_steps=10000,
                 selection_to_range_min=0.001):
        """
        Class constructor for RangeManager

        Parameters
        ----------
        add_sliders: bool
            True - the widget will include sliders for controlling the range,
            False - the widget will have no sliders without sliders
        slider_steps: int
            The number of slider steps. Determines the precision of the slider.
            Default value is sufficient in most cases
        selection_to_range_min: float
            Minimum ratio of the selected range and total range. Must be floating
            point number >=0. Used only when the value type is set to "float":
            `self.set_value_type("float")`. Minimum selected range is always 1
            when "int" value type is set.
        """
        super().__init__()

        self._name = name

        # Set the maximum number of steps for the sliders (resolution)
        self.sld_n_steps = slider_steps
        # Ratio of the minimum range and total range. It is used to compute
        #   the value of 'self._range_min_diff'. The widget will prevent
        #   range to be set to smaller value than 'self._range_min_diff'.
        self._selection_to_range_min = selection_to_range_min

        self._range_low = 0.0
        self._range_high = 100.0
        self._range_min_diff = (self._range_high -
                                self._range_low) * self._selection_to_range_min
        self._value_per_step = (self._range_high -
                                self._range_low) / self.sld_n_steps
        self._value_type = "float"

        # The following values are used to keep the low and high of the range.
        #   Those values are 'accepted' values that reflect current selected range.
        self._value_low = self._range_low
        self._value_high = self._range_high

        max_element_width = 200

        self.le_min_value = LineEditExtended()
        self.le_max_value = LineEditExtended()
        self.validator_low = DoubleValidatorRelaxed()
        self.validator_high = DoubleValidatorRelaxed()

        self.le_min_value.setMaximumWidth(max_element_width)
        self.le_min_value.textEdited.connect(self.le_min_value_text_edited)
        self.le_min_value.textChanged.connect(self.le_min_value_text_changed)
        self.le_min_value.editingFinished.connect(
            self.le_min_value_editing_finished)
        self.le_max_value.setMaximumWidth(max_element_width)
        self.le_max_value.textEdited.connect(self.le_max_value_text_edited)
        self.le_max_value.textChanged.connect(self.le_max_value_text_changed)
        self.le_max_value.editingFinished.connect(
            self.le_max_value_editing_finished)

        self.le_min_value.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        self.le_max_value.setAlignment(Qt.AlignRight | Qt.AlignVCenter)

        # The flag is set true if mouse is pressed on one of the sliders
        #   Both sliders can not be pressed at once, so one variable is sufficient
        self._sld_mouse_pressed = False

        self.sld_min_value = QSlider(Qt.Horizontal)
        self.sld_min_value.valueChanged.connect(
            self.sld_min_value_value_changed)
        self.sld_min_value.sliderPressed.connect(
            self.sld_min_value_slider_pressed)
        self.sld_min_value.sliderReleased.connect(
            self.sld_min_value_slider_released)
        self.sld_max_value = QSlider(Qt.Horizontal)
        self.sld_max_value.valueChanged.connect(
            self.sld_max_value_value_changed)
        self.sld_max_value.sliderPressed.connect(
            self.sld_max_value_slider_pressed)
        self.sld_max_value.sliderReleased.connect(
            self.sld_max_value_slider_released)

        self.sld_min_value.setMaximumWidth(max_element_width)
        self.sld_max_value.setMaximumWidth(max_element_width)

        # The slider for controlling minimum is inverted
        self.sld_min_value.setInvertedAppearance(True)
        self.sld_min_value.setInvertedControls(True)

        self.sld_min_value.setMaximum(self.sld_n_steps)
        self.sld_max_value.setMaximum(self.sld_n_steps)

        self.sld_min_value.setValue(self.sld_min_value.maximum())
        self.sld_max_value.setValue(self.sld_max_value.maximum())

        self.set_value_type(self._value_type)  # Set the validator

        grid = QGridLayout()
        grid.setHorizontalSpacing(0)
        grid.setVerticalSpacing(0)
        grid.setContentsMargins(0, 0, 0, 0)
        grid.addWidget(self.le_min_value, 0, 0)
        grid.addWidget(QLabel(".."), 0, 1)
        grid.addWidget(self.le_max_value, 0, 2)

        if add_sliders:
            grid.addWidget(self.sld_min_value, 1, 0)
            grid.addWidget(QLabel(""), 1, 1)
            grid.addWidget(self.sld_max_value, 1, 2)

        self.setLayout(grid)

        sp = QSizePolicy()
        sp.setControlType(QSizePolicy.PushButton)
        sp.setHorizontalPolicy(QSizePolicy.Maximum)
        self.setSizePolicy(sp)

    def le_min_value_text_edited(self, text):
        if self._min_value_validate(text):
            v = float(text)  # Works even if the value is expected to be 'int'
            n_steps = self._value_to_slider(v)
            self.sld_min_value.setValue(self.sld_n_steps - n_steps)

    def le_min_value_text_changed(self, text):
        self._min_value_validate(text)

    def le_min_value_editing_finished(self):
        # We don't set validator, so this method is called each time QLineEdit
        #   is losing focus or Enter is pressed
        val = self.le_min_value.text() if self._min_value_validate(
        ) else self._value_low
        if self._accept_value_low(val):
            self.emit_selection_changed()

    def le_max_value_text_edited(self, text):
        if self._max_value_validate(text):
            v = float(text)  # Works even if the value is expected to be 'int'
            n_steps = self._value_to_slider(v)
            self.sld_max_value.setValue(n_steps)

    def le_max_value_text_changed(self, text):
        self._max_value_validate(text)

    def le_max_value_editing_finished(self):
        # We don't set validator, so this method is called each time QLineEdit
        #   is losing focus or Enter is pressed
        val = self.le_max_value.text() if self._max_value_validate(
        ) else self._value_high
        if self._accept_value_high(val):
            self.emit_selection_changed()

    def sld_min_value_value_changed(self, n_steps):
        # Invert the reading for 'min' slider
        if self._sld_mouse_pressed:
            n_steps = self.sld_n_steps - n_steps
            v = self._slider_to_value(n_steps)
            self.le_min_value.setText(self._format_value(v))

    def sld_min_value_slider_pressed(self):
        self._sld_mouse_pressed = True

    def sld_min_value_slider_released(self):
        self._sld_mouse_pressed = False
        n_steps = self.sld_n_steps - self.sld_min_value.value()
        v = self._slider_to_value(n_steps)
        if self._accept_value_low(v):
            self.emit_selection_changed()

    def sld_max_value_value_changed(self, n_steps):
        if self._sld_mouse_pressed:
            v = self._slider_to_value(n_steps)
            self.le_max_value.setText(self._format_value(v))

    def sld_max_value_slider_pressed(self):
        self._sld_mouse_pressed = True

    def sld_max_value_slider_released(self):
        self._sld_mouse_pressed = False
        n_steps = self.sld_max_value.value()
        v = self._slider_to_value(n_steps)
        if self._accept_value_high(v):
            self.emit_selection_changed()

    def _format_value(self, value):
        return f"{value:.8g}"

    def _round_value(self, value):
        # Compute rounded value based on formatting used in the line edit boxes
        #   This rounding is needed to properly set the validators
        s = self._format_value(value) if not isinstance(value, str) else value
        return self._convert_type(s)

    def _check_value_type(self, value_type):
        if value_type not in ("float", "int"):
            raise ValueError(
                f"RangeManager.set_value_type(): value type '{value_type}' is not supported"
            )

    def _min_value_validate(self, text=None):
        text = text if text is not None else self.le_min_value.text()
        is_valid = self.validator_low.validate(text, 0)[0] == 2
        self.le_min_value.setValid(is_valid)
        return is_valid

    def _max_value_validate(self, text=None):
        text = text if text is not None else self.le_max_value.text()
        is_valid = self.validator_high.validate(text, 0)[0] == 2
        self.le_max_value.setValid(is_valid)
        return is_valid

    def _convert_type(self, val):
        if self._value_type == "float":
            return float(val)
        else:
            # Convert to int (input may be float or text string).
            # We want to round the value to the nearest int.
            return round(float(val))

    def _slider_to_value(self, sld_n):
        v = self._range_low + (self._range_high -
                               self._range_low) * (sld_n / self.sld_n_steps)
        return self._convert_type(v)

    def _value_to_slider(self, value):
        rng = self._range_high - self._range_low
        if rng > 1e-30:
            return round((value - self._range_low) / rng * self.sld_n_steps)
        else:
            return 0

    def _accept_value_low(self, val):
        val = self._convert_type(val)
        val_max = self._value_high - self._range_min_diff
        val = val if val <= val_max else val_max
        return self.set_selection(value_low=val)

    def _accept_value_high(self, val):
        val = self._convert_type(val)
        val_min = self._value_low + self._range_min_diff
        val = val if val >= val_min else val_min
        return self.set_selection(value_high=val)

    def _adjust_min_diff(self):
        if self._value_type == "float":
            self._range_min_diff = (self._range_high - self._range_low
                                    ) * self._selection_to_range_min
        else:
            self._range_min_diff = 1

    def _adjust_validators(self):
        """Set the range for validators based on full range and the selected range."""
        if self._value_type == "float":
            # Validator type: QDoubleValidator
            # The range is set a little wider (1% wider) in order to cover the 'true'
            #   boundary value. 'decimals=-1' - it seems that the precision is getting ignored.
            self.validator_low.setRange(
                self._round_value(self._range_low),
                self._round_value(self._value_high -
                                  self._range_min_diff * 0.99),
                decimals=-1,
            )
            self.validator_high.setRange(
                self._round_value(self._value_low +
                                  self._range_min_diff * 0.99),
                self._round_value(self._range_high),
                decimals=-1,
            )
        else:
            # Validator type: QIntValidator
            # With integer arithmetic we can set the range precisely
            self.validator_low.setRange(
                round(self._range_low),
                round(self._value_high - self._range_min_diff))
            self.validator_high.setRange(
                round(self._value_low + self._range_min_diff),
                round(self._range_high))

    def setAlignment(self, flags):
        """
        Set text alignment in QLineEdit widgets

        Parameters
        ----------
        flags: Qt.Alignment flags
            flags that set alignment of text in QLineEdit widgets
            For example `Qt.AlignCenter`. The default settings
            for the widget is `Qt.AlignRight | Qt.AlignVCenter`.
        """

        self.le_min_value.setAlignment(flags)
        self.le_max_value.setAlignment(flags)

    def setBackground(self, rgb):
        """
        Set background color of the widget. Similar to QTableWidgetItem.setBackground,
        but accepting a tuple of RGB values instead of QBrush.

        Parameters
        ----------
        rgb: tuple(int)
            RGB color in the form of (R, G, B)
        """
        self.setStyleSheet(
            get_background_css(rgb, widget="QWidget", editable=False))

        self.le_min_value.setStyleSheet(
            get_background_css(rgb, widget="QLineEdit", editable=True))
        self.le_max_value.setStyleSheet(
            get_background_css(rgb, widget="QLineEdit", editable=True))

    def setTextColor(self, rgb):
        """
        Set text color for the widget. This color is used in 'normal' (valid) state.

        Parameters
        ----------
        rgb: tuple(int)
            RGB color in the form of (R, G, B)
        """
        color = QColor(*rgb)
        pal = self.le_min_value.palette()
        pal.setColor(QPalette.Text, color)
        self.le_min_value.setPalette(pal)
        pal = self.le_max_value.palette()
        pal.setColor(QPalette.Text, color)
        self.le_max_value.setPalette(pal)

    def set_value_type(self, value_type="float"):
        """
        Set value type for the range widget. The value type determines
        the type and format of the displayed and returned values and the type of
        the validator used by the line edit widgets. The current choices are:
        "float" used for working with ranges expressed as floating point (double) numbers;
        "int" is intended for ranges expressed as integers.

        Parameters
        ----------
        value_type: str
            Type of values managed by the widget. The choices are "float" and "int".
            `ValueError` is raised if wrong value is supplied.

        Returns
        -------
        True - selected range was changed when full range was changed, False otherwise.
        """
        self._check_value_type(value_type)
        self._value_type = value_type

        # We don't set validators for QLineEdit widgets. Instead we call validators
        #   explicitly when the input changes. Validators are used to indicate invalid
        #   inputs and prevent user from accepting invalid inputs. There is no goal
        #   to prevent users from typing invalid expressions.
        if self._value_type == "float":
            self.validator_low = DoubleValidatorRelaxed()
            self.validator_high = DoubleValidatorRelaxed()
        else:
            self.validator_low = IntValidatorRelaxed()
            self.validator_high = IntValidatorRelaxed()

        # Completely reset the widget
        return self.set_range(self._range_low, self._range_high)

    def set_range(self, low, high):
        """
        Set the full range of the RangeManager widget. The range may not be negative or
        zero: `low` must be strictly smaller than `high`. The `ValueError` is raised if
        `low >= high`. The function will not emit `selection_changed` signal. Call
        `emit_selection_changed()` method to emit the signal.

        Parameters
        ----------
        low, high: float or int
            lower and upper boundaries of the full range

        Returns
        -------
        True - selected range was changed when full range was changed, False otherwise.
        """
        low, high = self._convert_type(low), self._convert_type(high)
        # Check range
        if low >= high:
            raise ValueError(
                f"RangeManager.set_range(): incorrect range: low > high ({low} > {high})"
            )

        def _compute_new_value(val_old):
            """
            Adjust the current value so that the selected range covers the same
            fraction of the total range.
            """
            range_old = self._range_high - self._range_low
            range_new = high - low
            return (val_old - self._range_low) / range_old * range_new + low

        new_value_low = _compute_new_value(self._value_low)
        new_value_high = _compute_new_value(self._value_high)

        self._range_high = high
        self._range_low = low

        self._value_per_step = (self._range_high -
                                self._range_low) / (self.sld_n_steps - 1)
        self._range_min_diff = (self._range_high -
                                self._range_low) * self._selection_to_range_min

        return self.set_selection(value_low=new_value_low,
                                  value_high=new_value_high)

    def set_selection(self, *, value_low=None, value_high=None):
        """
        Set the selected range. The function may be used to set only lower or upper
        boundary. The function will not emit `selection_changed` signal.
        Call `emit_selection_changed()` method to emit the signal.

        Parameters
        ----------
        value_low: float, int or None
            lower boundary of the selected range. If `None`, then the lower boundary
            is not changed. If `value_low` is outside the full range, then it is clipped.
        value_high: float, int or None
            upper boundary of the selected range. If `None`, then the upper boundary
            is not changed. If `value_low` is outside the full range, then it is clipped.

        Returns
        -------
        True - selected range changed, False - selected range stayed the same
        """
        old_low, old_high = self._value_low, self._value_high

        if value_low is not None or value_high is not None:
            self._adjust_min_diff()
            if value_low is not None:
                value_low = self._convert_type(value_low)
                value_low = max(min(value_low, self._range_high),
                                self._range_low)
                self._value_low = value_low
            if value_high is not None:
                value_high = self._convert_type(value_high)
                value_high = max(min(value_high, self._range_high),
                                 self._range_low)
                self._value_high = value_high
            # Exceptional case when the selection is smaller than the minimum selected
            #   range (or negative). Adjust the range: start at the specified 'low' value
            #   and cover the minimum selectable range; if 'high' value exceeds the top
            #   of the full range, then shift the selected range downwards to fit within
            #   the full range
            if self._value_high < self._value_low + self._range_min_diff:
                self._value_high = self._value_low + self._range_min_diff
                if self._value_high > self._range_high:
                    self._value_high = self._range_high
                    self._value_low = self._range_high - self._range_min_diff
            self._adjust_validators()
        if value_low is not None:
            self.sld_min_value.setValue(self.sld_n_steps -
                                        self._value_to_slider(self._value_low))
            self.le_min_value.setText(self._format_value(self._value_low))
        if value_high is not None:
            self.sld_max_value.setValue(self._value_to_slider(
                self._value_high))
            self.le_max_value.setText(self._format_value(self._value_high))

        # Return True if selection changed
        return (old_low != self._value_low) or (old_high != self._value_high)

    def reset(self):
        """
        Reset the selected range to full range of the RangeManager widget. The method will
        not emit `selection_changed` signal. Call `emit_selection_changed()` to
        emit the signal.

        Returns
        -------
        True - selected range changed, False - selected range stayed the same
        """
        return self.set_selection(value_low=self._range_low,
                                  value_high=self._range_high)

    def get_range(self):
        """
        Get the full range

        Returns
        -------
        tuple `(range_low, range_high)`, the values of `range_low` and `range_high` may be `int`
        or `float` type depending on the type set by `set_value_type()` method.
        """
        return self._range_low, self._range_high

    def get_selection(self):
        """
        Get the selected range

        Returns
        -------
        tuple `(v_low, v_high)`, the values of `v_low` and `v_high` may be `int`
        or `float` type depending on the type set by `set_value_type()` method.
        """
        return self._value_low, self._value_high

    def emit_selection_changed(self):
        """
        Emit `selection_changed` signal that passes the selected range as parameters.
        Note, that the parameters of the signal are ALWAYS `float`.
        """
        v_low = self._convert_type(self._value_low)
        v_high = self._convert_type(self._value_high)
        logger.debug(
            f"RangeManager ({self._name}): Emitting the signal 'selection_changed'. "
            f"Selection: ({v_low}, {v_high})")
        self.selection_changed.emit(v_low, v_high, self._name)