diff --git a/docs/architecture.md b/docs/architecture.md index 1233bda..7aa6c18 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -95,10 +95,11 @@ Key properties: ## Geometry Model -`PhotoSlice` stores 4-point quadrilateral and flags: +`PhotoSlice` stores a 4-point quadrilateral and flags: - `locked` - `side_handles_enabled` +- `swapped` (persistent left/right mirror state, honored by normalization) It supports: @@ -106,6 +107,7 @@ It supports: - rectangle detection (`is_perfect_rectangle`) - squarize while preserving tilt - reset to axis-aligned orientation +- explicit left/right swap (`swap_left_right`) ## Persistence Model diff --git a/docs/autotune.md b/docs/autotune.md index 785ce5a..012af52 100644 --- a/docs/autotune.md +++ b/docs/autotune.md @@ -113,8 +113,6 @@ Override: - `SCANSLICER_FEEDBACK_PATH` -Legacy override names are still accepted for backward compatibility. - Maximum retained entries: - `500` (latest entries kept) diff --git a/docs/runtime-config.md b/docs/runtime-config.md index 33aa2f3..bee7680 100644 --- a/docs/runtime-config.md +++ b/docs/runtime-config.md @@ -67,8 +67,6 @@ On startup failure, ScanSlicer prints backend-specific hints to stderr. - container: `/work/.scanslicer/status.log` - override: `SCANSLICER_STATUS_LOG_PATH` -Legacy override names are still accepted for backward compatibility. - Status log behavior: - new app launch truncates and starts a fresh session log diff --git a/scanslicer/autoslicer.py b/scanslicer/autoslicer.py index 63c1710..27715b9 100644 --- a/scanslicer/autoslicer.py +++ b/scanslicer/autoslicer.py @@ -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) diff --git a/scanslicer/autoslicer_detection.py b/scanslicer/autoslicer_detection.py index 3c142fb..8910252 100644 --- a/scanslicer/autoslicer_detection.py +++ b/scanslicer/autoslicer_detection.py @@ -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 diff --git a/scanslicer/autoslicer_feedback.py b/scanslicer/autoslicer_feedback.py index d09ec89..8c3b322 100644 --- a/scanslicer/autoslicer_feedback.py +++ b/scanslicer/autoslicer_feedback.py @@ -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.") diff --git a/scanslicer/canvas.py b/scanslicer/canvas.py index 60a736d..5e11bdb 100644 --- a/scanslicer/canvas.py +++ b/scanslicer/canvas.py @@ -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) diff --git a/scanslicer/canvas_interaction.py b/scanslicer/canvas_interaction.py index 5849984..8ff5fac 100644 --- a/scanslicer/canvas_interaction.py +++ b/scanslicer/canvas_interaction.py @@ -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: diff --git a/scanslicer/canvas_render.py b/scanslicer/canvas_render.py index e910f85..0c376af 100644 --- a/scanslicer/canvas_render.py +++ b/scanslicer/canvas_render.py @@ -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) diff --git a/scanslicer/canvas_state.py b/scanslicer/canvas_state.py index cf91dc9..9cab606 100644 --- a/scanslicer/canvas_state.py +++ b/scanslicer/canvas_state.py @@ -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(), diff --git a/scanslicer/controller.py b/scanslicer/controller.py index 8d306d9..96768c2 100644 --- a/scanslicer/controller.py +++ b/scanslicer/controller.py @@ -5,7 +5,6 @@ from .controller_params import ScanSlicerParamsMixin from .controller_status import ScanSlicerStatusMixin -# ScanSlicerControllerMixin: class definition for ScanSlicer behavior. class ScanSlicerControllerMixin( ScanSlicerStatusMixin, ScanSlicerCanvasActionsMixin, diff --git a/scanslicer/controller_canvas_actions.py b/scanslicer/controller_canvas_actions.py index b58a42a..0005504 100644 --- a/scanslicer/controller_canvas_actions.py +++ b/scanslicer/controller_canvas_actions.py @@ -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() diff --git a/scanslicer/controller_detection.py b/scanslicer/controller_detection.py index af63c9c..2266ff4 100644 --- a/scanslicer/controller_detection.py +++ b/scanslicer/controller_detection.py @@ -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 diff --git a/scanslicer/controller_image_io.py b/scanslicer/controller_image_io.py index 2bace76..a6f9311 100644 --- a/scanslicer/controller_image_io.py +++ b/scanslicer/controller_image_io.py @@ -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) diff --git a/scanslicer/controller_params.py b/scanslicer/controller_params.py index 5c7c33f..9b01cd2 100644 --- a/scanslicer/controller_params.py +++ b/scanslicer/controller_params.py @@ -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 diff --git a/scanslicer/controller_status.py b/scanslicer/controller_status.py index 1f2829c..e479d08 100644 --- a/scanslicer/controller_status.py +++ b/scanslicer/controller_status.py @@ -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: diff --git a/scanslicer/entry.py b/scanslicer/entry.py index d658836..4288f9e 100644 --- a/scanslicer/entry.py +++ b/scanslicer/entry.py @@ -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 diff --git a/scanslicer/main.py b/scanslicer/main.py index ae66cf7..e89d991 100644 --- a/scanslicer/main.py +++ b/scanslicer/main.py @@ -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: diff --git a/scanslicer/params.py b/scanslicer/params.py index af8eae5..4bd3a55 100644 --- a/scanslicer/params.py +++ b/scanslicer/params.py @@ -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: diff --git a/scanslicer/slices.py b/scanslicer/slices.py index bd0a365..1ed42e3 100644 --- a/scanslicer/slices.py +++ b/scanslicer/slices.py @@ -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) diff --git a/scanslicer/ui_builders.py b/scanslicer/ui_builders.py index ae60a76..0041d4f 100644 --- a/scanslicer/ui_builders.py +++ b/scanslicer/ui_builders.py @@ -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.""" diff --git a/scanslicer/ui_builders_controls.py b/scanslicer/ui_builders_controls.py index efdeeed..17fc243 100644 --- a/scanslicer/ui_builders_controls.py +++ b/scanslicer/ui_builders_controls.py @@ -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) diff --git a/scanslicer/ui_builders_preview.py b/scanslicer/ui_builders_preview.py index 5f8540c..f89e79f 100644 --- a/scanslicer/ui_builders_preview.py +++ b/scanslicer/ui_builders_preview.py @@ -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) diff --git a/scanslicer/ui_builders_shared.py b/scanslicer/ui_builders_shared.py index 7b538f1..4df0075 100644 --- a/scanslicer/ui_builders_shared.py +++ b/scanslicer/ui_builders_shared.py @@ -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