From 2b7dfb330d4ea183035fa5f6db6ca766c088dbbb Mon Sep 17 00:00:00 2001 From: Ethanfel Date: Sun, 19 Apr 2026 12:45:14 +0200 Subject: [PATCH] fix: DB schema missing scan_export column, add threshold filter and N hotkey MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fresh databases were missing scan_export column — broke first export - Threshold slider now filters existing scan results without rescanning - N key toggles hard negative on selected scan regions - All 59 tests passing Co-Authored-By: Claude Opus 4.6 --- core/db.py | 1 + main.py | 33 +++++++++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/core/db.py b/core/db.py index 450f817..57236c9 100644 --- a/core/db.py +++ b/core/db.py @@ -49,6 +49,7 @@ class ProcessedDB: " spread REAL NOT NULL DEFAULT 3.0," " profile TEXT NOT NULL DEFAULT 'default'," " source_path TEXT NOT NULL DEFAULT ''," + " scan_export INTEGER NOT NULL DEFAULT 0," " processed_at TEXT NOT NULL" ")" ) diff --git a/main.py b/main.py index 7eed29b..8e6da6f 100755 --- a/main.py +++ b/main.py @@ -697,12 +697,31 @@ class ScanResultsPanel(QWidget): self._tabs.setTabText(tab_idx, f"{model} ({count})") self.tab_changed.emit() + def filter_by_threshold(self, threshold: float) -> None: + """Show/hide rows based on score threshold across all tabs.""" + for i in range(self._tabs.count()): + table = self._tabs.widget(i) + if not isinstance(table, QTableWidget): + continue + visible = 0 + for row in range(table.rowCount()): + score = float(table.item(row, 2).text()) + hide = score < threshold + table.setRowHidden(row, hide) + if not hide: + visible += 1 + model = self._tabs.tabText(i).rsplit(" (", 1)[0] + self._tabs.setTabText(i, f"{model} ({visible})") + self.regions_edited.emit() + def _get_tab_regions(self, table: QTableWidget, include_disabled: bool = False ) -> list[tuple[float, float, float]]: - """Extract (start, end, score) from a table widget, skipping disabled rows.""" + """Extract (start, end, score) from a table widget, skipping disabled/hidden rows.""" regions = [] for row in range(table.rowCount()): + if table.isRowHidden(row): + continue if not include_disabled: disabled = table.item(row, 0).data(Qt.ItemDataRole.UserRole + 2) if disabled: @@ -714,12 +733,14 @@ class ScanResultsPanel(QWidget): return regions def current_regions_with_orig(self) -> list[tuple[float, float, float, float, float]]: - """Return (start, end, score, orig_start, orig_end) for enabled rows.""" + """Return (start, end, score, orig_start, orig_end) for enabled, visible rows.""" table = self._tabs.currentWidget() if not isinstance(table, QTableWidget): return [] regions = [] for row in range(table.rowCount()): + if table.isRowHidden(row): + continue item0 = table.item(row, 0) disabled = item0.data(Qt.ItemDataRole.UserRole + 2) if disabled: @@ -961,6 +982,8 @@ class ScanResultsPanel(QWidget): self.undo() elif event.key() in (Qt.Key.Key_Delete, Qt.Key.Key_Backspace): self.toggle_disable_selected() + elif event.key() == Qt.Key.Key_N: + self._on_add_negatives() else: super().keyPressEvent(event) @@ -2700,6 +2723,7 @@ class MainWindow(QMainWindow): self._scan_panel.negatives_removed.connect(self._on_scan_negatives_removed) self._scan_panel.tab_changed.connect(self._update_scan_export_count) self._scan_panel.regions_edited.connect(self._on_scan_regions_edited) + self._sld_threshold.valueChanged.connect(self._on_threshold_changed) # Root: horizontal splitter splitter = QSplitter(Qt.Orientation.Horizontal) @@ -2783,6 +2807,7 @@ class MainWindow(QMainWindow): "GToggle cursor lock" "AAutoclip — fit clip count to pause position" "Delete / BackspaceToggle disable on selected scan regions" + "NToggle hard negative on selected scan regions" "Ctrl+ZUndo last scan panel action" "? / F1This help" "
" @@ -3694,6 +3719,10 @@ class MainWindow(QMainWindow): self._update_scan_export_count() self._show_status(f"Removed {len(times)} hard negative(s)") + def _on_threshold_changed(self, value: float) -> None: + """Filter existing scan results by threshold without rescanning.""" + self._scan_panel.filter_by_threshold(value) + def _on_scan_regions_edited(self) -> None: """A scan region was disabled/enabled or resized — refresh timeline and count.""" self._timeline.set_scan_regions(