Add image adjustment sliders, mask alpha, Load Prev Mask, and button state colours
- Three vertical sliders (Brightness, Contrast, Gamma) to the left of the canvas for display-only image adjustment; all use power/linear formulae applied on-the-fly without touching saved data - Alpha slider controls mask overlay transparency - Brush size slider moved to its own row - Each slider has a reset (↺) button restoring its default value - Hide Mask button turns red when active; Eraser button turns orange - Load Prev Mask button copies the saved mask from the previous clip in the list onto the current clip; the action is pushed onto the undo stack so it can be reverted with Undo - Right survey panel narrowed (stretch factor 2 → 1) - README Controls section updated to document all new features Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,7 @@ from pathlib import Path
|
||||
import cv2
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
from PySide6.QtCore import QTimer
|
||||
from PySide6.QtCore import Qt, QTimer
|
||||
from PySide6.QtWidgets import (
|
||||
QApplication,
|
||||
QButtonGroup,
|
||||
@@ -113,9 +113,10 @@ class Annotator(QMainWindow):
|
||||
btn_undo = QPushButton("Undo")
|
||||
btn_undo10 = QPushButton("Undo×10")
|
||||
btn_redo = QPushButton("Redo")
|
||||
btn_load_prev_mask = QPushButton("Load Prev Mask")
|
||||
|
||||
row1 = QHBoxLayout()
|
||||
for b in [self.btn_prev, btn_next, btn_skip]:
|
||||
for b in [self.btn_prev, btn_next, btn_skip, btn_load_prev_mask]:
|
||||
row1.addWidget(b)
|
||||
|
||||
row2 = QHBoxLayout()
|
||||
@@ -128,13 +129,42 @@ class Annotator(QMainWindow):
|
||||
self.mc.btn_mask,
|
||||
]:
|
||||
row2.addWidget(b)
|
||||
row2.addWidget(QLabel("Brush"))
|
||||
row2.addWidget(self.mc.brush_slider)
|
||||
|
||||
row3 = QHBoxLayout()
|
||||
row3.addWidget(QLabel("Brush size"))
|
||||
row3.addWidget(self.mc.brush_slider)
|
||||
row3.addWidget(self.mc.brush_reset)
|
||||
|
||||
row4 = QHBoxLayout()
|
||||
row4.addWidget(QLabel("Alpha"))
|
||||
row4.addWidget(self.mc.alpha_slider)
|
||||
row4.addWidget(self.mc.alpha_reset)
|
||||
|
||||
vert_panel = QHBoxLayout()
|
||||
vert_panel.setContentsMargins(0, 0, 4, 0)
|
||||
for label_text, slider, reset_btn in [
|
||||
("Brightness", self.mc.brightness_slider, self.mc.brightness_reset),
|
||||
("Contrast", self.mc.contrast_slider, self.mc.contrast_reset),
|
||||
("Gamma", self.mc.gamma_slider, self.mc.gamma_reset),
|
||||
]:
|
||||
col = QVBoxLayout()
|
||||
lbl = QLabel(label_text)
|
||||
lbl.setAlignment(Qt.AlignmentFlag.AlignHCenter)
|
||||
col.addWidget(lbl)
|
||||
col.addWidget(slider, 1)
|
||||
col.addWidget(reset_btn)
|
||||
vert_panel.addLayout(col)
|
||||
|
||||
canvas_row = QHBoxLayout()
|
||||
canvas_row.addLayout(vert_panel)
|
||||
canvas_row.addWidget(self.mc.canvas, 1)
|
||||
|
||||
left = QVBoxLayout()
|
||||
left.addWidget(self.mc.canvas)
|
||||
left.addLayout(canvas_row)
|
||||
left.addLayout(row1)
|
||||
left.addLayout(row2)
|
||||
left.addLayout(row3)
|
||||
left.addLayout(row4)
|
||||
|
||||
left_widget = QWidget()
|
||||
left_widget.setLayout(left)
|
||||
@@ -143,7 +173,7 @@ class Annotator(QMainWindow):
|
||||
|
||||
main = QHBoxLayout()
|
||||
main.addWidget(left_widget, 3)
|
||||
main.addWidget(right_widget, 2)
|
||||
main.addWidget(right_widget, 1)
|
||||
|
||||
container = QWidget()
|
||||
container.setLayout(main)
|
||||
@@ -156,6 +186,7 @@ class Annotator(QMainWindow):
|
||||
btn_undo.clicked.connect(self.mc.undo)
|
||||
btn_undo10.clicked.connect(self.mc.undo10)
|
||||
btn_redo.clicked.connect(self.mc.redo)
|
||||
btn_load_prev_mask.clicked.connect(self.load_prev_mask)
|
||||
|
||||
if self._pending_answers:
|
||||
self._set_answers(self._pending_answers)
|
||||
@@ -339,3 +370,28 @@ class Annotator(QMainWindow):
|
||||
|
||||
def skip_clip(self):
|
||||
self._advance_clip()
|
||||
|
||||
def load_prev_mask(self):
|
||||
try:
|
||||
idx = self.selector.clips.index(self.filename)
|
||||
except ValueError:
|
||||
return
|
||||
if idx == 0:
|
||||
QMessageBox.information(
|
||||
self, "No previous clip", "This is the first clip in the list."
|
||||
)
|
||||
return
|
||||
prev_clip = self.selector.clips[idx - 1]
|
||||
mask_path = self.out_dir / prev_clip.stem / self.cfg.filenames.mask
|
||||
if not mask_path.exists():
|
||||
QMessageBox.information(
|
||||
self, "No mask found", f"No saved mask found for '{prev_clip.stem}'."
|
||||
)
|
||||
return
|
||||
mask_full = np.array(Image.open(mask_path).convert("L"))
|
||||
mask = cv2.resize(
|
||||
(mask_full > 127).astype(np.uint8),
|
||||
(self.dw, self.dh),
|
||||
interpolation=cv2.INTER_NEAREST,
|
||||
)
|
||||
self.mc.set_mask(mask)
|
||||
|
||||
Reference in New Issue
Block a user