feat: Implement quality filtering in Identify Panel for enhanced face identification
This commit adds a quality filtering feature to the Identify Panel, allowing users to filter faces based on a quality score (0-100%). The `_get_unidentified_faces()` method has been updated to accept a `min_quality_score` parameter, and the SQL query now includes a WHERE clause for quality filtering. All relevant call sites have been modified to utilize this new feature, improving the user experience during face identification. The unique checkbox default state has also been confirmed to be unchecked, ensuring consistency in the UI behavior.
This commit is contained in:
parent
986fc81005
commit
ddb156520b
166
IDENTIFY_PANEL_FIXES.md
Normal file
166
IDENTIFY_PANEL_FIXES.md
Normal file
@ -0,0 +1,166 @@
|
||||
# Identify Panel Fixes
|
||||
|
||||
**Date:** October 16, 2025
|
||||
**Status:** ✅ Complete
|
||||
|
||||
## Issues Fixed
|
||||
|
||||
### 1. ✅ Unique Checkbox Default State
|
||||
**Issue:** User requested that the "Unique faces only" checkbox be unchecked by default.
|
||||
|
||||
**Status:** Already correct! The checkbox was already unchecked by default.
|
||||
|
||||
**Code Location:** `src/gui/identify_panel.py`, line 76
|
||||
```python
|
||||
self.components['unique_var'] = tk.BooleanVar() # Defaults to False (unchecked)
|
||||
```
|
||||
|
||||
### 2. ✅ Quality Filter Not Working
|
||||
**Issue:** The "Min quality" filter slider wasn't actually filtering faces when loading them from the database.
|
||||
|
||||
**Root Cause:**
|
||||
- The quality filter value was being captured in the GUI (slider with 0-100% range)
|
||||
- However, the `_get_unidentified_faces()` method wasn't using this filter when querying the database
|
||||
- Quality filtering was only happening during navigation (Back/Next buttons), not during initial load
|
||||
|
||||
**Solution:**
|
||||
1. Modified `_get_unidentified_faces()` to accept a `min_quality_score` parameter
|
||||
2. Added SQL WHERE clause to filter by quality score: `AND f.quality_score >= ?`
|
||||
3. Updated all 4 calls to `_get_unidentified_faces()` to pass the quality filter value:
|
||||
- `_start_identification()` - Initial load
|
||||
- `on_unique_change()` - When toggling unique faces filter
|
||||
- `_load_more_faces()` - Loading additional batches
|
||||
- `_apply_date_filters()` - When applying date filters
|
||||
|
||||
**Code Changes:**
|
||||
|
||||
**File:** `src/gui/identify_panel.py`
|
||||
|
||||
**Modified Method Signature (line 519-521):**
|
||||
```python
|
||||
def _get_unidentified_faces(self, batch_size: int, date_from: str = None, date_to: str = None,
|
||||
date_processed_from: str = None, date_processed_to: str = None,
|
||||
min_quality_score: float = 0.0) -> List[Tuple]:
|
||||
```
|
||||
|
||||
**Added SQL Filter (lines 537-540):**
|
||||
```python
|
||||
# Add quality filtering if specified
|
||||
if min_quality_score > 0.0:
|
||||
query += ' AND f.quality_score >= ?'
|
||||
params.append(min_quality_score)
|
||||
```
|
||||
|
||||
**Updated Call Sites:**
|
||||
|
||||
1. **`_start_identification()` (lines 494-501):**
|
||||
```python
|
||||
# Get quality filter
|
||||
min_quality = self.components['quality_filter_var'].get()
|
||||
min_quality_score = min_quality / 100.0
|
||||
|
||||
# Get unidentified faces with quality filter
|
||||
self.current_faces = self._get_unidentified_faces(batch_size, date_from, date_to,
|
||||
date_processed_from, date_processed_to,
|
||||
min_quality_score)
|
||||
```
|
||||
|
||||
2. **`on_unique_change()` (lines 267-274):**
|
||||
```python
|
||||
# Get quality filter
|
||||
min_quality = self.components['quality_filter_var'].get()
|
||||
min_quality_score = min_quality / 100.0
|
||||
|
||||
# Reload faces with current filters
|
||||
self.current_faces = self._get_unidentified_faces(batch_size, date_from, date_to,
|
||||
date_processed_from, date_processed_to,
|
||||
min_quality_score)
|
||||
```
|
||||
|
||||
3. **`_load_more_faces()` (lines 1378-1385):**
|
||||
```python
|
||||
# Get quality filter
|
||||
min_quality = self.components['quality_filter_var'].get()
|
||||
min_quality_score = min_quality / 100.0
|
||||
|
||||
# Get more faces
|
||||
more_faces = self._get_unidentified_faces(DEFAULT_BATCH_SIZE, date_from, date_to,
|
||||
date_processed_from, date_processed_to,
|
||||
min_quality_score)
|
||||
```
|
||||
|
||||
4. **`_apply_date_filters()` (lines 1575-1581):**
|
||||
```python
|
||||
# Quality filter is already extracted above in min_quality
|
||||
min_quality_score = min_quality / 100.0
|
||||
|
||||
# Reload faces with new filters
|
||||
self.current_faces = self._get_unidentified_faces(batch_size, date_from, date_to,
|
||||
date_processed_from, date_processed_to,
|
||||
min_quality_score)
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
**Syntax Check:** ✅ Passed
|
||||
```bash
|
||||
python3 -m py_compile src/gui/identify_panel.py
|
||||
```
|
||||
|
||||
**Linter Check:** ✅ No errors found
|
||||
|
||||
## How Quality Filter Now Works
|
||||
|
||||
1. **User adjusts slider:** Sets quality from 0% to 100% (in 5% increments)
|
||||
2. **User clicks "Start Identification":**
|
||||
- Gets quality value (e.g., 75%)
|
||||
- Converts to 0.0-1.0 scale (e.g., 0.75)
|
||||
- Passes to `_get_unidentified_faces()`
|
||||
- SQL query filters: `WHERE f.quality_score >= 0.75`
|
||||
- Only faces with quality ≥ 75% are loaded
|
||||
3. **Quality filter persists:**
|
||||
- When loading more batches
|
||||
- When toggling unique faces
|
||||
- When applying date filters
|
||||
- When navigating (Back/Next already had quality filtering)
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
### Quality Filter = 0% (default)
|
||||
- Shows all faces regardless of quality
|
||||
- SQL: No quality filter applied
|
||||
|
||||
### Quality Filter = 50%
|
||||
- Shows only faces with quality ≥ 50%
|
||||
- SQL: `WHERE f.quality_score >= 0.5`
|
||||
|
||||
### Quality Filter = 75%
|
||||
- Shows only faces with quality ≥ 75%
|
||||
- SQL: `WHERE f.quality_score >= 0.75`
|
||||
|
||||
### Quality Filter = 100%
|
||||
- Shows only perfect quality faces
|
||||
- SQL: `WHERE f.quality_score >= 1.0`
|
||||
|
||||
## Notes
|
||||
|
||||
- The quality score is stored in the database as a float between 0.0 and 1.0
|
||||
- The GUI displays it as a percentage (0-100%) for user-friendliness
|
||||
- The conversion happens at every call site: `min_quality_score = min_quality / 100.0`
|
||||
- The Back/Next navigation already had quality filtering logic via `_find_next_qualifying_face()` - this continues to work as before
|
||||
|
||||
## Files Modified
|
||||
|
||||
- `src/gui/identify_panel.py` (1 file, ~15 lines changed)
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
- [x] Quality filter parameter added to method signature
|
||||
- [x] SQL WHERE clause added for quality filtering
|
||||
- [x] All 4 call sites updated with quality filter
|
||||
- [x] Syntax validation passed
|
||||
- [x] No linter errors
|
||||
- [x] Unique checkbox already defaults to unchecked
|
||||
- [x] Code follows PEP 8 style guidelines
|
||||
- [x] Changes are backward compatible (min_quality_score defaults to 0.0)
|
||||
|
||||
@ -264,9 +264,14 @@ class IdentifyPanel:
|
||||
except Exception:
|
||||
batch_size = DEFAULT_BATCH_SIZE
|
||||
|
||||
# Get quality filter
|
||||
min_quality = self.components['quality_filter_var'].get()
|
||||
min_quality_score = min_quality / 100.0
|
||||
|
||||
# Reload faces with current filters
|
||||
self.current_faces = self._get_unidentified_faces(batch_size, date_from, date_to,
|
||||
date_processed_from, date_processed_to)
|
||||
date_processed_from, date_processed_to,
|
||||
min_quality_score)
|
||||
|
||||
print(f"✅ Reloaded: {len(self.current_faces)} faces")
|
||||
|
||||
@ -491,9 +496,14 @@ class IdentifyPanel:
|
||||
date_processed_from = self.components['date_processed_from_var'].get().strip() or None
|
||||
date_processed_to = self.components['date_processed_to_var'].get().strip() or None
|
||||
|
||||
# Get unidentified faces (without quality filter - we'll filter in display)
|
||||
# Get quality filter
|
||||
min_quality = self.components['quality_filter_var'].get()
|
||||
min_quality_score = min_quality / 100.0
|
||||
|
||||
# Get unidentified faces with quality filter
|
||||
self.current_faces = self._get_unidentified_faces(batch_size, date_from, date_to,
|
||||
date_processed_from, date_processed_to)
|
||||
date_processed_from, date_processed_to,
|
||||
min_quality_score)
|
||||
|
||||
if not self.current_faces:
|
||||
messagebox.showinfo("No Faces", "🎉 All faces have been identified!")
|
||||
@ -517,8 +527,9 @@ class IdentifyPanel:
|
||||
self.is_active = True
|
||||
|
||||
def _get_unidentified_faces(self, batch_size: int, date_from: str = None, date_to: str = None,
|
||||
date_processed_from: str = None, date_processed_to: str = None) -> List[Tuple]:
|
||||
"""Get unidentified faces from database with optional date filtering"""
|
||||
date_processed_from: str = None, date_processed_to: str = None,
|
||||
min_quality_score: float = 0.0) -> List[Tuple]:
|
||||
"""Get unidentified faces from database with optional date and quality filtering"""
|
||||
with self.db.get_db_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
|
||||
@ -533,6 +544,11 @@ class IdentifyPanel:
|
||||
'''
|
||||
params = []
|
||||
|
||||
# Add quality filtering if specified
|
||||
if min_quality_score > 0.0:
|
||||
query += ' AND f.quality_score >= ?'
|
||||
params.append(min_quality_score)
|
||||
|
||||
# Add date taken filtering if specified
|
||||
if date_from:
|
||||
query += ' AND p.date_taken >= ?'
|
||||
@ -1359,9 +1375,14 @@ class IdentifyPanel:
|
||||
date_processed_from = self.components['date_processed_from_var'].get().strip() or None
|
||||
date_processed_to = self.components['date_processed_to_var'].get().strip() or None
|
||||
|
||||
# Get quality filter
|
||||
min_quality = self.components['quality_filter_var'].get()
|
||||
min_quality_score = min_quality / 100.0
|
||||
|
||||
# Get more faces
|
||||
more_faces = self._get_unidentified_faces(DEFAULT_BATCH_SIZE, date_from, date_to,
|
||||
date_processed_from, date_processed_to)
|
||||
date_processed_from, date_processed_to,
|
||||
min_quality_score)
|
||||
|
||||
if more_faces:
|
||||
# Add to current faces
|
||||
@ -1551,9 +1572,13 @@ class IdentifyPanel:
|
||||
except Exception:
|
||||
batch_size = DEFAULT_BATCH_SIZE
|
||||
|
||||
# Quality filter is already extracted above in min_quality
|
||||
min_quality_score = min_quality / 100.0
|
||||
|
||||
# Reload faces with new filters
|
||||
self.current_faces = self._get_unidentified_faces(batch_size, date_from, date_to,
|
||||
date_processed_from, date_processed_to)
|
||||
date_processed_from, date_processed_to,
|
||||
min_quality_score)
|
||||
|
||||
if not self.current_faces:
|
||||
messagebox.showinfo("No Faces Found", "No unidentified faces found with the current filters.")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user