Update image editor#26
Conversation
WalkthroughThe PR introduces asynchronous rendering for histograms and previews using background thread pools with token-based result matching to prevent stale data application. It enhances ImageEditor with thread-safety, caching, and new public methods for preview retrieval. Input validation, logging, and UI slider throttling are refined. Image buffer management is optimized for live editing, and crop box handling gains defensive copies and bounds checking. Changes
Sequence DiagramssequenceDiagram
participant UI as UI Thread
participant AC as AppController
participant Pool as Thread Pool
participant IE as ImageEditor
rect rgb(230, 245, 250)
Note over UI,IE: Histogram Async Flow
UI->>AC: set_edit_parameter(key, value)
AC->>AC: _kick_histogram_worker()
AC->>Pool: submit(_compute_histogram_worker)
Pool->>IE: get_preview_data_cached()
IE-->>Pool: DecodedImage (cached)
Pool->>Pool: compute histogram from image
Pool-->>AC: _on_histogram_done(future)
AC->>AC: extract result (token-guarded)
AC->>UI: emit histogramReady(payload)
UI->>UI: _apply_histogram_result(payload)
end
rect rgb(245, 230, 250)
Note over UI,IE: Preview Async Flow
UI->>AC: load_image_for_editing() / enter crop mode
AC->>AC: _kick_preview_worker()
AC->>Pool: submit(_render_preview_worker)
Pool->>IE: render preview with current edits
IE-->>Pool: rendered DecodedImage
Pool-->>AC: _on_preview_done(future)
AC->>AC: token match check
AC->>AC: _last_rendered_preview = result
AC->>UI: emit previewReady(payload)
UI->>UI: _apply_preview_result(payload)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (3)
faststack/faststack/imaging/editor.py (1)
58-58: Minor: Redundant exception object inlog.exception()calls.Per static analysis (TRY401),
log.exception()automatically includes the current exception, so passingeis redundant. This is a cosmetic issue and doesn't affect functionality.🔎 Proposed fix
- log.exception(f"Failed to create backup: {e}") + log.exception("Failed to create backup")Similar changes can be applied to lines 258, 722, and 731.
Also applies to: 258-258, 722-722, 731-731
faststack/faststack/app.py (2)
3027-3049: Robust crop box normalization with good defensive coding.The validation handles QJSValue wrappers, list-to-tuple conversion, bounds clamping, and coordinate ordering. This prevents edge cases from causing crashes.
Minor: Static analysis flags
l(line 3041) as an ambiguous variable name. Consider usingleftfor clarity.🔎 Proposed fix for variable naming
- l, t, r, b = [max(0, min(1000, int(x))) for x in crop_box_raw] - - # Ensure correct order (left <= right, top <= bottom) - crop_box_raw = (min(l, r), min(t, b), max(l, r), max(t, b)) + left, top, right, bottom = [max(0, min(1000, int(x))) for x in crop_box_raw] + + # Ensure correct order (left <= right, top <= bottom) + crop_box_raw = (min(left, right), min(top, bottom), max(left, right), max(top, bottom))
2504-2528: Broad exception handling is appropriate here for UI stability.Catching
Exceptioninset_edit_parameterprevents slider interactions from crashing the app. The error is logged for debugging. Per static analysis, consider usinglog.exception()instead oflog.error()to include the stack trace.🔎 Proposed fix
except Exception as e: - log.error("Error setting edit parameter %s=%s: %s", key, value, e) + log.exception("Error setting edit parameter %s=%s", key, value)
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
faststack/faststack/app.pyfaststack/faststack/config.pyfaststack/faststack/imaging/editor.pyfaststack/faststack/imaging/jpeg.pyfaststack/faststack/imaging/prefetch.pyfaststack/faststack/logging_setup.pyfaststack/faststack/qml/Components.qmlfaststack/faststack/qml/ImageEditorDialog.qmlfaststack/faststack/ui/provider.py
🧰 Additional context used
📓 Path-based instructions (5)
faststack/faststack/**/*.py
📄 CodeRabbit inference engine (faststack/faststack/AGENTS.md)
faststack/faststack/**/*.py: Use four-space indentation in Python files
Use trailing commas for multi-line structures in Python
Use descriptive snake_case identifiers for variables and functions in Python
Use CamelCase exclusively for class names and Qt signal names in Python
Include type hints in all Python code as seen inmodels.pyandAppController
Every new Python module should expose a top-levellog = logging.getLogger(__name__)logger
Format Python code with Black before opening a pull request
Lint Python code with Ruff before opening a pull request
Prefer relative paths in configuration and file handling
Files:
faststack/faststack/config.pyfaststack/faststack/imaging/prefetch.pyfaststack/faststack/logging_setup.pyfaststack/faststack/imaging/jpeg.pyfaststack/faststack/imaging/editor.pyfaststack/faststack/app.pyfaststack/faststack/ui/provider.py
faststack/faststack/**/config.py
📄 CodeRabbit inference engine (faststack/faststack/AGENTS.md)
Document any new INI key in
config.py
Files:
faststack/faststack/config.py
faststack/faststack/imaging/**/*.py
📄 CodeRabbit inference engine (faststack/faststack/AGENTS.md)
Cover every new branch in
imaging/orio/modules with focusedpytesttest cases
Files:
faststack/faststack/imaging/prefetch.pyfaststack/faststack/imaging/jpeg.pyfaststack/faststack/imaging/editor.py
faststack/faststack/**/app.py
📄 CodeRabbit inference engine (faststack/faststack/AGENTS.md)
app.pyis the PySide6 entrypoint that coordinates image indexing, caching, and QML screens
Files:
faststack/faststack/app.py
faststack/faststack/ui/**/*.py
📄 CodeRabbit inference engine (faststack/faststack/AGENTS.md)
Treat drag-and-drop helpers like
make_hdropas security-sensitive and validate all user-supplied paths before invoking external tools
Files:
faststack/faststack/ui/provider.py
🧠 Learnings (4)
📚 Learning: 2025-12-08T04:46:41.632Z
Learnt from: CR
Repo: AlanRockefeller/faststack PR: 0
File: faststack/faststack/AGENTS.md:0-0
Timestamp: 2025-12-08T04:46:41.632Z
Learning: Applies to faststack/faststack/**/config.py : Document any new INI key in `config.py`
Applied to files:
faststack/faststack/config.py
📚 Learning: 2025-12-08T04:46:41.632Z
Learnt from: CR
Repo: AlanRockefeller/faststack PR: 0
File: faststack/faststack/AGENTS.md:0-0
Timestamp: 2025-12-08T04:46:41.632Z
Learning: `imaging/` module manages decode workflow, metadata, editing, and ICC caches
Applied to files:
faststack/faststack/imaging/prefetch.pyfaststack/faststack/imaging/editor.py
📚 Learning: 2025-12-08T04:46:41.632Z
Learnt from: CR
Repo: AlanRockefeller/faststack PR: 0
File: faststack/faststack/AGENTS.md:0-0
Timestamp: 2025-12-08T04:46:41.632Z
Learning: Applies to faststack/faststack/**/*.py : Every new Python module should expose a top-level `log = logging.getLogger(__name__)` logger
Applied to files:
faststack/faststack/logging_setup.py
📚 Learning: 2025-12-08T04:46:41.632Z
Learnt from: CR
Repo: AlanRockefeller/faststack PR: 0
File: faststack/faststack/AGENTS.md:0-0
Timestamp: 2025-12-08T04:46:41.632Z
Learning: Applies to faststack/faststack/**/app.py : `app.py` is the PySide6 entrypoint that coordinates image indexing, caching, and QML screens
Applied to files:
faststack/faststack/imaging/editor.pyfaststack/faststack/app.pyfaststack/faststack/ui/provider.py
🧬 Code graph analysis (2)
faststack/faststack/imaging/editor.py (2)
faststack/faststack/models.py (1)
DecodedImage(37-46)faststack/faststack/app.py (1)
get_preview_data(2493-2495)
faststack/faststack/ui/provider.py (1)
faststack/faststack/app.py (1)
get_decoded_image(436-550)
🪛 Ruff (0.14.10)
faststack/faststack/imaging/editor.py
58-58: Redundant exception object included in logging.exception call
(TRY401)
256-256: Consider moving this statement to an else block
(TRY300)
258-258: Redundant exception object included in logging.exception call
(TRY401)
592-592: Consider moving this statement to an else block
(TRY300)
722-722: Redundant exception object included in logging.exception call
(TRY401)
731-731: Redundant exception object included in logging.exception call
(TRY401)
faststack/faststack/app.py
2527-2527: Do not catch blind exception: Exception
(BLE001)
2528-2528: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
2664-2664: Redefinition of unused np from line 55
Remove definition: np
(F811)
2717-2717: Consider moving this statement to an else block
(TRY300)
2718-2718: Do not catch blind exception: Exception
(BLE001)
2727-2727: Do not catch blind exception: Exception
(BLE001)
2775-2775: Consider moving this statement to an else block
(TRY300)
2786-2786: Do not catch blind exception: Exception
(BLE001)
3038-3038: Abstract raise to an inner function
(TRY301)
3038-3038: Avoid specifying long messages outside the exception class
(TRY003)
3041-3041: Ambiguous variable name: l
(E741)
🔇 Additional comments (22)
faststack/faststack/config.py (1)
18-43: Excellent documentation for Auto Levels configuration.The expanded inline documentation block clearly explains the behavior, algorithm, and practical tuning guidelines for the Auto Levels feature. The examples (0.05, 0.001) are well-chosen and the distinction between fraction and percentage is explicit. Default values are sensible and align with the documented guidance.
faststack/faststack/imaging/jpeg.py (1)
118-119: LGTM: Improved input validation.The boundary check now handles negative dimensions in addition to zero, making the validation more robust. This prevents potential issues downstream if negative dimensions are passed.
faststack/faststack/imaging/prefetch.py (2)
422-426: LGTM: Enhanced timing metrics.The explicit copy phase duration logging provides useful performance insights for the ICC fallback path, consistent with the timing structure in other decode paths.
456-459: LGTM: Consistent timing metrics.The copy phase timing addition aligns with the ICC fallback path and provides consistent observability across all decode paths.
faststack/faststack/logging_setup.py (1)
25-43: LGTM: Console logging addition.Adding a console handler alongside the file handler improves developer experience by making logs visible in the terminal. Both handlers share the same formatter and respect the debug flag, which is the correct approach.
faststack/faststack/qml/Components.qml (2)
618-618: LGTM: Defensive copy prevents mutations.Creating a defensive copy of the crop box array prevents unintended mutations of the shared
uiState.currentCropBoxobject, which is good practice for avoiding side effects.
662-676: LGTM: Division-by-zero guard and consistent data handling.The addition of the
boxH > 0check on Line 665 prevents division by zero when calculating the crop start aspect ratio. Using the defensive copy throughout ensures consistent behavior and prevents reference-related bugs.faststack/faststack/ui/provider.py (3)
36-43: LGTM: Correct preview routing.The logic correctly restricts the editor preview to only the currently edited index (
index == self.app_controller.current_index), preventing thumbnails or prefetch requests for other images from receiving the editor's live preview buffer. The fallback toget_decoded_image(index)ensures a valid image is always returned.
46-57: LGTM: Defensive format handling.The code safely handles the case where
image_data.formatisNoneor missing (from the prefetcher) by defaulting toFormat_RGB888. This prevents crashes while maintaining correct behavior.
59-69: LGTM: Correct buffer ownership strategy.The two-path buffer management approach is correct:
Editor path (copy): The deep copy detaches from shared memory that may be mutated during live editing, preventing texture corruption. This is necessary despite the performance cost.
Browsing path (reference): Keeping
original_bufferas a Python reference prevents garbage collection while Qt holds the QImage data pointer. This is essential becauseQImagecreated from bytes does not own or copy the underlying buffer.This implementation correctly handles the ownership semantics of QImage in Python bindings.
faststack/faststack/qml/ImageEditorDialog.qml (3)
400-421: Well-designed slider throttling mechanism.The throttle Timer pattern at ~30fps (32ms interval) is appropriate for smooth UI updates while preventing backend overload. The delta check (
> 0.001) prevents redundant updates. TheBindingwithwhen: !slider.pressedcorrectly allows backend-driven updates when the user isn't actively dragging.
457-468: Solid cleanup on slider release.Stopping the timer and sending the final value immediately on release ensures the backend always receives the user's intended final value. Triggering
update_histogram()only on release is appropriate since histogram computation can be expensive.
123-123: Section header initialization viaonLoadedis acceptable.Using
onLoadedto set thetextproperty works correctly with Loader components. This is a minor refactor from property binding.Also applies to: 140-143, 220-220, 257-260, 270-273
faststack/faststack/imaging/editor.py (3)
176-180: Good thread-safety foundation with RLock and revision tracking.Using
threading.RLock()allows reentrant calls, and the revision counter pattern (_edits_rev,_cached_rev) is a clean way to invalidate cached previews. This design correctly separates mutation tracking from the actual preview computation.
524-564: Well-implemented cached preview with snapshot pattern.The implementation correctly:
- Acquires lock only to check cache validity and snapshot data
- Performs heavy computation (
_apply_edits) outside the lock- Re-checks revision before caching to avoid stale updates
The
allow_computeparameter is a nice touch for non-blocking callers.
599-617: Float comparison prevents unnecessary cache invalidation.Using
math.isclose()with appropriate tolerances prevents cache thrashing from floating-point noise. The fallback to direct equality for non-numeric types is sensible.faststack/faststack/app.py (6)
97-113: Proper background executor setup with clean shutdown handling.Single-worker ThreadPoolExecutors for histogram and preview rendering prevent thread explosion. The in-flight/pending flags and token-based matching are correct patterns for coalescing rapid requests.
2157-2162: Clean executor shutdown prevents hanging on exit.Setting
_shutting_down = Truebefore shutdown and usingwait=False, cancel_futures=Trueensures the application doesn't hang. The flag is correctly checked in all callback methods.
2655-2719: Histogram computation correctly offloaded to background thread.The static method pattern prevents accidental QObject access. Token-based result gating in
_apply_histogram_resultcorrectly discards stale results.One note: line 2664 re-imports
numpy as npinside the static method, shadowing the module-level import. This is harmless but unnecessary since the module-level import is already available.
2750-2812: Preview rendering with token-based stale rejection is well-implemented.The pattern correctly:
- Guards against re-entry with
_preview_inflight- Uses token matching to discard outdated renders
- Uses
QTimer.singleShot(0, ...)to chain pending requests on the UI threadOne minor observation:
_apply_preview_resultholds_preview_lockwhile emitting signals and modifying UI state. Since the slot runs on the UI thread (via signal-slot queuing), this is safe but slightly unusual. Consider moving signal emission outside the lock if you encounter deadlocks in the future.
464-467: Preview fallback correctly uses background-rendered result.Returning
_last_rendered_previewwhen the editor is open ensures the UI displays the most recent edit preview rather than stale cached data.
2766-2778: Thread safety is correctly implemented.All modifications to
_preview_imageandoriginal_imageare properly protected byself._lock. Theget_preview_data_cached()method correctly uses the snapshot pattern: it copies_preview_imageandcurrent_editsunder the lock, then releases the lock before performing heavy computation with_apply_edits(). The static method_render_preview_workersafely receivesimage_editorand callsget_preview_data_cached(allow_compute=True)without race conditions.
Summary by CodeRabbit
Release Notes
Performance
Bug Fixes
UI/UX Improvements
✏️ Tip: You can customize this high-level summary in your review settings.