chore: prune boilerplate and refresh stale docs
- Remove 257 redundant per-definition comment stubs across the package - Drop dead nonlocal declarations in autoslicer_detection.py - Document the swapped flag and swap_left_right in architecture docs - Remove unsupported 'legacy override' claims from autotune/runtime docs
This commit is contained in:
parent
929c828438
commit
426a18f342
24 changed files with 5 additions and 264 deletions
|
|
@ -6,7 +6,6 @@ from .autoslicer_feedback import AutoslicerFeedbackMixin
|
|||
from .params import AutoslicerParams
|
||||
|
||||
|
||||
# Autoslicer: class definition for ScanSlicer behavior.
|
||||
class Autoslicer(AutoslicerFeedbackMixin, AutoslicerDetectionMixin):
|
||||
MAX_RETURNED_BOXES = 400
|
||||
MAX_FEEDBACK_ENTRIES = 500
|
||||
|
|
@ -31,7 +30,6 @@ class Autoslicer(AutoslicerFeedbackMixin, AutoslicerDetectionMixin):
|
|||
"dilate_kernel",
|
||||
)
|
||||
|
||||
# __init__: function definition for ScanSlicer behavior.
|
||||
def __init__(self, params=None):
|
||||
self.image = None
|
||||
self.image_gray = None
|
||||
|
|
@ -41,30 +39,24 @@ class Autoslicer(AutoslicerFeedbackMixin, AutoslicerDetectionMixin):
|
|||
self.set_params(params)
|
||||
self.feedback_entries = self.load_feedback_entries()
|
||||
|
||||
# set_params: function definition for ScanSlicer behavior.
|
||||
def set_params(self, params):
|
||||
self.params = params if params is not None else AutoslicerParams()
|
||||
|
||||
# image_loaded: function definition for ScanSlicer behavior.
|
||||
def image_loaded(self):
|
||||
return self.image is not None
|
||||
|
||||
# load_image: function definition for ScanSlicer behavior.
|
||||
def load_image(self, image_path):
|
||||
self.image_path = image_path
|
||||
self.image = cv2.imread(image_path)
|
||||
self.image_gray = cv2.cvtColor(self.image, cv2.COLOR_BGR2GRAY)
|
||||
|
||||
# abort_operation: function definition for ScanSlicer behavior.
|
||||
def abort_operation(self):
|
||||
self.abort_flag = True
|
||||
|
||||
# update_status: function definition for ScanSlicer behavior.
|
||||
def update_status(self, update_status_callback, text):
|
||||
if update_status_callback is not None:
|
||||
update_status_callback(text)
|
||||
|
||||
# save_slice: function definition for ScanSlicer behavior.
|
||||
def save_slice(self, hull_quad, out_path, imwrite_params=None):
|
||||
hull_quad = np.array(hull_quad, dtype=np.float32).reshape(4, 2)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,8 @@ import cv2
|
|||
import numpy as np
|
||||
|
||||
|
||||
# AutoslicerDetectionMixin: class definition for ScanSlicer behavior.
|
||||
class AutoslicerDetectionMixin:
|
||||
# Find all bbox related to n
|
||||
# get_box_relatives: function definition for ScanSlicer behavior.
|
||||
def get_box_relatives(self, hierarchy, n):
|
||||
relatives = []
|
||||
|
||||
|
|
@ -17,7 +15,6 @@ class AutoslicerDetectionMixin:
|
|||
else:
|
||||
return relatives
|
||||
|
||||
# _poly_iou_prepared: function definition for ScanSlicer behavior.
|
||||
def _poly_iou_prepared(self, a, b, area_a=None, area_b=None):
|
||||
if area_a is None:
|
||||
area_a = cv2.contourArea(a)
|
||||
|
|
@ -36,13 +33,11 @@ class AutoslicerDetectionMixin:
|
|||
return 0.0
|
||||
return float(inter_area / union_area)
|
||||
|
||||
# poly_iou: function definition for ScanSlicer behavior.
|
||||
def poly_iou(self, poly_a, poly_b):
|
||||
a = np.asarray(poly_a, dtype=np.float32).reshape(-1, 2)
|
||||
b = np.asarray(poly_b, dtype=np.float32).reshape(-1, 2)
|
||||
return self._poly_iou_prepared(a, b)
|
||||
|
||||
# suppress_border_bar_artifacts: function definition for ScanSlicer behavior.
|
||||
def suppress_border_bar_artifacts(self, mask, update_status_callback=None, status_prefix=""):
|
||||
if mask is None or mask.ndim != 2:
|
||||
return mask
|
||||
|
|
@ -84,9 +79,8 @@ class AutoslicerDetectionMixin:
|
|||
min_band_long_h = max(20, int(round(h * 0.2)))
|
||||
trimmed = 0
|
||||
|
||||
# trim_horizontal_band: function definition for ScanSlicer behavior.
|
||||
def trim_horizontal_band(row_start, row_end):
|
||||
nonlocal cleaned, trimmed
|
||||
nonlocal trimmed
|
||||
band = cleaned[row_start:row_end, :]
|
||||
if band.size == 0:
|
||||
return
|
||||
|
|
@ -108,9 +102,8 @@ class AutoslicerDetectionMixin:
|
|||
trimmed += 1
|
||||
cleaned[row_start:row_end, :] = band
|
||||
|
||||
# trim_vertical_band: function definition for ScanSlicer behavior.
|
||||
def trim_vertical_band(col_start, col_end):
|
||||
nonlocal cleaned, trimmed
|
||||
nonlocal trimmed
|
||||
band = cleaned[:, col_start:col_end]
|
||||
if band.size == 0:
|
||||
return
|
||||
|
|
@ -153,7 +146,6 @@ class AutoslicerDetectionMixin:
|
|||
|
||||
return mask
|
||||
|
||||
# extract_boxes_from_mask: function definition for ScanSlicer behavior.
|
||||
def extract_boxes_from_mask(self, mask, update_status_callback=None, status_prefix=""):
|
||||
mask = self.suppress_border_bar_artifacts(mask, update_status_callback, status_prefix)
|
||||
self.update_status(update_status_callback, status_prefix + "Finding contours...")
|
||||
|
|
@ -290,7 +282,6 @@ class AutoslicerDetectionMixin:
|
|||
|
||||
return boxes
|
||||
|
||||
# apply_morphology: function definition for ScanSlicer behavior.
|
||||
def apply_morphology(self, mask, update_status_callback=None):
|
||||
operation = self.params.morph_op.get()
|
||||
kernel_size = self.params.dilate_kernel.get()
|
||||
|
|
@ -312,7 +303,6 @@ class AutoslicerDetectionMixin:
|
|||
return cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
|
||||
return mask
|
||||
|
||||
# apply_threshold_pipeline: function definition for ScanSlicer behavior.
|
||||
def apply_threshold_pipeline(self, image_gray, update_status_callback=None):
|
||||
# Binary filter
|
||||
if self.params.bw_method.get() == 0:
|
||||
|
|
@ -348,14 +338,12 @@ class AutoslicerDetectionMixin:
|
|||
)
|
||||
return mask
|
||||
|
||||
# apply_canny_pipeline: function definition for ScanSlicer behavior.
|
||||
def apply_canny_pipeline(self, image_gray, update_status_callback=None):
|
||||
self.update_status(update_status_callback, "Canny edge detection...")
|
||||
low = min(self.params.canny_low.get(), self.params.canny_high.get())
|
||||
high = max(self.params.canny_low.get(), self.params.canny_high.get())
|
||||
return cv2.Canny(image_gray, low, high)
|
||||
|
||||
# extract_boxes_from_mask_relaxed: function definition for ScanSlicer behavior.
|
||||
def extract_boxes_from_mask_relaxed(self, mask, update_status_callback=None, status_prefix="", return_areas=False):
|
||||
mask = self.suppress_border_bar_artifacts(mask, update_status_callback, status_prefix)
|
||||
self.update_status(update_status_callback, status_prefix + "Finding contours...")
|
||||
|
|
@ -435,7 +423,6 @@ class AutoslicerDetectionMixin:
|
|||
return boxes, box_areas
|
||||
return boxes
|
||||
|
||||
# white_background_fallback: function definition for ScanSlicer behavior.
|
||||
def white_background_fallback(self, image_gray, update_status_callback=None):
|
||||
self.update_status(update_status_callback, "Fallback: trying white-background recovery...")
|
||||
|
||||
|
|
@ -503,7 +490,6 @@ class AutoslicerDetectionMixin:
|
|||
|
||||
return best_boxes, best_mask
|
||||
|
||||
# autodetect_slices: function definition for ScanSlicer behavior.
|
||||
def autodetect_slices(self, update_status_callback=None, preview_filter_output=False, fast_mode=False):
|
||||
filter_out = self.image_gray
|
||||
self.abort_flag = False
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import cv2
|
|||
import numpy as np
|
||||
|
||||
|
||||
# AutoslicerFeedbackMixin: class definition for ScanSlicer behavior.
|
||||
class AutoslicerFeedbackMixin:
|
||||
AUTOTUNE_PROFILE_ORDER = ("Fast", "Balanced", "Thorough", "Exhaustive")
|
||||
DEFAULT_AUTOTUNE_PROFILE = "Balanced"
|
||||
|
|
@ -86,15 +85,12 @@ class AutoslicerFeedbackMixin:
|
|||
},
|
||||
}
|
||||
|
||||
# autotune_profile_names: function definition for ScanSlicer behavior.
|
||||
def autotune_profile_names(self):
|
||||
return list(self.AUTOTUNE_PROFILE_ORDER)
|
||||
|
||||
# default_autotune_profile_name: function definition for ScanSlicer behavior.
|
||||
def default_autotune_profile_name(self):
|
||||
return self.DEFAULT_AUTOTUNE_PROFILE
|
||||
|
||||
# normalize_autotune_profile: function definition for ScanSlicer behavior.
|
||||
def normalize_autotune_profile(self, profile_name):
|
||||
text = str(profile_name).strip().lower() if profile_name is not None else ""
|
||||
for name in self.AUTOTUNE_PROFILE_ORDER:
|
||||
|
|
@ -102,12 +98,10 @@ class AutoslicerFeedbackMixin:
|
|||
return name
|
||||
return self.DEFAULT_AUTOTUNE_PROFILE
|
||||
|
||||
# get_autotune_profile_config: function definition for ScanSlicer behavior.
|
||||
def get_autotune_profile_config(self, profile_name):
|
||||
normalized = self.normalize_autotune_profile(profile_name)
|
||||
return normalized, dict(self.AUTOTUNE_PROFILES[normalized])
|
||||
|
||||
# sample_candidates_evenly: function definition for ScanSlicer behavior.
|
||||
def sample_candidates_evenly(self, candidates, max_candidates):
|
||||
if max_candidates <= 0 or len(candidates) <= max_candidates:
|
||||
return list(candidates)
|
||||
|
|
@ -136,7 +130,6 @@ class AutoslicerFeedbackMixin:
|
|||
|
||||
return selected
|
||||
|
||||
# feedback_path: function definition for ScanSlicer behavior.
|
||||
def feedback_path(self):
|
||||
override = os.environ.get("SCANSLICER_FEEDBACK_PATH")
|
||||
if override:
|
||||
|
|
@ -150,7 +143,6 @@ class AutoslicerFeedbackMixin:
|
|||
config_home = os.path.join(os.path.expanduser("~"), ".config")
|
||||
return os.path.join(config_home, "scanslicer", "autotune-feedback.json")
|
||||
|
||||
# load_feedback_entries: function definition for ScanSlicer behavior.
|
||||
def load_feedback_entries(self):
|
||||
path = self.feedback_path()
|
||||
if not os.path.isfile(path):
|
||||
|
|
@ -184,7 +176,6 @@ class AutoslicerFeedbackMixin:
|
|||
cleaned.append(entry)
|
||||
return cleaned[-self.MAX_FEEDBACK_ENTRIES:]
|
||||
|
||||
# save_feedback_entries: function definition for ScanSlicer behavior.
|
||||
def save_feedback_entries(self):
|
||||
path = self.feedback_path()
|
||||
directory = os.path.dirname(path)
|
||||
|
|
@ -194,7 +185,6 @@ class AutoslicerFeedbackMixin:
|
|||
json.dump(self.feedback_entries[-self.MAX_FEEDBACK_ENTRIES:], fh, indent=2, sort_keys=True)
|
||||
return path
|
||||
|
||||
# clear_feedback_entries: function definition for ScanSlicer behavior.
|
||||
def clear_feedback_entries(self):
|
||||
cleared_count = len(self.feedback_entries)
|
||||
self.feedback_entries = []
|
||||
|
|
@ -209,7 +199,6 @@ class AutoslicerFeedbackMixin:
|
|||
"removed_file": removed_file,
|
||||
}
|
||||
|
||||
# get_param_range_map: function definition for ScanSlicer behavior.
|
||||
def get_param_range_map(self):
|
||||
range_map = {}
|
||||
for spec in self.params.SPECS:
|
||||
|
|
@ -221,7 +210,6 @@ class AutoslicerFeedbackMixin:
|
|||
range_map[name] = max(1.0, float(max_value - min_value))
|
||||
return range_map
|
||||
|
||||
# compute_image_features: function definition for ScanSlicer behavior.
|
||||
def compute_image_features(self, image_gray=None):
|
||||
gray = image_gray if image_gray is not None else self.image_gray
|
||||
if gray is None:
|
||||
|
|
@ -239,7 +227,6 @@ class AutoslicerFeedbackMixin:
|
|||
"edge_density": float(edge_density),
|
||||
}
|
||||
|
||||
# normalize_boxes_for_storage: function definition for ScanSlicer behavior.
|
||||
def normalize_boxes_for_storage(self, boxes):
|
||||
normalized = []
|
||||
for box in boxes:
|
||||
|
|
@ -247,7 +234,6 @@ class AutoslicerFeedbackMixin:
|
|||
normalized.append([[float(arr[i, 0]), float(arr[i, 1])] for i in range(4)])
|
||||
return normalized
|
||||
|
||||
# boxes_signature: function definition for ScanSlicer behavior.
|
||||
def boxes_signature(self, boxes):
|
||||
if not boxes:
|
||||
return ""
|
||||
|
|
@ -264,7 +250,6 @@ class AutoslicerFeedbackMixin:
|
|||
]
|
||||
return json.dumps(rounded, sort_keys=True)
|
||||
|
||||
# record_feedback: function definition for ScanSlicer behavior.
|
||||
def record_feedback(self, image_path, final_boxes, detected_boxes=None):
|
||||
if not image_path or not final_boxes:
|
||||
return False
|
||||
|
|
@ -303,7 +288,6 @@ class AutoslicerFeedbackMixin:
|
|||
self.save_feedback_entries()
|
||||
return True
|
||||
|
||||
# image_similarity: function definition for ScanSlicer behavior.
|
||||
def image_similarity(self, a, b):
|
||||
if not a or not b:
|
||||
return 0.0
|
||||
|
|
@ -314,7 +298,6 @@ class AutoslicerFeedbackMixin:
|
|||
dist += abs(float(a.get("edge_density", 0.0)) - float(b.get("edge_density", 0.0))) / 0.2
|
||||
return float(math.exp(-dist))
|
||||
|
||||
# param_similarity: function definition for ScanSlicer behavior.
|
||||
def param_similarity(self, candidate_values, reference_values, range_map):
|
||||
distances = []
|
||||
for key in self.AUTOTUNE_PARAM_KEYS:
|
||||
|
|
@ -333,7 +316,6 @@ class AutoslicerFeedbackMixin:
|
|||
mean_dist = float(np.mean(distances))
|
||||
return max(0.0, 1.0 - min(1.0, mean_dist))
|
||||
|
||||
# feedback_score_bonus: function definition for ScanSlicer behavior.
|
||||
def feedback_score_bonus(self, candidate_values, box_count, image_features):
|
||||
if not self.feedback_entries or not image_features:
|
||||
return 0.0
|
||||
|
|
@ -368,7 +350,6 @@ class AutoslicerFeedbackMixin:
|
|||
# Keep this as a moderate bias, not an override.
|
||||
return bonus * 280.0
|
||||
|
||||
# score_detected_boxes: function definition for ScanSlicer behavior.
|
||||
def score_detected_boxes(self, boxes, candidate_values=None, image_features=None, box_area_props=None):
|
||||
if not boxes:
|
||||
return -1e9
|
||||
|
|
@ -418,13 +399,11 @@ class AutoslicerFeedbackMixin:
|
|||
score += self.feedback_score_bonus(candidate, count, image_features)
|
||||
return score
|
||||
|
||||
# build_autotune_candidates: function definition for ScanSlicer behavior.
|
||||
def build_autotune_candidates(self, base_values, profile_name=None):
|
||||
_, profile = self.get_autotune_profile_config(profile_name)
|
||||
base = dict(base_values)
|
||||
candidates = []
|
||||
|
||||
# add: function definition for ScanSlicer behavior.
|
||||
def add(overrides):
|
||||
c = dict(base)
|
||||
c.update(overrides)
|
||||
|
|
@ -521,7 +500,6 @@ class AutoslicerFeedbackMixin:
|
|||
)
|
||||
return self.sample_candidates_evenly(deduped_candidates, max_candidates)
|
||||
|
||||
# rank_autotune_candidates: function definition for ScanSlicer behavior.
|
||||
def rank_autotune_candidates(self, candidates, base_values, image_features):
|
||||
if not candidates:
|
||||
return []
|
||||
|
|
@ -569,7 +547,6 @@ class AutoslicerFeedbackMixin:
|
|||
ranked.sort(key=lambda item: item[0], reverse=True)
|
||||
return [candidate for _, candidate in ranked]
|
||||
|
||||
# autodetect_candidate_boxes: function definition for ScanSlicer behavior.
|
||||
def autodetect_candidate_boxes(self, candidate_values, fast_mode=False, downscale_max_dim=None):
|
||||
original_image = self.image
|
||||
original_gray = self.image_gray
|
||||
|
|
@ -611,7 +588,6 @@ class AutoslicerFeedbackMixin:
|
|||
self.image = original_image
|
||||
self.image_gray = original_gray
|
||||
|
||||
# autotune_for_current_image: function definition for ScanSlicer behavior.
|
||||
def autotune_for_current_image(self, update_status_callback=None, profile_name=None):
|
||||
if not self.image_loaded():
|
||||
raise RuntimeError("No image loaded for auto-tune.")
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ from .canvas_render import CanvasRenderMixin
|
|||
from .canvas_state import CanvasStateMixin
|
||||
|
||||
|
||||
# QtSlicingCanvas: class definition for ScanSlicer behavior.
|
||||
class QtSlicingCanvas(CanvasInteractionMixin, CanvasRenderMixin, CanvasStateMixin, QWidget):
|
||||
AUTO_FIT_SCALE = 0.95
|
||||
OFF_CANVAS_COLOR = QColor(38, 38, 38)
|
||||
|
|
@ -17,7 +16,6 @@ class QtSlicingCanvas(CanvasInteractionMixin, CanvasRenderMixin, CanvasStateMixi
|
|||
SIDE_HANDLE_SIZE_INACTIVE = 9.0
|
||||
active_slice_changed = Signal(int)
|
||||
|
||||
# __init__: function definition for ScanSlicer behavior.
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setMouseTracking(True)
|
||||
|
|
|
|||
|
|
@ -2,16 +2,13 @@ import numpy as np
|
|||
from PySide6.QtCore import QPointF, Qt
|
||||
|
||||
|
||||
# CanvasInteractionMixin: class definition for ScanSlicer behavior.
|
||||
class CanvasInteractionMixin:
|
||||
@staticmethod
|
||||
# _distance_sq: function definition for ScanSlicer behavior.
|
||||
def _distance_sq(pos_xy, point_xy):
|
||||
dx = float(pos_xy[0] - point_xy[0])
|
||||
dy = float(pos_xy[1] - point_xy[1])
|
||||
return dx * dx + dy * dy
|
||||
|
||||
# _iter_slice_indices_for_hit_test: function definition for ScanSlicer behavior.
|
||||
def _iter_slice_indices_for_hit_test(self):
|
||||
active = self.active_slice_index
|
||||
if active is not None and 0 <= int(active) < len(self.slices):
|
||||
|
|
@ -24,7 +21,6 @@ class CanvasInteractionMixin:
|
|||
for si in reversed(range(len(self.slices))):
|
||||
yield si
|
||||
|
||||
# _find_corner: function definition for ScanSlicer behavior.
|
||||
def _find_corner(self, pos):
|
||||
p = np.array([pos.x(), pos.y()], dtype=np.float64)
|
||||
hit_radius = float(getattr(self, "CORNER_HANDLE_HIT_RADIUS", 12.0))
|
||||
|
|
@ -37,7 +33,6 @@ class CanvasInteractionMixin:
|
|||
return si, ci
|
||||
return None
|
||||
|
||||
# _point_in_polygon: function definition for ScanSlicer behavior.
|
||||
def _point_in_polygon(self, point, polygon):
|
||||
x, y = point
|
||||
inside = False
|
||||
|
|
@ -53,7 +48,6 @@ class CanvasInteractionMixin:
|
|||
inside = not inside
|
||||
return inside
|
||||
|
||||
# _find_slice_containing_point: function definition for ScanSlicer behavior.
|
||||
def _find_slice_containing_point(self, pos):
|
||||
point_img = self._view_to_image(pos)
|
||||
for si in reversed(range(len(self.slices))):
|
||||
|
|
@ -61,7 +55,6 @@ class CanvasInteractionMixin:
|
|||
return si
|
||||
return None
|
||||
|
||||
# _find_side_handle: function definition for ScanSlicer behavior.
|
||||
def _find_side_handle(self, pos):
|
||||
p = np.array([pos.x(), pos.y()], dtype=np.float64)
|
||||
hit_radius = float(getattr(self, "SIDE_HANDLE_HIT_RADIUS", 14.0))
|
||||
|
|
@ -77,7 +70,6 @@ class CanvasInteractionMixin:
|
|||
return si, ei
|
||||
return None
|
||||
|
||||
# _try_begin_handle_drag: function definition for ScanSlicer behavior.
|
||||
def _try_begin_handle_drag(self, pos):
|
||||
# Side handles are checked before corners to make squarized edge-drag
|
||||
# interactions easier to grab.
|
||||
|
|
@ -100,7 +92,6 @@ class CanvasInteractionMixin:
|
|||
|
||||
return False
|
||||
|
||||
# _drag_side: function definition for ScanSlicer behavior.
|
||||
def _drag_side(self, si, ei, pos):
|
||||
s = self.slices[si]
|
||||
pts = np.array(s.bbox, dtype=np.float64).reshape(4, 2)
|
||||
|
|
@ -130,7 +121,6 @@ class CanvasInteractionMixin:
|
|||
pts[i1] += shift
|
||||
s.bbox = self._clamp_bbox_to_image(pts)
|
||||
|
||||
# _start_rotation_drag: function definition for ScanSlicer behavior.
|
||||
def _start_rotation_drag(self, si, pos):
|
||||
s = self.slices[si]
|
||||
center = np.mean(s.bbox, axis=0)
|
||||
|
|
@ -146,7 +136,6 @@ class CanvasInteractionMixin:
|
|||
"start_bbox": np.array(s.bbox, dtype=np.float64),
|
||||
}
|
||||
|
||||
# _drag_rotation: function definition for ScanSlicer behavior.
|
||||
def _drag_rotation(self, pos):
|
||||
if self.rotation_dragging is None:
|
||||
return
|
||||
|
|
@ -177,7 +166,6 @@ class CanvasInteractionMixin:
|
|||
rotated = (start_bbox - center) @ rotation.T + center
|
||||
self.slices[si].bbox = self._clamp_bbox_to_image(rotated)
|
||||
|
||||
# _start_box_drag: function definition for ScanSlicer behavior.
|
||||
def _start_box_drag(self, si, pos):
|
||||
return {
|
||||
"slice_index": si,
|
||||
|
|
@ -185,7 +173,6 @@ class CanvasInteractionMixin:
|
|||
"start_bbox": np.array(self.slices[si].bbox, dtype=np.float64),
|
||||
}
|
||||
|
||||
# _drag_box: function definition for ScanSlicer behavior.
|
||||
def _drag_box(self, pos):
|
||||
if self.box_dragging is None:
|
||||
return
|
||||
|
|
@ -199,7 +186,6 @@ class CanvasInteractionMixin:
|
|||
moved = self.box_dragging["start_bbox"] + delta
|
||||
self.slices[si].bbox = self._clamp_bbox_to_image(moved)
|
||||
|
||||
# mousePressEvent: function definition for ScanSlicer behavior.
|
||||
def mousePressEvent(self, event):
|
||||
pos = event.position()
|
||||
|
||||
|
|
@ -235,7 +221,6 @@ class CanvasInteractionMixin:
|
|||
if inside is not None:
|
||||
self.set_active_slice(inside)
|
||||
|
||||
# mouseDoubleClickEvent: function definition for ScanSlicer behavior.
|
||||
def mouseDoubleClickEvent(self, event):
|
||||
if event.button() != Qt.LeftButton or self.image is None:
|
||||
return
|
||||
|
|
@ -251,7 +236,6 @@ class CanvasInteractionMixin:
|
|||
self.set_active_slice(inside)
|
||||
self.box_dragging = self._start_box_drag(inside, pos)
|
||||
|
||||
# mouseMoveEvent: function definition for ScanSlicer behavior.
|
||||
def mouseMoveEvent(self, event):
|
||||
pos = event.position()
|
||||
|
||||
|
|
@ -286,7 +270,6 @@ class CanvasInteractionMixin:
|
|||
self._drag_box(pos)
|
||||
self.update()
|
||||
|
||||
# mouseReleaseEvent: function definition for ScanSlicer behavior.
|
||||
def mouseReleaseEvent(self, event):
|
||||
if event.button() == Qt.RightButton:
|
||||
self.panning = False
|
||||
|
|
@ -296,7 +279,6 @@ class CanvasInteractionMixin:
|
|||
self.rotation_dragging = None
|
||||
self.box_dragging = None
|
||||
|
||||
# wheelEvent: function definition for ScanSlicer behavior.
|
||||
def wheelEvent(self, event):
|
||||
if self.image is None:
|
||||
return
|
||||
|
|
@ -324,7 +306,6 @@ class CanvasInteractionMixin:
|
|||
)
|
||||
self.update()
|
||||
|
||||
# resizeEvent: function definition for ScanSlicer behavior.
|
||||
def resizeEvent(self, event):
|
||||
super().resizeEvent(event)
|
||||
if self.image is not None and not self.user_transformed_view:
|
||||
|
|
|
|||
|
|
@ -3,9 +3,7 @@ from PySide6.QtCore import QRectF, Qt
|
|||
from PySide6.QtGui import QColor, QFont, QPainter, QPen
|
||||
|
||||
|
||||
# CanvasRenderMixin: class definition for ScanSlicer behavior.
|
||||
class CanvasRenderMixin:
|
||||
# paintEvent: function definition for ScanSlicer behavior.
|
||||
def paintEvent(self, event):
|
||||
painter = QPainter(self)
|
||||
painter.fillRect(self.rect(), self.OFF_CANVAS_COLOR)
|
||||
|
|
|
|||
|
|
@ -4,9 +4,7 @@ from PySide6.QtCore import QPointF, QRectF
|
|||
from .slices import PhotoSlice, polys_iou
|
||||
|
||||
|
||||
# CanvasStateMixin: class definition for ScanSlicer behavior.
|
||||
class CanvasStateMixin:
|
||||
# fit_image_to_view: function definition for ScanSlicer behavior.
|
||||
def fit_image_to_view(self):
|
||||
if self.image is None:
|
||||
return
|
||||
|
|
@ -22,7 +20,6 @@ class CanvasStateMixin:
|
|||
shown_h = image_h * self.zoom
|
||||
self.pan_offset = QPointF((view_w - shown_w) / 2.0, (view_h - shown_h) / 2.0)
|
||||
|
||||
# reset_view: function definition for ScanSlicer behavior.
|
||||
def reset_view(self):
|
||||
if self.image is None:
|
||||
return False
|
||||
|
|
@ -32,7 +29,6 @@ class CanvasStateMixin:
|
|||
self.update()
|
||||
return True
|
||||
|
||||
# set_image: function definition for ScanSlicer behavior.
|
||||
def set_image(self, image, new_image=False):
|
||||
if self.image is None or new_image:
|
||||
self.user_transformed_view = False
|
||||
|
|
@ -44,27 +40,23 @@ class CanvasStateMixin:
|
|||
self.fit_image_to_view()
|
||||
self.update()
|
||||
|
||||
# _clamp_slice_to_image: function definition for ScanSlicer behavior.
|
||||
def _clamp_slice_to_image(self, slice_obj):
|
||||
if slice_obj is None:
|
||||
return None
|
||||
slice_obj.bbox = self._clamp_bbox_to_image(slice_obj.bbox)
|
||||
return slice_obj
|
||||
|
||||
# _coerce_slice_in_bounds: function definition for ScanSlicer behavior.
|
||||
def _coerce_slice_in_bounds(self, bbx):
|
||||
if isinstance(bbx, PhotoSlice):
|
||||
return self._clamp_slice_to_image(bbx)
|
||||
return PhotoSlice(self._clamp_bbox_to_image(bbx))
|
||||
|
||||
# add_bbox: function definition for ScanSlicer behavior.
|
||||
def add_bbox(self, bbx):
|
||||
self.slices.append(self._coerce_slice_in_bounds(bbx))
|
||||
self.set_active_slice(len(self.slices) - 1)
|
||||
self.update()
|
||||
|
||||
@staticmethod
|
||||
# _aabb_overlaps: function definition for ScanSlicer behavior.
|
||||
def _aabb_overlaps(poly_a, poly_b):
|
||||
a = np.array(poly_a, dtype=np.float32).reshape(-1, 2)
|
||||
b = np.array(poly_b, dtype=np.float32).reshape(-1, 2)
|
||||
|
|
@ -83,7 +75,6 @@ class CanvasStateMixin:
|
|||
or b_max_y < a_min_y
|
||||
)
|
||||
|
||||
# update_bboxes: function definition for ScanSlicer behavior.
|
||||
def update_bboxes(self, bbxs=None):
|
||||
if bbxs is not None:
|
||||
active_slice = None
|
||||
|
|
@ -114,7 +105,6 @@ class CanvasStateMixin:
|
|||
|
||||
self.update()
|
||||
|
||||
# set_active_slice: function definition for ScanSlicer behavior.
|
||||
def set_active_slice(self, index):
|
||||
if index is None:
|
||||
normalized = None
|
||||
|
|
@ -130,7 +120,6 @@ class CanvasStateMixin:
|
|||
self.active_slice_changed.emit(-1 if normalized is None else normalized)
|
||||
self.update()
|
||||
|
||||
# get_active_slice: function definition for ScanSlicer behavior.
|
||||
def get_active_slice(self):
|
||||
if self.active_slice_index is None:
|
||||
return None
|
||||
|
|
@ -139,7 +128,6 @@ class CanvasStateMixin:
|
|||
return None
|
||||
return self.slices[self.active_slice_index]
|
||||
|
||||
# set_active_slice_locked: function definition for ScanSlicer behavior.
|
||||
def set_active_slice_locked(self, locked):
|
||||
active_slice = self.get_active_slice()
|
||||
if active_slice is None:
|
||||
|
|
@ -148,20 +136,17 @@ class CanvasStateMixin:
|
|||
self.update()
|
||||
return True
|
||||
|
||||
# set_all_slices_locked: function definition for ScanSlicer behavior.
|
||||
def set_all_slices_locked(self, locked):
|
||||
for s in self.slices:
|
||||
s.toggle_locked(bool(locked))
|
||||
self.update()
|
||||
return len(self.slices)
|
||||
|
||||
# are_all_slices_locked: function definition for ScanSlicer behavior.
|
||||
def are_all_slices_locked(self):
|
||||
if len(self.slices) == 0:
|
||||
return False
|
||||
return all(s.locked for s in self.slices)
|
||||
|
||||
# remove_active_slice: function definition for ScanSlicer behavior.
|
||||
def remove_active_slice(self):
|
||||
if self.active_slice_index is None:
|
||||
return False
|
||||
|
|
@ -183,7 +168,6 @@ class CanvasStateMixin:
|
|||
self.update()
|
||||
return True
|
||||
|
||||
# squarize_active_slice: function definition for ScanSlicer behavior.
|
||||
def squarize_active_slice(self):
|
||||
active_slice = self.get_active_slice()
|
||||
if active_slice is None:
|
||||
|
|
@ -192,7 +176,6 @@ class CanvasStateMixin:
|
|||
self.update()
|
||||
return True
|
||||
|
||||
# swap_active_slice_left_right: function definition for ScanSlicer behavior.
|
||||
def swap_active_slice_left_right(self):
|
||||
active_slice = self.get_active_slice()
|
||||
if active_slice is None:
|
||||
|
|
@ -201,7 +184,6 @@ class CanvasStateMixin:
|
|||
self.update()
|
||||
return True
|
||||
|
||||
# reset_active_slice_rotation: function definition for ScanSlicer behavior.
|
||||
def reset_active_slice_rotation(self):
|
||||
active_slice = self.get_active_slice()
|
||||
if active_slice is None:
|
||||
|
|
@ -211,11 +193,9 @@ class CanvasStateMixin:
|
|||
self.update()
|
||||
return True
|
||||
|
||||
# set_rotate_handles_mode: function definition for ScanSlicer behavior.
|
||||
def set_rotate_handles_mode(self, enabled):
|
||||
self.rotate_handles_mode = bool(enabled)
|
||||
|
||||
# _image_rect_view: function definition for ScanSlicer behavior.
|
||||
def _image_rect_view(self):
|
||||
if self.image is None:
|
||||
return QRectF()
|
||||
|
|
@ -226,7 +206,6 @@ class CanvasStateMixin:
|
|||
self.image.height() * self.zoom,
|
||||
)
|
||||
|
||||
# _view_to_image: function definition for ScanSlicer behavior.
|
||||
def _view_to_image(self, p):
|
||||
return np.array(
|
||||
[
|
||||
|
|
@ -236,7 +215,6 @@ class CanvasStateMixin:
|
|||
dtype=np.float64,
|
||||
)
|
||||
|
||||
# _clamp_view_pos_to_canvas: function definition for ScanSlicer behavior.
|
||||
def _clamp_view_pos_to_canvas(self, p):
|
||||
max_x = max(0.0, float(self.width() - 1))
|
||||
max_y = max(0.0, float(self.height() - 1))
|
||||
|
|
@ -245,7 +223,6 @@ class CanvasStateMixin:
|
|||
min(max(p.y(), 0.0), max_y),
|
||||
)
|
||||
|
||||
# _clamp_image_point: function definition for ScanSlicer behavior.
|
||||
def _clamp_image_point(self, point):
|
||||
if self.image is None:
|
||||
return np.array(point, dtype=np.float64)
|
||||
|
|
@ -257,7 +234,6 @@ class CanvasStateMixin:
|
|||
p[1] = min(max(p[1], 0.0), max_y)
|
||||
return p
|
||||
|
||||
# _clamp_bbox_to_image: function definition for ScanSlicer behavior.
|
||||
def _clamp_bbox_to_image(self, bbox):
|
||||
if self.image is None:
|
||||
return np.array(bbox, dtype=np.float64)
|
||||
|
|
@ -269,7 +245,6 @@ class CanvasStateMixin:
|
|||
pts[:, 1] = np.clip(pts[:, 1], 0.0, max_y)
|
||||
return pts
|
||||
|
||||
# _image_to_view: function definition for ScanSlicer behavior.
|
||||
def _image_to_view(self, p):
|
||||
return QPointF(
|
||||
p[0] * self.zoom + self.pan_offset.x(),
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ from .controller_params import ScanSlicerParamsMixin
|
|||
from .controller_status import ScanSlicerStatusMixin
|
||||
|
||||
|
||||
# ScanSlicerControllerMixin: class definition for ScanSlicer behavior.
|
||||
class ScanSlicerControllerMixin(
|
||||
ScanSlicerStatusMixin,
|
||||
ScanSlicerCanvasActionsMixin,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,10 @@
|
|||
from .slices import PhotoSlice
|
||||
|
||||
|
||||
# ScanSlicerCanvasActionsMixin: class definition for ScanSlicer behavior.
|
||||
class ScanSlicerCanvasActionsMixin:
|
||||
# on_active_slice_changed: function definition for ScanSlicer behavior.
|
||||
def on_active_slice_changed(self, index):
|
||||
self.refresh_active_box_toolbar(index)
|
||||
|
||||
# refresh_active_box_toolbar: function definition for ScanSlicer behavior.
|
||||
def refresh_active_box_toolbar(self, index=None):
|
||||
if index == -1:
|
||||
index = None
|
||||
|
|
@ -45,14 +42,12 @@ class ScanSlicerCanvasActionsMixin:
|
|||
self.active_box_lock_button.setChecked(active_slice.locked)
|
||||
self.active_box_lock_button.blockSignals(False)
|
||||
|
||||
# toggle_active_box_lock: function definition for ScanSlicer behavior.
|
||||
def toggle_active_box_lock(self, checked):
|
||||
if not self.slicing_canvas.set_active_slice_locked(checked):
|
||||
self.refresh_active_box_toolbar()
|
||||
return
|
||||
self.refresh_active_box_toolbar()
|
||||
|
||||
# toggle_all_boxes_lock: function definition for ScanSlicer behavior.
|
||||
def toggle_all_boxes_lock(self, checked):
|
||||
count = self.slicing_canvas.set_all_slices_locked(checked)
|
||||
self.refresh_active_box_toolbar()
|
||||
|
|
@ -64,28 +59,24 @@ class ScanSlicerCanvasActionsMixin:
|
|||
else:
|
||||
self.update_statusbar(f"Unlocked {count} box(es).")
|
||||
|
||||
# squarize_active_box: function definition for ScanSlicer behavior.
|
||||
def squarize_active_box(self):
|
||||
if not self.slicing_canvas.squarize_active_slice():
|
||||
self.update_statusbar("No active box selected.")
|
||||
return
|
||||
self.update_statusbar("Active box squarized.")
|
||||
|
||||
# swap_active_box_left_right: function definition for ScanSlicer behavior.
|
||||
def swap_active_box_left_right(self):
|
||||
if not self.slicing_canvas.swap_active_slice_left_right():
|
||||
self.update_statusbar("No active box selected.")
|
||||
return
|
||||
self.update_statusbar("Active box left/right swapped.")
|
||||
|
||||
# reset_active_box_rotation: function definition for ScanSlicer behavior.
|
||||
def reset_active_box_rotation(self):
|
||||
if not self.slicing_canvas.reset_active_slice_rotation():
|
||||
self.update_statusbar("No active box selected.")
|
||||
return
|
||||
self.update_statusbar("Active box rotation reset.")
|
||||
|
||||
# remove_active_box: function definition for ScanSlicer behavior.
|
||||
def remove_active_box(self):
|
||||
if not self.slicing_canvas.remove_active_slice():
|
||||
self.update_statusbar("No active box selected.")
|
||||
|
|
@ -93,7 +84,6 @@ class ScanSlicerCanvasActionsMixin:
|
|||
self.refresh_active_box_toolbar()
|
||||
self.update_statusbar("Active box removed.")
|
||||
|
||||
# set_preview_filter_enabled: function definition for ScanSlicer behavior.
|
||||
def set_preview_filter_enabled(self, checked):
|
||||
self.preview_filter_enabled = bool(checked)
|
||||
if self.preview_filter_enabled:
|
||||
|
|
@ -104,7 +94,6 @@ class ScanSlicerCanvasActionsMixin:
|
|||
if self.autoslicer.image_loaded():
|
||||
self.update_preview(new_image=False, run_detection=False)
|
||||
|
||||
# set_rotate_handles_mode: function definition for ScanSlicer behavior.
|
||||
def set_rotate_handles_mode(self, checked):
|
||||
self.slicing_canvas.set_rotate_handles_mode(checked)
|
||||
if checked:
|
||||
|
|
@ -112,14 +101,12 @@ class ScanSlicerCanvasActionsMixin:
|
|||
else:
|
||||
self.update_statusbar("Rotate mode disabled.")
|
||||
|
||||
# reset_canvas_view: function definition for ScanSlicer behavior.
|
||||
def reset_canvas_view(self):
|
||||
if not self.slicing_canvas.reset_view():
|
||||
self.update_statusbar("No image loaded.")
|
||||
return
|
||||
self.update_statusbar("View reset.")
|
||||
|
||||
# add_box: function definition for ScanSlicer behavior.
|
||||
def add_box(self):
|
||||
new_slice = PhotoSlice(None)
|
||||
new_slice.toggle_locked()
|
||||
|
|
|
|||
|
|
@ -6,22 +6,18 @@ from PySide6.QtGui import QImage
|
|||
from PySide6.QtWidgets import QMessageBox
|
||||
|
||||
|
||||
# DetectionTaskSignals: class definition for ScanSlicer behavior.
|
||||
class DetectionTaskSignals(QObject):
|
||||
status = Signal(str)
|
||||
finished = Signal(object, object, object)
|
||||
failed = Signal(str)
|
||||
|
||||
|
||||
# DetectionTask: class definition for ScanSlicer behavior.
|
||||
class DetectionTask(QRunnable):
|
||||
# __init__: function definition for ScanSlicer behavior.
|
||||
def __init__(self, autoslicer):
|
||||
super().__init__()
|
||||
self.autoslicer = autoslicer
|
||||
self.signals = DetectionTaskSignals()
|
||||
|
||||
# run: function definition for ScanSlicer behavior.
|
||||
def run(self):
|
||||
try:
|
||||
boxes, filter_preview_image = self.autoslicer.autodetect_slices(
|
||||
|
|
@ -35,23 +31,19 @@ class DetectionTask(QRunnable):
|
|||
self.signals.failed.emit(details)
|
||||
|
||||
|
||||
# AutotuneTaskSignals: class definition for ScanSlicer behavior.
|
||||
class AutotuneTaskSignals(QObject):
|
||||
status = Signal(str)
|
||||
finished = Signal(object)
|
||||
failed = Signal(str)
|
||||
|
||||
|
||||
# AutotuneTask: class definition for ScanSlicer behavior.
|
||||
class AutotuneTask(QRunnable):
|
||||
# __init__: function definition for ScanSlicer behavior.
|
||||
def __init__(self, autoslicer, profile_name):
|
||||
super().__init__()
|
||||
self.autoslicer = autoslicer
|
||||
self.profile_name = profile_name
|
||||
self.signals = AutotuneTaskSignals()
|
||||
|
||||
# run: function definition for ScanSlicer behavior.
|
||||
def run(self):
|
||||
try:
|
||||
result = self.autoslicer.autotune_for_current_image(
|
||||
|
|
@ -64,16 +56,13 @@ class AutotuneTask(QRunnable):
|
|||
self.signals.failed.emit(details)
|
||||
|
||||
|
||||
# rgb_array_to_qimage: function definition for ScanSlicer behavior.
|
||||
def rgb_array_to_qimage(image):
|
||||
h, w, c = image.shape
|
||||
bytes_per_line = c * w
|
||||
return QImage(image.data, w, h, bytes_per_line, QImage.Format_RGB888).copy()
|
||||
|
||||
|
||||
# ScanSlicerDetectionMixin: class definition for ScanSlicer behavior.
|
||||
class ScanSlicerDetectionMixin:
|
||||
# _get_worker_pool: function definition for ScanSlicer behavior.
|
||||
def _get_worker_pool(self):
|
||||
pool = getattr(self, "_worker_pool", None)
|
||||
if pool is None:
|
||||
|
|
@ -83,13 +72,11 @@ class ScanSlicerDetectionMixin:
|
|||
self._worker_pool = pool
|
||||
return pool
|
||||
|
||||
# invalidate_preview_cache: function definition for ScanSlicer behavior.
|
||||
def invalidate_preview_cache(self):
|
||||
self.last_detected_boxes = None
|
||||
self.last_preview_normal_image = None
|
||||
self.last_preview_filter_image = None
|
||||
|
||||
# has_preview_cache: function definition for ScanSlicer behavior.
|
||||
def has_preview_cache(self):
|
||||
return (
|
||||
self.last_detected_boxes is not None
|
||||
|
|
@ -97,7 +84,6 @@ class ScanSlicerDetectionMixin:
|
|||
and self.last_preview_filter_image is not None
|
||||
)
|
||||
|
||||
# render_cached_preview: function definition for ScanSlicer behavior.
|
||||
def render_cached_preview(self, new_image=False):
|
||||
if not self.has_preview_cache():
|
||||
return False
|
||||
|
|
@ -105,7 +91,6 @@ class ScanSlicerDetectionMixin:
|
|||
self.slicing_canvas.set_image(rgb_array_to_qimage(image), new_image)
|
||||
return True
|
||||
|
||||
# update_preview: function definition for ScanSlicer behavior.
|
||||
def update_preview(self, new_image=False, run_detection=True):
|
||||
if self.processing_busy:
|
||||
return
|
||||
|
|
@ -121,7 +106,6 @@ class ScanSlicerDetectionMixin:
|
|||
if run_detection:
|
||||
self._start_detection_worker(new_image=bool(new_image))
|
||||
|
||||
# _start_detection_worker: function definition for ScanSlicer behavior.
|
||||
def _start_detection_worker(self, new_image=False):
|
||||
if getattr(self, "_is_closing", False):
|
||||
return
|
||||
|
|
@ -148,7 +132,6 @@ class ScanSlicerDetectionMixin:
|
|||
|
||||
self._get_worker_pool().start(task)
|
||||
|
||||
# _is_active_detection_task: function definition for ScanSlicer behavior.
|
||||
def _is_active_detection_task(self, task_id):
|
||||
return (
|
||||
task_id is not None
|
||||
|
|
@ -156,7 +139,6 @@ class ScanSlicerDetectionMixin:
|
|||
and bool(getattr(self, "_detection_running", False))
|
||||
)
|
||||
|
||||
# _on_detection_status: function definition for ScanSlicer behavior.
|
||||
def _on_detection_status(self, task_id, text):
|
||||
if getattr(self, "_is_closing", False):
|
||||
return
|
||||
|
|
@ -164,7 +146,6 @@ class ScanSlicerDetectionMixin:
|
|||
return
|
||||
self.update_statusbar(text)
|
||||
|
||||
# _on_detection_finished: function definition for ScanSlicer behavior.
|
||||
def _on_detection_finished(self, task_id, bbxs, filter_preview_image, normal_preview_image):
|
||||
if not self._is_active_detection_task(task_id):
|
||||
return
|
||||
|
|
@ -196,7 +177,6 @@ class ScanSlicerDetectionMixin:
|
|||
if not getattr(self, "_is_closing", False):
|
||||
self.set_processing_busy(False)
|
||||
|
||||
# _on_detection_failed: function definition for ScanSlicer behavior.
|
||||
def _on_detection_failed(self, task_id, error_text):
|
||||
if not self._is_active_detection_task(task_id):
|
||||
return
|
||||
|
|
@ -209,7 +189,6 @@ class ScanSlicerDetectionMixin:
|
|||
self.update_statusbar("Detection failed.")
|
||||
self.set_processing_busy(False)
|
||||
|
||||
# _start_autotune_worker: function definition for ScanSlicer behavior.
|
||||
def _start_autotune_worker(self):
|
||||
if getattr(self, "_is_closing", False):
|
||||
return
|
||||
|
|
@ -235,7 +214,6 @@ class ScanSlicerDetectionMixin:
|
|||
|
||||
self._get_worker_pool().start(task)
|
||||
|
||||
# _is_active_autotune_task: function definition for ScanSlicer behavior.
|
||||
def _is_active_autotune_task(self, task_id):
|
||||
return (
|
||||
task_id is not None
|
||||
|
|
@ -243,7 +221,6 @@ class ScanSlicerDetectionMixin:
|
|||
and bool(getattr(self, "_autotune_running", False))
|
||||
)
|
||||
|
||||
# _on_autotune_status: function definition for ScanSlicer behavior.
|
||||
def _on_autotune_status(self, task_id, text):
|
||||
if getattr(self, "_is_closing", False):
|
||||
return
|
||||
|
|
@ -251,7 +228,6 @@ class ScanSlicerDetectionMixin:
|
|||
return
|
||||
self.update_statusbar(text)
|
||||
|
||||
# _on_autotune_finished: function definition for ScanSlicer behavior.
|
||||
def _on_autotune_finished(self, task_id, result):
|
||||
if not self._is_active_autotune_task(task_id):
|
||||
return
|
||||
|
|
@ -283,7 +259,6 @@ class ScanSlicerDetectionMixin:
|
|||
)
|
||||
self.update_preview(new_image=False, run_detection=True)
|
||||
|
||||
# _on_autotune_failed: function definition for ScanSlicer behavior.
|
||||
def _on_autotune_failed(self, task_id, error_text):
|
||||
if not self._is_active_autotune_task(task_id):
|
||||
return
|
||||
|
|
|
|||
|
|
@ -6,9 +6,7 @@ import numpy as np
|
|||
from PySide6.QtWidgets import QFileDialog, QMessageBox
|
||||
|
||||
|
||||
# ScanSlicerImageIOMixin: class definition for ScanSlicer behavior.
|
||||
class ScanSlicerImageIOMixin:
|
||||
# resolve_directory_dialog_start_dir: function definition for ScanSlicer behavior.
|
||||
def resolve_directory_dialog_start_dir(self):
|
||||
if self.input_dir and os.path.isdir(self.input_dir):
|
||||
return self.input_dir
|
||||
|
|
@ -24,7 +22,6 @@ class ScanSlicerImageIOMixin:
|
|||
return home_dir
|
||||
return ""
|
||||
|
||||
# refresh_goto_image_dropdown: function definition for ScanSlicer behavior.
|
||||
def refresh_goto_image_dropdown(self):
|
||||
dropdown = getattr(self, "goto_image_dropdown", None)
|
||||
if dropdown is None:
|
||||
|
|
@ -38,7 +35,6 @@ class ScanSlicerImageIOMixin:
|
|||
dropdown.blockSignals(False)
|
||||
self.sync_goto_image_selection()
|
||||
|
||||
# sync_goto_image_selection: function definition for ScanSlicer behavior.
|
||||
def sync_goto_image_selection(self):
|
||||
dropdown = getattr(self, "goto_image_dropdown", None)
|
||||
if dropdown is None:
|
||||
|
|
@ -57,7 +53,6 @@ class ScanSlicerImageIOMixin:
|
|||
dropdown.setCurrentIndex(index)
|
||||
dropdown.blockSignals(False)
|
||||
|
||||
# goto_image_from_dropdown: function definition for ScanSlicer behavior.
|
||||
def goto_image_from_dropdown(self, index):
|
||||
if self.processing_busy:
|
||||
return
|
||||
|
|
@ -69,9 +64,7 @@ class ScanSlicerImageIOMixin:
|
|||
self.source_index = index
|
||||
self.show_current_image(new_image=True)
|
||||
|
||||
# sort_image_paths: function definition for ScanSlicer behavior.
|
||||
def sort_image_paths(self, image_paths):
|
||||
# safe_mtime: function definition for ScanSlicer behavior.
|
||||
def safe_mtime(path):
|
||||
try:
|
||||
return os.path.getmtime(path)
|
||||
|
|
@ -95,7 +88,6 @@ class ScanSlicerImageIOMixin:
|
|||
),
|
||||
)
|
||||
|
||||
# show_current_image: function definition for ScanSlicer behavior.
|
||||
def show_current_image(self, new_image=True):
|
||||
if self.processing_busy:
|
||||
return
|
||||
|
|
@ -111,7 +103,6 @@ class ScanSlicerImageIOMixin:
|
|||
self.update_preview(new_image=new_image, run_detection=True)
|
||||
self.update_statusbar("Loaded " + self.source_images[self.source_index])
|
||||
|
||||
# load_image: function definition for ScanSlicer behavior.
|
||||
def load_image(self, move_index=0):
|
||||
if self.processing_busy:
|
||||
return
|
||||
|
|
@ -138,20 +129,16 @@ class ScanSlicerImageIOMixin:
|
|||
|
||||
self.show_current_image(new_image=True)
|
||||
|
||||
# set_save_format: function definition for ScanSlicer behavior.
|
||||
def set_save_format(self, choice):
|
||||
self.save_format = choice
|
||||
self.update_quality_controls_state()
|
||||
|
||||
# set_jpg_quality: function definition for ScanSlicer behavior.
|
||||
def set_jpg_quality(self, value):
|
||||
self.jpg_quality = int(value)
|
||||
|
||||
# set_png_compression: function definition for ScanSlicer behavior.
|
||||
def set_png_compression(self, value):
|
||||
self.png_compression = int(value)
|
||||
|
||||
# update_quality_controls_state: function definition for ScanSlicer behavior.
|
||||
def update_quality_controls_state(self):
|
||||
jpeg_enabled = self.save_format == "jpg"
|
||||
png_enabled = self.save_format == "png"
|
||||
|
|
@ -162,7 +149,6 @@ class ScanSlicerImageIOMixin:
|
|||
self.png_compression_spinbox.setEnabled(png_enabled)
|
||||
self.png_compression_spinbox.setVisible(png_enabled)
|
||||
|
||||
# set_sort_mode: function definition for ScanSlicer behavior.
|
||||
def set_sort_mode(self, choice):
|
||||
mode = choice.lower()
|
||||
if mode not in ("name", "time"):
|
||||
|
|
@ -187,14 +173,12 @@ class ScanSlicerImageIOMixin:
|
|||
|
||||
self.show_current_image(new_image=True)
|
||||
|
||||
# update_output_dir_label: function definition for ScanSlicer behavior.
|
||||
def update_output_dir_label(self):
|
||||
if self.output_dir:
|
||||
self.output_dir_label.setText(self.output_dir)
|
||||
else:
|
||||
self.output_dir_label.setText("(not set)")
|
||||
|
||||
# set_output_directory: function definition for ScanSlicer behavior.
|
||||
def set_output_directory(self, basedir=None):
|
||||
# QPushButton.clicked emits a bool; ignore it when this method is used as a slot.
|
||||
if isinstance(basedir, bool):
|
||||
|
|
@ -211,7 +195,6 @@ class ScanSlicerImageIOMixin:
|
|||
self.update_output_dir_label()
|
||||
self.update_statusbar("Output directory set to " + self.output_dir)
|
||||
|
||||
# save_all: function definition for ScanSlicer behavior.
|
||||
def save_all(self):
|
||||
if self.processing_busy:
|
||||
return
|
||||
|
|
@ -276,7 +259,6 @@ class ScanSlicerImageIOMixin:
|
|||
self.append_status_log(f"Feedback save failed: {exc}")
|
||||
QMessageBox.information(self, "Slices saved", f"{total_saved} slices have been saved")
|
||||
|
||||
# open_directory: function definition for ScanSlicer behavior.
|
||||
def open_directory(self, basedir=None):
|
||||
if self.processing_busy:
|
||||
return
|
||||
|
|
@ -313,10 +295,8 @@ class ScanSlicerImageIOMixin:
|
|||
else:
|
||||
self.load_image(0)
|
||||
|
||||
# next_image: function definition for ScanSlicer behavior.
|
||||
def next_image(self):
|
||||
self.load_image(1)
|
||||
|
||||
# prev_image: function definition for ScanSlicer behavior.
|
||||
def prev_image(self):
|
||||
self.load_image(-1)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
from PySide6.QtWidgets import QComboBox, QInputDialog, QMessageBox, QSlider
|
||||
|
||||
|
||||
# ScanSlicerParamsMixin: class definition for ScanSlicer behavior.
|
||||
class ScanSlicerParamsMixin:
|
||||
# set_autotune_profile: function definition for ScanSlicer behavior.
|
||||
def set_autotune_profile(self, profile_name):
|
||||
normalized = self.autoslicer.normalize_autotune_profile(profile_name)
|
||||
self.autotune_profile = normalized
|
||||
|
|
@ -14,12 +12,10 @@ class ScanSlicerParamsMixin:
|
|||
dropdown.setCurrentText(normalized)
|
||||
dropdown.blockSignals(False)
|
||||
|
||||
# set_show_advanced_parameters: function definition for ScanSlicer behavior.
|
||||
def set_show_advanced_parameters(self, checked):
|
||||
self.show_advanced_params = bool(checked)
|
||||
self.update_parameter_visibility()
|
||||
|
||||
# sync_param_controls_from_model: function definition for ScanSlicer behavior.
|
||||
def sync_param_controls_from_model(self):
|
||||
self.updating_param_controls = True
|
||||
for name, p in self.params.items():
|
||||
|
|
@ -33,7 +29,6 @@ class ScanSlicerParamsMixin:
|
|||
self.updating_param_controls = False
|
||||
self.update_parameter_visibility()
|
||||
|
||||
# update_parameter_visibility: function definition for ScanSlicer behavior.
|
||||
def update_parameter_visibility(self):
|
||||
pipeline = self.params.detect_pipeline.get()
|
||||
method = self.params.bw_method.get()
|
||||
|
|
@ -59,7 +54,6 @@ class ScanSlicerParamsMixin:
|
|||
is_visible = False
|
||||
block.setVisible(is_visible)
|
||||
|
||||
# load_default_parameters: function definition for ScanSlicer behavior.
|
||||
def load_default_parameters(self):
|
||||
mode = self.params.load_defaults()
|
||||
self.sync_param_controls_from_model()
|
||||
|
|
@ -69,7 +63,6 @@ class ScanSlicerParamsMixin:
|
|||
else:
|
||||
self.update_statusbar("Loaded factory defaults (no saved defaults found).")
|
||||
|
||||
# save_default_parameters: function definition for ScanSlicer behavior.
|
||||
def save_default_parameters(self):
|
||||
try:
|
||||
path = self.params.save_user_defaults()
|
||||
|
|
@ -80,7 +73,6 @@ class ScanSlicerParamsMixin:
|
|||
|
||||
self.update_statusbar("Saved defaults to " + path)
|
||||
|
||||
# autotune_parameters: function definition for ScanSlicer behavior.
|
||||
def autotune_parameters(self):
|
||||
if self.processing_busy:
|
||||
return
|
||||
|
|
@ -89,7 +81,6 @@ class ScanSlicerParamsMixin:
|
|||
return
|
||||
self._start_autotune_worker()
|
||||
|
||||
# clear_autotune_history: function definition for ScanSlicer behavior.
|
||||
def clear_autotune_history(self):
|
||||
if self.processing_busy:
|
||||
return
|
||||
|
|
@ -125,7 +116,6 @@ class ScanSlicerParamsMixin:
|
|||
)
|
||||
self.update_statusbar("Auto-tune history cleared.")
|
||||
|
||||
# set_preset_dropdown_custom: function definition for ScanSlicer behavior.
|
||||
def set_preset_dropdown_custom(self):
|
||||
if self.preset_dropdown.currentText() == "Custom":
|
||||
return
|
||||
|
|
@ -133,7 +123,6 @@ class ScanSlicerParamsMixin:
|
|||
self.preset_dropdown.setCurrentText("Custom")
|
||||
self.preset_dropdown.blockSignals(False)
|
||||
|
||||
# refresh_preset_dropdown: function definition for ScanSlicer behavior.
|
||||
def refresh_preset_dropdown(self, selected_name=None):
|
||||
if selected_name is None:
|
||||
selected_name = self.preset_dropdown.currentText()
|
||||
|
|
@ -149,7 +138,6 @@ class ScanSlicerParamsMixin:
|
|||
self.preset_dropdown.setCurrentText("Custom")
|
||||
self.preset_dropdown.blockSignals(False)
|
||||
|
||||
# save_current_as_preset: function definition for ScanSlicer behavior.
|
||||
def save_current_as_preset(self):
|
||||
if self.processing_busy:
|
||||
return
|
||||
|
|
@ -184,7 +172,6 @@ class ScanSlicerParamsMixin:
|
|||
self.refresh_preset_dropdown(selected_name=saved_name)
|
||||
self.update_statusbar("Saved preset: " + saved_name)
|
||||
|
||||
# delete_current_preset: function definition for ScanSlicer behavior.
|
||||
def delete_current_preset(self):
|
||||
if self.processing_busy:
|
||||
return
|
||||
|
|
@ -216,7 +203,6 @@ class ScanSlicerParamsMixin:
|
|||
self.refresh_preset_dropdown(selected_name="Custom")
|
||||
self.update_statusbar("Deleted preset: " + deleted_name)
|
||||
|
||||
# apply_parameter_preset: function definition for ScanSlicer behavior.
|
||||
def apply_parameter_preset(self, preset_name):
|
||||
if preset_name == "Custom":
|
||||
return
|
||||
|
|
|
|||
|
|
@ -6,13 +6,11 @@ from time import monotonic
|
|||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
|
||||
# ScanSlicerStatusMixin: class definition for ScanSlicer behavior.
|
||||
class ScanSlicerStatusMixin:
|
||||
STATUS_EVENT_PUMP_INTERVAL_SEC = 0.05
|
||||
STATUS_LOG_FLUSH_INTERVAL_SEC = 0.5
|
||||
STATUS_LOG_BATCH_SIZE = 24
|
||||
|
||||
# update_status_filename: function definition for ScanSlicer behavior.
|
||||
def update_status_filename(self, image_path=None):
|
||||
label = getattr(self, "status_filename_label", None)
|
||||
if label is None:
|
||||
|
|
@ -22,7 +20,6 @@ class ScanSlicerStatusMixin:
|
|||
else:
|
||||
label.setText("No file")
|
||||
|
||||
# resolve_status_log_path: function definition for ScanSlicer behavior.
|
||||
def resolve_status_log_path(self):
|
||||
override = os.environ.get("SCANSLICER_STATUS_LOG_PATH")
|
||||
if override:
|
||||
|
|
@ -36,7 +33,6 @@ class ScanSlicerStatusMixin:
|
|||
config_home = os.path.join(os.path.expanduser("~"), ".config")
|
||||
return os.path.join(config_home, "scanslicer", "status.log")
|
||||
|
||||
# initialize_status_log: function definition for ScanSlicer behavior.
|
||||
def initialize_status_log(self):
|
||||
self._status_log_buffer = []
|
||||
self._status_log_last_flush_ts = monotonic()
|
||||
|
|
@ -56,7 +52,6 @@ class ScanSlicerStatusMixin:
|
|||
self.status_logging_enabled = False
|
||||
self._status_log_file = None
|
||||
|
||||
# _flush_status_log_if_due: function definition for ScanSlicer behavior.
|
||||
def _flush_status_log_if_due(self, force=False):
|
||||
if not self.status_logging_enabled or not self.status_log_path:
|
||||
return
|
||||
|
|
@ -92,7 +87,6 @@ class ScanSlicerStatusMixin:
|
|||
self._status_log_buffer = []
|
||||
self._status_log_last_flush_ts = now
|
||||
|
||||
# _close_status_log: function definition for ScanSlicer behavior.
|
||||
def _close_status_log(self):
|
||||
self._flush_status_log_if_due(force=True)
|
||||
fh = getattr(self, "_status_log_file", None)
|
||||
|
|
@ -104,7 +98,6 @@ class ScanSlicerStatusMixin:
|
|||
pass
|
||||
self._status_log_file = None
|
||||
|
||||
# append_status_log: function definition for ScanSlicer behavior.
|
||||
def append_status_log(self, text):
|
||||
if not self.status_logging_enabled or not self.status_log_path:
|
||||
return
|
||||
|
|
@ -119,7 +112,6 @@ class ScanSlicerStatusMixin:
|
|||
buffer.append(line)
|
||||
self._flush_status_log_if_due(force=False)
|
||||
|
||||
# log_detection_config: function definition for ScanSlicer behavior.
|
||||
def log_detection_config(self):
|
||||
preset_name = self.preset_dropdown.currentText()
|
||||
values = self.params.to_dict()
|
||||
|
|
@ -127,7 +119,6 @@ class ScanSlicerStatusMixin:
|
|||
self.append_status_log(f"Detection config: preset={preset_name}")
|
||||
self.append_status_log(f"Detection params: {values_text}")
|
||||
|
||||
# _pump_ui_events_if_due: function definition for ScanSlicer behavior.
|
||||
def _pump_ui_events_if_due(self, force=False):
|
||||
now = monotonic()
|
||||
last_pump = getattr(self, "_status_last_event_pump_ts", 0.0)
|
||||
|
|
@ -135,13 +126,11 @@ class ScanSlicerStatusMixin:
|
|||
QApplication.processEvents()
|
||||
self._status_last_event_pump_ts = now
|
||||
|
||||
# update_statusbar: function definition for ScanSlicer behavior.
|
||||
def update_statusbar(self, text):
|
||||
self.status_message_label.setText(text)
|
||||
self.append_status_log(text)
|
||||
self._pump_ui_events_if_due()
|
||||
|
||||
# set_processing_busy: function definition for ScanSlicer behavior.
|
||||
def set_processing_busy(self, busy, status_text=None):
|
||||
busy = bool(busy)
|
||||
if self.processing_busy == busy:
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import os
|
|||
import sys
|
||||
|
||||
|
||||
# parse_args: function definition for ScanSlicer behavior.
|
||||
def parse_args(argv=None):
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="scanslicer",
|
||||
|
|
@ -23,7 +22,6 @@ def parse_args(argv=None):
|
|||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
# _detect_backend_from_qpa_env: function definition for ScanSlicer behavior.
|
||||
def _detect_backend_from_qpa_env():
|
||||
qpa_value = os.environ.get("QT_QPA_PLATFORM", "").strip().lower()
|
||||
if not qpa_value:
|
||||
|
|
@ -38,7 +36,6 @@ def _detect_backend_from_qpa_env():
|
|||
return None
|
||||
|
||||
|
||||
# resolve_runtime_backend: function definition for ScanSlicer behavior.
|
||||
def resolve_runtime_backend(backend):
|
||||
if backend in ("wayland", "x11"):
|
||||
return backend
|
||||
|
|
@ -65,7 +62,6 @@ def resolve_runtime_backend(backend):
|
|||
return "wayland"
|
||||
|
||||
|
||||
# configure_qt_backend: function definition for ScanSlicer behavior.
|
||||
def configure_qt_backend(backend):
|
||||
if backend == "wayland":
|
||||
os.environ["QT_QPA_PLATFORM"] = "wayland"
|
||||
|
|
@ -99,7 +95,6 @@ def configure_qt_backend(backend):
|
|||
os.environ.setdefault("QT_QPA_PLATFORMTHEME", "gtk3")
|
||||
|
||||
|
||||
# validate_wayland_environment: function definition for ScanSlicer behavior.
|
||||
def validate_wayland_environment():
|
||||
runtime_dir = os.environ.get("XDG_RUNTIME_DIR", "").strip()
|
||||
display_name = os.environ.get("WAYLAND_DISPLAY", "").strip()
|
||||
|
|
@ -116,7 +111,6 @@ def validate_wayland_environment():
|
|||
)
|
||||
|
||||
|
||||
# validate_x11_environment: function definition for ScanSlicer behavior.
|
||||
def validate_x11_environment():
|
||||
display_name = os.environ.get("DISPLAY", "").strip()
|
||||
if not display_name:
|
||||
|
|
@ -125,7 +119,6 @@ def validate_x11_environment():
|
|||
)
|
||||
|
||||
|
||||
# main: function definition for ScanSlicer behavior.
|
||||
def main(argv=None):
|
||||
args = parse_args(sys.argv[1:] if argv is None else argv)
|
||||
runtime_backend = None
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ from .controller import ScanSlicerControllerMixin
|
|||
from .ui_builders import ScanSlicerUiBuilderMixin
|
||||
|
||||
|
||||
# _unwrap_qvariant: function definition for ScanSlicer behavior.
|
||||
def _unwrap_qvariant(value):
|
||||
unwrapped = value
|
||||
for _ in range(4):
|
||||
|
|
@ -45,7 +44,6 @@ def _unwrap_qvariant(value):
|
|||
return unwrapped
|
||||
|
||||
|
||||
# _read_portal_dark_preference: function definition for ScanSlicer behavior.
|
||||
def _read_portal_dark_preference():
|
||||
try:
|
||||
from PySide6.QtDBus import QDBusConnection, QDBusInterface, QDBusMessage
|
||||
|
|
@ -94,7 +92,6 @@ def _read_portal_dark_preference():
|
|||
return None
|
||||
|
||||
|
||||
# apply_dark_theme_if_requested: function definition for ScanSlicer behavior.
|
||||
def apply_dark_theme_if_requested(app):
|
||||
theme_value = os.environ.get("GTK_THEME", "").lower()
|
||||
dark_theme_requested = "dark" in theme_value
|
||||
|
|
@ -131,7 +128,6 @@ def apply_dark_theme_if_requested(app):
|
|||
app.setPalette(palette)
|
||||
|
||||
|
||||
# apply_global_text_size_offset: function definition for ScanSlicer behavior.
|
||||
def apply_global_text_size_offset(app, pixel_offset=2):
|
||||
font = app.font()
|
||||
info = QFontInfo(font)
|
||||
|
|
@ -148,7 +144,6 @@ def apply_global_text_size_offset(app, pixel_offset=2):
|
|||
app.setFont(font)
|
||||
|
||||
|
||||
# resolve_app_icon: function definition for ScanSlicer behavior.
|
||||
def resolve_app_icon():
|
||||
icon = QIcon.fromTheme("scanslicer")
|
||||
if not icon.isNull():
|
||||
|
|
@ -175,7 +170,6 @@ def resolve_app_icon():
|
|||
return QIcon()
|
||||
|
||||
|
||||
# QtScanSlicer: class definition for ScanSlicer behavior.
|
||||
class QtScanSlicer(QMainWindow, ScanSlicerUiBuilderMixin, ScanSlicerControllerMixin):
|
||||
CONTROL_PANEL_WIDTH = 370
|
||||
STEPPER_WIDTH = 75
|
||||
|
|
@ -325,7 +319,6 @@ class QtScanSlicer(QMainWindow, ScanSlicerUiBuilderMixin, ScanSlicerControllerMi
|
|||
"PSJNOCAxMmg4Ii8+PC9zdmc+"
|
||||
)
|
||||
|
||||
# __init__: function definition for ScanSlicer behavior.
|
||||
def __init__(self, startup_dir=None):
|
||||
super().__init__()
|
||||
|
||||
|
|
@ -402,7 +395,6 @@ class QtScanSlicer(QMainWindow, ScanSlicerUiBuilderMixin, ScanSlicerControllerMi
|
|||
if startup_dir:
|
||||
self.open_directory(startup_dir)
|
||||
|
||||
# _build_header_controls: function definition for ScanSlicer behavior.
|
||||
def _build_header_controls(self, parent):
|
||||
header = QWidget(parent)
|
||||
header_layout = QHBoxLayout(header)
|
||||
|
|
@ -436,14 +428,12 @@ class QtScanSlicer(QMainWindow, ScanSlicerUiBuilderMixin, ScanSlicerControllerMi
|
|||
header_layout.addWidget(self.button_quit)
|
||||
return header
|
||||
|
||||
# toggle_maximize_restore: function definition for ScanSlicer behavior.
|
||||
def toggle_maximize_restore(self):
|
||||
if self.isMaximized():
|
||||
self.showNormal()
|
||||
return
|
||||
self.showMaximized()
|
||||
|
||||
# closeEvent: function definition for ScanSlicer behavior.
|
||||
def closeEvent(self, event):
|
||||
self._is_closing = True
|
||||
if hasattr(self, "autoslicer"):
|
||||
|
|
@ -452,7 +442,6 @@ class QtScanSlicer(QMainWindow, ScanSlicerUiBuilderMixin, ScanSlicerControllerMi
|
|||
self._close_status_log()
|
||||
super().closeEvent(event)
|
||||
|
||||
# run_app: function definition for ScanSlicer behavior.
|
||||
def run_app(startup_dir=None):
|
||||
app = QApplication.instance()
|
||||
if app is None:
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@ import json
|
|||
import os
|
||||
|
||||
|
||||
# Parameter: class definition for ScanSlicer behavior.
|
||||
class Parameter:
|
||||
# __init__: function definition for ScanSlicer behavior.
|
||||
def __init__(self, default, min, max, step, label, label_secondary=""):
|
||||
self.default = default
|
||||
self.min = min
|
||||
|
|
@ -14,11 +12,9 @@ class Parameter:
|
|||
self.label_secondary = label_secondary
|
||||
self.value = default
|
||||
|
||||
# get: function definition for ScanSlicer behavior.
|
||||
def get(self):
|
||||
return self.value
|
||||
|
||||
# set: function definition for ScanSlicer behavior.
|
||||
def set(self, value):
|
||||
int_value = int(value)
|
||||
if int_value < self.min:
|
||||
|
|
@ -27,12 +23,10 @@ class Parameter:
|
|||
int_value = self.max
|
||||
self.value = int_value
|
||||
|
||||
# reset: function definition for ScanSlicer behavior.
|
||||
def reset(self):
|
||||
self.value = self.default
|
||||
|
||||
|
||||
# AutoslicerParams: class definition for ScanSlicer behavior.
|
||||
class AutoslicerParams:
|
||||
SPECS = [
|
||||
("detect_pipeline", 0, 0, 1, 1, "Edge Detector", "(0=Threshold, 1=Canny)"),
|
||||
|
|
@ -59,7 +53,6 @@ class AutoslicerParams:
|
|||
]
|
||||
PRESETS = {}
|
||||
|
||||
# __init__: function definition for ScanSlicer behavior.
|
||||
def __init__(self):
|
||||
for spec in self.SPECS:
|
||||
if len(spec) == 6:
|
||||
|
|
@ -78,13 +71,11 @@ class AutoslicerParams:
|
|||
self.user_presets = self.load_user_presets()
|
||||
self.load_user_defaults()
|
||||
|
||||
# items: function definition for ScanSlicer behavior.
|
||||
def items(self):
|
||||
for name, *_ in self.SPECS:
|
||||
yield name, getattr(self, name)
|
||||
|
||||
@classmethod
|
||||
# defaults_path: function definition for ScanSlicer behavior.
|
||||
def defaults_path(cls):
|
||||
override = os.environ.get("SCANSLICER_DEFAULTS_PATH")
|
||||
if override:
|
||||
|
|
@ -99,7 +90,6 @@ class AutoslicerParams:
|
|||
return os.path.join(config_home, "scanslicer", "defaults.json")
|
||||
|
||||
@classmethod
|
||||
# presets_path: function definition for ScanSlicer behavior.
|
||||
def presets_path(cls):
|
||||
override = os.environ.get("SCANSLICER_PRESETS_PATH")
|
||||
if override:
|
||||
|
|
@ -113,11 +103,9 @@ class AutoslicerParams:
|
|||
config_home = os.path.join(os.path.expanduser("~"), ".config")
|
||||
return os.path.join(config_home, "scanslicer", "presets.json")
|
||||
|
||||
# to_dict: function definition for ScanSlicer behavior.
|
||||
def to_dict(self):
|
||||
return {name: parameter.get() for name, parameter in self.items()}
|
||||
|
||||
# apply_values: function definition for ScanSlicer behavior.
|
||||
def apply_values(self, values):
|
||||
if not isinstance(values, dict):
|
||||
return
|
||||
|
|
@ -141,12 +129,10 @@ class AutoslicerParams:
|
|||
if self.canny_low.get() > self.canny_high.get():
|
||||
self.canny_high.set(self.canny_low.get())
|
||||
|
||||
# reset_to_factory_defaults: function definition for ScanSlicer behavior.
|
||||
def reset_to_factory_defaults(self):
|
||||
for _, parameter in self.items():
|
||||
parameter.reset()
|
||||
|
||||
# load_user_defaults: function definition for ScanSlicer behavior.
|
||||
def load_user_defaults(self):
|
||||
path = self.defaults_path()
|
||||
if not os.path.isfile(path):
|
||||
|
|
@ -164,7 +150,6 @@ class AutoslicerParams:
|
|||
self.apply_values(loaded)
|
||||
return True
|
||||
|
||||
# load_defaults: function definition for ScanSlicer behavior.
|
||||
def load_defaults(self):
|
||||
if self.load_user_defaults():
|
||||
return "saved"
|
||||
|
|
@ -172,7 +157,6 @@ class AutoslicerParams:
|
|||
self.reset_to_factory_defaults()
|
||||
return "factory"
|
||||
|
||||
# save_user_defaults: function definition for ScanSlicer behavior.
|
||||
def save_user_defaults(self):
|
||||
path = self.defaults_path()
|
||||
directory = os.path.dirname(path)
|
||||
|
|
@ -181,7 +165,6 @@ class AutoslicerParams:
|
|||
json.dump(self.to_dict(), fh, indent=2, sort_keys=True)
|
||||
return path
|
||||
|
||||
# load_user_presets: function definition for ScanSlicer behavior.
|
||||
def load_user_presets(self):
|
||||
path = self.presets_path()
|
||||
if not os.path.isfile(path):
|
||||
|
|
@ -208,7 +191,6 @@ class AutoslicerParams:
|
|||
cleaned[normalized_name] = values
|
||||
return cleaned
|
||||
|
||||
# save_user_presets: function definition for ScanSlicer behavior.
|
||||
def save_user_presets(self):
|
||||
path = self.presets_path()
|
||||
directory = os.path.dirname(path)
|
||||
|
|
@ -217,13 +199,11 @@ class AutoslicerParams:
|
|||
json.dump(self.user_presets, fh, indent=2, sort_keys=True)
|
||||
return path
|
||||
|
||||
# preset_names: function definition for ScanSlicer behavior.
|
||||
def preset_names(self):
|
||||
builtin_names = list(self.PRESETS.keys())
|
||||
user_names = sorted(self.user_presets.keys(), key=str.lower)
|
||||
return builtin_names + user_names
|
||||
|
||||
# save_named_preset: function definition for ScanSlicer behavior.
|
||||
def save_named_preset(self, preset_name):
|
||||
normalized_name = str(preset_name).strip()
|
||||
if len(normalized_name) == 0:
|
||||
|
|
@ -237,7 +217,6 @@ class AutoslicerParams:
|
|||
self.save_user_presets()
|
||||
return normalized_name
|
||||
|
||||
# delete_named_preset: function definition for ScanSlicer behavior.
|
||||
def delete_named_preset(self, preset_name):
|
||||
normalized_name = str(preset_name).strip()
|
||||
if len(normalized_name) == 0:
|
||||
|
|
@ -253,7 +232,6 @@ class AutoslicerParams:
|
|||
self.save_user_presets()
|
||||
return normalized_name
|
||||
|
||||
# apply_preset: function definition for ScanSlicer behavior.
|
||||
def apply_preset(self, preset_name):
|
||||
values = self.PRESETS.get(preset_name)
|
||||
if values is None:
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@ import numpy as np
|
|||
import cv2
|
||||
|
||||
|
||||
# PhotoSlice: class definition for ScanSlicer behavior.
|
||||
class PhotoSlice:
|
||||
# __init__: function definition for ScanSlicer behavior.
|
||||
def __init__(self, bbox=None):
|
||||
self._bbox = None
|
||||
self._rect_cache = None
|
||||
|
|
@ -23,18 +21,15 @@ class PhotoSlice:
|
|||
self.swapped = False
|
||||
|
||||
@property
|
||||
# bbox: function definition for ScanSlicer behavior.
|
||||
def bbox(self):
|
||||
return self._bbox
|
||||
|
||||
@bbox.setter
|
||||
# bbox: function definition for ScanSlicer behavior.
|
||||
def bbox(self, value):
|
||||
self._bbox = np.array(value, dtype=np.float64).reshape(4, 2)
|
||||
self._rect_cache = None
|
||||
self._rect_cache_tol = None
|
||||
|
||||
# normalize_top_left: function definition for ScanSlicer behavior.
|
||||
def normalize_top_left(self):
|
||||
# Assume the top-left point of the quad is the image upper-left corner.
|
||||
top_left_index = np.lexsort((self.bbox[:, 0], self.bbox[:, 1]))[0]
|
||||
|
|
@ -44,7 +39,6 @@ class PhotoSlice:
|
|||
# the most horizontal "top" edge.
|
||||
reversed_winding = np.array([ordered[0], ordered[3], ordered[2], ordered[1]])
|
||||
|
||||
# horizontal_score: function definition for ScanSlicer behavior.
|
||||
def horizontal_score(points):
|
||||
edge = points[1] - points[0]
|
||||
return (abs(edge[0]) - abs(edge[1]), edge[0])
|
||||
|
|
@ -59,7 +53,6 @@ class PhotoSlice:
|
|||
|
||||
self.bbox = ordered
|
||||
|
||||
# swap_left_right: function definition for ScanSlicer behavior.
|
||||
def swap_left_right(self):
|
||||
# Mirror left/right: swap the two top corners and the two bottom
|
||||
# corners, keeping the top (edge 0->1) and bottom edges in place.
|
||||
|
|
@ -69,14 +62,12 @@ class PhotoSlice:
|
|||
self._rect_cache = None
|
||||
self._rect_cache_tol = None
|
||||
|
||||
# toggle_locked: function definition for ScanSlicer behavior.
|
||||
def toggle_locked(self, locked=None):
|
||||
if locked is not None:
|
||||
self.locked = locked
|
||||
else:
|
||||
self.locked = not self.locked
|
||||
|
||||
# update_corner: function definition for ScanSlicer behavior.
|
||||
def update_corner(self, ci, x, y):
|
||||
self._bbox[ci][0] = x
|
||||
self._bbox[ci][1] = y
|
||||
|
|
@ -85,7 +76,6 @@ class PhotoSlice:
|
|||
# Freeform corner edits exit side-handle mode.
|
||||
self.side_handles_enabled = False
|
||||
|
||||
# is_perfect_rectangle: function definition for ScanSlicer behavior.
|
||||
def is_perfect_rectangle(self, tol=1e-3):
|
||||
if self._rect_cache is not None and self._rect_cache_tol == tol:
|
||||
return self._rect_cache
|
||||
|
|
@ -121,7 +111,6 @@ class PhotoSlice:
|
|||
self._rect_cache_tol = tol
|
||||
return True
|
||||
|
||||
# squarize: function definition for ScanSlicer behavior.
|
||||
def squarize(self):
|
||||
points = np.array(self.bbox, dtype=np.float64).reshape(4, 2)
|
||||
center = np.mean(points, axis=0)
|
||||
|
|
@ -173,7 +162,6 @@ class PhotoSlice:
|
|||
self.normalize_top_left()
|
||||
self.side_handles_enabled = True
|
||||
|
||||
# reset_rotation: function definition for ScanSlicer behavior.
|
||||
def reset_rotation(self):
|
||||
points = np.array(self.bbox, dtype=np.float64).reshape(4, 2)
|
||||
center = np.mean(points, axis=0)
|
||||
|
|
@ -201,7 +189,6 @@ class PhotoSlice:
|
|||
self.side_handles_enabled = True
|
||||
|
||||
|
||||
# polys_iou: function definition for ScanSlicer behavior.
|
||||
def polys_iou(poly1, poly2):
|
||||
a = np.array(poly1, dtype=np.float32).reshape(-1, 2)
|
||||
b = np.array(poly2, dtype=np.float32).reshape(-1, 2)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ from .ui_builders_preview import ScanSlicerUiPreviewMixin
|
|||
from .ui_builders_shared import ScrollbarWheelOnlyScrollArea, icon_from_svg_data_url
|
||||
|
||||
|
||||
# ScanSlicerUiBuilderMixin: class definition for ScanSlicer behavior.
|
||||
class ScanSlicerUiBuilderMixin(ScanSlicerUiControlsMixin, ScanSlicerUiPreviewMixin):
|
||||
"""Composed UI builder mixin split by panel responsibility."""
|
||||
|
||||
|
|
|
|||
|
|
@ -13,9 +13,7 @@ from PySide6.QtWidgets import (
|
|||
from .ui_builders_shared import ScanSlicerUiSharedMixin, ScrollbarWheelOnlyScrollArea, icon_from_svg_data_url
|
||||
|
||||
|
||||
# ScanSlicerUiControlsMixin: class definition for ScanSlicer behavior.
|
||||
class ScanSlicerUiControlsMixin(ScanSlicerUiSharedMixin):
|
||||
# _add_directory_section: function definition for ScanSlicer behavior.
|
||||
def _add_directory_section(self, controls_layout, section_row):
|
||||
controls_layout.addWidget(self._build_section_title("Directory"), section_row, 0, 1, 2)
|
||||
section_row += 1
|
||||
|
|
@ -45,7 +43,6 @@ class ScanSlicerUiControlsMixin(ScanSlicerUiSharedMixin):
|
|||
controls_layout.addWidget(directory_group, section_row, 0, 1, 2)
|
||||
return section_row + 1
|
||||
|
||||
# _add_images_section: function definition for ScanSlicer behavior.
|
||||
def _add_images_section(self, controls_layout, section_row):
|
||||
controls_layout.addWidget(self._build_section_title("Images"), section_row, 0, 1, 2)
|
||||
section_row += 1
|
||||
|
|
@ -142,7 +139,6 @@ class ScanSlicerUiControlsMixin(ScanSlicerUiSharedMixin):
|
|||
controls_layout.addWidget(image_group, section_row, 0, 1, 2)
|
||||
return section_row + 1
|
||||
|
||||
# _add_presets_section: function definition for ScanSlicer behavior.
|
||||
def _add_presets_section(self, controls_layout, section_row):
|
||||
controls_layout.addWidget(self._build_section_title("Presets"), section_row, 0, 1, 2)
|
||||
section_row += 1
|
||||
|
|
@ -194,7 +190,6 @@ class ScanSlicerUiControlsMixin(ScanSlicerUiSharedMixin):
|
|||
controls_layout.addWidget(presets_group, section_row, 0, 1, 2)
|
||||
return section_row + 1
|
||||
|
||||
# _add_parameters_section: function definition for ScanSlicer behavior.
|
||||
def _add_parameters_section(self, controls_layout, section_row):
|
||||
controls_layout.addWidget(self._build_section_title("Parameters"), section_row, 0, 1, 2)
|
||||
section_row += 1
|
||||
|
|
@ -226,7 +221,6 @@ class ScanSlicerUiControlsMixin(ScanSlicerUiSharedMixin):
|
|||
controls_layout.addWidget(parameters_group, section_row, 0, 1, 2)
|
||||
return section_row + 1
|
||||
|
||||
# _build_controls_panel: function definition for ScanSlicer behavior.
|
||||
def _build_controls_panel(self, root):
|
||||
controls = QWidget()
|
||||
controls_layout = QGridLayout(controls)
|
||||
|
|
|
|||
|
|
@ -5,9 +5,7 @@ from .canvas import QtSlicingCanvas
|
|||
from .ui_builders_shared import ScanSlicerUiSharedMixin, icon_from_svg_data_url
|
||||
|
||||
|
||||
# ScanSlicerUiPreviewMixin: class definition for ScanSlicer behavior.
|
||||
class ScanSlicerUiPreviewMixin(ScanSlicerUiSharedMixin):
|
||||
# _create_toolbar_button: function definition for ScanSlicer behavior.
|
||||
def _create_toolbar_button(
|
||||
self,
|
||||
parent,
|
||||
|
|
@ -35,7 +33,6 @@ class ScanSlicerUiPreviewMixin(ScanSlicerUiSharedMixin):
|
|||
button.clicked.connect(callback)
|
||||
return button
|
||||
|
||||
# _build_preview_panel: function definition for ScanSlicer behavior.
|
||||
def _build_preview_panel(self, root):
|
||||
preview_panel = QWidget(root)
|
||||
preview_layout = QGridLayout(preview_panel)
|
||||
|
|
@ -171,7 +168,6 @@ class ScanSlicerUiPreviewMixin(ScanSlicerUiSharedMixin):
|
|||
preview_layout.setRowStretch(1, 1)
|
||||
return preview_panel
|
||||
|
||||
# _build_status_bar: function definition for ScanSlicer behavior.
|
||||
def _build_status_bar(self):
|
||||
status_bar = self.statusBar()
|
||||
status_bar.setMinimumHeight(30)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ from PySide6.QtWidgets import (
|
|||
)
|
||||
|
||||
|
||||
# _icon_color_from_palette: function definition for ScanSlicer behavior.
|
||||
def _icon_color_from_palette(palette):
|
||||
if palette is None:
|
||||
return "#f2f2f2"
|
||||
|
|
@ -37,7 +36,6 @@ def _icon_color_from_palette(palette):
|
|||
return "#1f1f1f" if luma >= 0.5 else "#f2f2f2"
|
||||
|
||||
|
||||
# icon_from_svg_data_url: function definition for ScanSlicer behavior.
|
||||
def icon_from_svg_data_url(data_url, size=24, color_hex=None, palette=None):
|
||||
prefix = "data:image/svg+xml;base64,"
|
||||
if not data_url.startswith(prefix):
|
||||
|
|
@ -73,9 +71,7 @@ def icon_from_svg_data_url(data_url, size=24, color_hex=None, palette=None):
|
|||
return QIcon(pixmap)
|
||||
|
||||
|
||||
# ScrollbarWheelOnlyScrollArea: class definition for ScanSlicer behavior.
|
||||
class ScrollbarWheelOnlyScrollArea(QScrollArea):
|
||||
# wheelEvent: function definition for ScanSlicer behavior.
|
||||
def wheelEvent(self, event):
|
||||
scrollbar = self.verticalScrollBar()
|
||||
if scrollbar is None or not scrollbar.isVisible():
|
||||
|
|
@ -91,9 +87,7 @@ class ScrollbarWheelOnlyScrollArea(QScrollArea):
|
|||
super().wheelEvent(event)
|
||||
|
||||
|
||||
# ScanSlicerUiSharedMixin: class definition for ScanSlicer behavior.
|
||||
class ScanSlicerUiSharedMixin:
|
||||
# _build_section_title: function definition for ScanSlicer behavior.
|
||||
def _build_section_title(self, text):
|
||||
label = QLabel(text)
|
||||
title_font = QFont(label.font())
|
||||
|
|
@ -106,7 +100,6 @@ class ScanSlicerUiSharedMixin:
|
|||
)
|
||||
return label
|
||||
|
||||
# _add_vertical_spacer: function definition for ScanSlicer behavior.
|
||||
def _add_vertical_spacer(self, target_layout, row_index):
|
||||
target_layout.addItem(
|
||||
QSpacerItem(
|
||||
|
|
@ -121,7 +114,6 @@ class ScanSlicerUiSharedMixin:
|
|||
2,
|
||||
)
|
||||
|
||||
# _new_group_layout: function definition for ScanSlicer behavior.
|
||||
def _new_group_layout(self):
|
||||
group = QGroupBox()
|
||||
group_layout = QGridLayout(group)
|
||||
|
|
@ -133,21 +125,17 @@ class ScanSlicerUiSharedMixin:
|
|||
)
|
||||
return group, group_layout
|
||||
|
||||
# _add_full_width_widget: function definition for ScanSlicer behavior.
|
||||
def _add_full_width_widget(self, target_layout, row_index, widget):
|
||||
target_layout.addWidget(widget, row_index, 0, 1, 2)
|
||||
return row_index + 1
|
||||
|
||||
# _add_spacer_row: function definition for ScanSlicer behavior.
|
||||
def _add_spacer_row(self, target_layout, row_index):
|
||||
self._add_vertical_spacer(target_layout, row_index)
|
||||
return row_index + 1
|
||||
|
||||
# _add_label_row: function definition for ScanSlicer behavior.
|
||||
def _add_label_row(self, target_layout, row_index, text):
|
||||
return self._add_full_width_widget(target_layout, row_index, QLabel(text))
|
||||
|
||||
# _create_panel_button: function definition for ScanSlicer behavior.
|
||||
def _create_panel_button(self, text, callback, tooltip=None):
|
||||
button = QPushButton(text)
|
||||
if tooltip:
|
||||
|
|
@ -155,7 +143,6 @@ class ScanSlicerUiSharedMixin:
|
|||
button.clicked.connect(callback)
|
||||
return button
|
||||
|
||||
# _build_parameter_block: function definition for ScanSlicer behavior.
|
||||
def _build_parameter_block(self, parent, name, parameter):
|
||||
param_block = QWidget(parent)
|
||||
param_block_layout = QVBoxLayout(param_block)
|
||||
|
|
@ -189,7 +176,6 @@ class ScanSlicerUiSharedMixin:
|
|||
if choice_index >= 0:
|
||||
control.setCurrentIndex(choice_index)
|
||||
|
||||
# on_param_choice_changed: function definition for ScanSlicer behavior.
|
||||
def on_param_choice_changed(index, p=parameter, combo=control, param_name=name):
|
||||
value = combo.itemData(index)
|
||||
p.set(value)
|
||||
|
|
@ -216,7 +202,6 @@ class ScanSlicerUiSharedMixin:
|
|||
value_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
||||
value_label.setMinimumWidth(40)
|
||||
|
||||
# on_param_slider_changed: function definition for ScanSlicer behavior.
|
||||
def on_param_slider_changed(value, p=parameter, slider=control, label=value_label, param_name=name):
|
||||
step = max(1, p.step)
|
||||
snapped = p.min + round((value - p.min) / step) * step
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue