Skip to content

Version 0.1#1

Merged
AlanRockefeller merged 6 commits intomainfrom
test
Nov 1, 2025
Merged

Version 0.1#1
AlanRockefeller merged 6 commits intomainfrom
test

Conversation

@AlanRockefeller
Copy link
Copy Markdown
Owner

@AlanRockefeller AlanRockefeller commented Nov 1, 2025

Version 0.1

Summary by CodeRabbit

  • New Features

    • Launched FastStack v0.1 image viewer with ultra-fast navigation and high-performance decoding
    • Added zoom, pan, and grid view capabilities
    • Introduced automatic RAW-JPG file pairing
    • Added image stacking and Helicon Focus integration
    • Implemented metadata management (flag/reject images)
    • Added configurable settings via INI file
    • Provided comprehensive keyboard shortcuts reference
  • Documentation

    • Added project README with features, installation, and usage guide
    • Added MIT License

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Nov 1, 2025

Walkthrough

Introduces FastStack, a complete image viewer application for photographers. The PR adds configuration management, JPEG/RAW file indexing and pairing, high-performance image decoding with TurboJPEG fallback, background prefetching, filesystem watching, metadata handling via sidecar JSON, Helicon Focus integration, Qt/QML UI components, keyboard input handling, application state management, and comprehensive test coverage.

Changes

Cohort / File(s) Summary
Project Configuration & Tooling
.gitignore, pyproject.toml, requirements.txt
Added standard ignore patterns and project metadata; declared dependencies (PySide6, PyTurboJPEG, watchdog, cachetools, Pillow, numpy) with version constraints and build system configuration.
Documentation
README.md, faststack/README.md, faststack/LICENSE
Removed single line from root README; added project-level README (v0.1) detailing FastStack features, installation, usage, and keyboard shortcuts; added MIT License (2025, Alan Rockefeller).
Core Data Models
faststack/faststack/models.py
Introduced dataclasses: ImageFile (path, raw_pair, timestamp), EntryMetadata (flag, reject), Sidecar (version, last_index, entries, stacks), DecodedImage (buffer, dimensions, format) with \sizeof support.
Logging & Application Setup
faststack/faststack/logging_setup.py, faststack/faststack/config.py
Added logging configuration with rotating file handler; implemented INI-based configuration system for cache size, prefetch radius, and Helicon Focus settings.
Image I/O & Indexing
faststack/faststack/io/indexer.py, faststack/faststack/io/watcher.py
Implemented JPG/RAW pairing by filename similarity and modification time; added filesystem watcher for directory changes.
Image Decoding & Caching
faststack/faststack/imaging/jpeg.py, faststack/faststack/imaging/cache.py
Implemented PyTurboJPEG-based JPEG decoding (RGB and thumbnail) with Pillow fallback; introduced ByteLRUCache with byte-aware eviction.
Background Prefetching
faststack/faststack/imaging/prefetch.py
Added threaded prefetcher for asynchronous decoding of adjacent images within a configurable radius.
Metadata & Sidecar Management
faststack/faststack/io/sidecar.py
Implemented atomic JSON sidecar (faststack.json) persistence for per-image metadata and stack groupings.
External Integration
faststack/faststack/io/helicon.py
Added launcher for Helicon Focus with RAW file aggregation and temporary file cleanup.
UI Components & State
faststack/faststack/qml/Components.qml, faststack/faststack/qml/Main.qml
Introduced QML UI: loupe-style image viewer (pan/zoom), metadata overlay, menu bar with dialogs, status bar showing image count and flags/rejects.
UI Integration & Input
faststack/faststack/ui/provider.py, faststack/faststack/ui/keystrokes.py
Implemented QML image provider and UIState bridge for image streaming; added keyboard event mapper for navigation, tagging, and stack management.
Application Controller
faststack/faststack/app.py
Implemented AppController wiring backend, caching, prefetching, UI, and keyboard input; added entry point main(image_dir) with Qt/QML initialization and graceful shutdown.
Tests
faststack/faststack/tests/test_cache.py, faststack/faststack/tests/test_pairing.py, faststack/faststack/tests/test_sidecar.py
Added test suites for ByteLRUCache eviction, RAW/JPG pairing logic, and sidecar load/save/metadata operations.
Utilities
faststack/test.py
Added TurboJPEG environment variable test script.

Sequence Diagram

sequenceDiagram
    participant User
    participant main
    participant AppController
    participant Backend as Backend<br/>(Cache, Prefetch, etc.)
    participant UI as UI<br/>(QML, UIState)
    participant FileSystem as FileSystem<br/>(Watcher, Sidecar)

    User->>main: faststack /path/to/images
    main->>AppController: Initialize (image_dir, engine)
    AppController->>Backend: Set up Cache, Prefetcher, Watcher
    AppController->>FileSystem: Load Sidecar metadata
    AppController->>Backend: Load and index images
    AppController->>UI: Wire UIState, register image provider
    Backend->>Backend: Prefetch adjacent images (async)
    AppController->>User: Show application window
    
    User->>UI: Press arrow key / click menu
    UI->>AppController: next_image() or toggle_flag()
    AppController->>Backend: Get decoded image from cache
    alt Cache hit
        Backend->>UI: Return cached DecodedImage
    else Cache miss
        Backend->>Backend: Synchronously decode via Prefetcher
        Backend->>UI: Return decoded image
    end
    AppController->>UI: Update UIState (index, metadata, image source)
    UI->>User: Render image and metadata
    Backend->>Backend: Update prefetch window (async)
    
    User->>UI: Begin/end stack or request Helicon Focus
    AppController->>FileSystem: Save stack info and metadata to sidecar
    AppController->>FileSystem: Launch Helicon Focus with aggregated RAW files
    
    User->>UI: Exit or close window
    AppController->>AppController: Sync UI state, cancel prefetch tasks
    AppController->>FileSystem: Save last index and metadata to sidecar
    AppController->>Backend: Shutdown cache, watcher, prefetcher
    rect rgba(0, 200, 150, 0.2)
        Note over AppController: Graceful cleanup
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring extra attention:

  • faststack/faststack/app.py – Core application controller with intricate wiring of cache, prefetcher, UI, keyboard input, and graceful shutdown; validates logic flow from startup through image navigation to termination.
  • faststack/faststack/ui/provider.py – QML integration layer for image provider and UIState; verify correct signal/slot binding, image buffer lifecycle, and thread safety in image fetching.
  • faststack/faststack/imaging/prefetch.py – Background thread pool for async image decoding; verify generation counter logic, task cancellation, and proper cleanup to avoid memory leaks or stale tasks.
  • faststack/faststack/io/sidecar.py – Atomic file I/O for sidecar persistence; verify atomic rename, watcher state management, and robust error handling for corrupted JSON.
  • faststack/faststack/imaging/jpeg.py – Dual-path decoding (PyTurboJPEG + Pillow fallback); verify error handling, numpy array conversions, and correct RGB format in both paths.
  • File pairing logic in faststack/faststack/io/indexer.py – RAW/JPG matching by modification time; confirm 2-second tolerance window and sorting stability.
  • QML UI coherence – Verify that Main.qml and Components.qml correctly expose uiState properties, handle missing context gracefully, and coordinate menu actions with AppController slots.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.74% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The title "Version 0.1" is vague and does not convey meaningful information about the changeset. While the version number is technically present in the PR (reflected in pyproject.toml and README), the title fails to describe what the changeset actually introduces. This is a substantial pull request that brings an entirely new application (FastStack photo management tool) to the codebase, including core application logic, UI components, configuration, testing, and documentation. A developer scanning the commit history would not understand the scope or purpose of this change from the title alone, as "Version 0.1" could apply to any version bump in any project. Consider using a more descriptive title that captures the main purpose of the changeset, such as "Introduce FastStack v0.1: photo management and image stacking application" or "Add FastStack application with image viewer, RAW/JPEG pairing, and stacking support". This would help readers immediately understand the scope and intent of the changes without needing to examine the full PR description.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch test

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

🧹 Nitpick comments (10)
faststack/requirements.txt (1)

1-9: Consider separating development and runtime dependencies.

The requirements file mixes runtime dependencies (PySide6, PyTurboJPEG, numpy, etc.) with build and testing tools (pyinstaller, pytest). This can lead to bloated production installations.

Consider one of these approaches:

Option 1: Use pyproject.toml optional dependencies
Since you already have pyproject.toml, move these to optional dependency groups:

In pyproject.toml:

[project.optional-dependencies]
dev = [
    "pytest>=8.0,<9.0",
    "pyinstaller>=6.0,<7.0",
]

Then install with: pip install -e ".[dev]"

Option 2: Separate requirements files
Create requirements-dev.txt:

pytest==8.*
pyinstaller==6.*

And keep only runtime dependencies in requirements.txt.

faststack/faststack/ui/keystrokes.py (1)

39-39: Lower log level for key press events.

Logging every key press at INFO level may create excessive log noise during normal operation.

-log.info(f"Key pressed: {event.key()}")
+log.debug(f"Key pressed: {event.key()}")
faststack/pyproject.toml (1)

28-28: Move pytest to optional dev dependencies.

pytest is a testing framework and shouldn't be a runtime dependency.

 dependencies = [
     "PySide6>=6.0,<7.0",
     "PyTurboJPEG>=1.8,<2.0",
     "numpy>=2.0,<3.0",
     "cachetools>=5.0,<6.0",
     "watchdog>=4.0,<5.0",
     "typer>=0.12,<1.0",
     "Pillow>=10.0,<11.0",
-    "pytest>=8.0,<9.0",
 ]
+
+[project.optional-dependencies]
+dev = [
+    "pytest>=8.0,<9.0",
+]
faststack/faststack/io/sidecar.py (1)

49-52: Use logging.exception for better error diagnostics.

When logging errors in exception handlers, logging.exception automatically includes the traceback, which aids debugging.

 except (json.JSONDecodeError, TypeError) as e:
-    log.error(f"Failed to load or parse sidecar file {self.path}: {e}")
+    log.exception(f"Failed to load or parse sidecar file {self.path}")
     # Consider backing up the corrupted file here
     return Sidecar()

Note: The comment on line 51 suggests backing up corrupted files—consider implementing this to prevent data loss.

faststack/faststack/tests/test_pairing.py (1)

55-55: Consider splitting compound statements for PEP 8 compliance.

Multiple statements are combined on single lines using semicolons, which reduces readability.

Example for Line 55:

-    jpg_stat = MagicMock(); jpg_stat.st_mtime = 1000.0
+    jpg_stat = MagicMock()
+    jpg_stat.st_mtime = 1000.0

Apply similar changes to Lines 58, 63, 68, and 69.

Also applies to: 58-58, 63-63, 68-69

faststack/faststack/io/helicon.py (2)

50-52: Use logging.exception for better debugging.

When logging in an exception handler, logging.exception automatically includes the stack trace.

Apply this diff:

     except Exception as e:
-        log.error(f"Failed to launch Helicon Focus: {e}")
+        log.exception("Failed to launch Helicon Focus")
         return False, None

46-47: Remove duplicate log statement.

Lines 46 and 47 both log the command, with Line 46 at INFO level and Line 47 at DEBUG level. One is sufficient.

Apply this diff:

         log.info(f"Launching Helicon Focus with {len(raw_files)} files.")
-        log.info(f"Helicon Focus command: {args}") # Log the full command
         log.debug(f"Command: {args}")
faststack/faststack/qml/Components.qml (2)

72-72: Simplify redundant ternary operators.

The outer condition already checks uiState.isFlagged / uiState.isRejected, so the inner ternary is redundant.

Apply this diff:

             Text {
-                text: uiState && uiState.isFlagged ? `[${uiState.isFlagged ? 'F' : ''}]` : ""
+                text: uiState && uiState.isFlagged ? "[F]" : ""
                 color: "lightgreen"
                 font.bold: true
             }
              Text {
-                text: uiState && uiState.isRejected ? `[${uiState.isRejected ? 'X' : ''}]` : ""
+                text: uiState && uiState.isRejected ? "[X]" : ""
                 color: "red"
                 font.bold: true
             }

Also applies to: 77-77


43-48: Consider adding zoom bounds.

The current zoom implementation multiplies scale indefinitely without limits, which could lead to extreme zoom levels (either very large or very small).

Consider adding bounds:

onWheel: {
    var scaleFactor = wheel.angleDelta.y > 0 ? 1.2 : 1 / 1.2;
    var newScale = mainImage.scale * scaleFactor;
    // Clamp between 0.1x and 10x
    mainImage.scale = Math.max(0.1, Math.min(10.0, newScale));
}
faststack/faststack/imaging/cache.py (1)

35-38: Consider moving import to module level.

The import of DecodedImage on Line 35 is inside the function. Per PEP 8, imports should typically be at the module level unless there's a specific reason (e.g., circular import).

If there's no circular dependency, apply this diff:

 import logging
 from typing import Any, Callable
 
 from cachetools import LRUCache
+from faststack.models import DecodedImage

 log = logging.getLogger(__name__)
 
 ...
 
 def get_decoded_image_size(item) -> int:
     """Calculates the size of a decoded image tuple (buffer, qimage)."""
     # In this simplified example, we only store the buffer.
     # In the full app, this would also account for the QImage/QTexture.
-    from faststack.models import DecodedImage
     if isinstance(item, DecodedImage):
         return item.buffer.nbytes
     return 1 # Should not happen
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7eff71f and ff72a99.

📒 Files selected for processing (25)
  • .gitignore (1 hunks)
  • README.md (0 hunks)
  • faststack/LICENSE (1 hunks)
  • faststack/README.md (1 hunks)
  • faststack/faststack/app.py (1 hunks)
  • faststack/faststack/config.py (1 hunks)
  • faststack/faststack/imaging/cache.py (1 hunks)
  • faststack/faststack/imaging/jpeg.py (1 hunks)
  • faststack/faststack/imaging/prefetch.py (1 hunks)
  • faststack/faststack/io/helicon.py (1 hunks)
  • faststack/faststack/io/indexer.py (1 hunks)
  • faststack/faststack/io/sidecar.py (1 hunks)
  • faststack/faststack/io/watcher.py (1 hunks)
  • faststack/faststack/logging_setup.py (1 hunks)
  • faststack/faststack/models.py (1 hunks)
  • faststack/faststack/qml/Components.qml (1 hunks)
  • faststack/faststack/qml/Main.qml (1 hunks)
  • faststack/faststack/tests/test_cache.py (1 hunks)
  • faststack/faststack/tests/test_pairing.py (1 hunks)
  • faststack/faststack/tests/test_sidecar.py (1 hunks)
  • faststack/faststack/ui/keystrokes.py (1 hunks)
  • faststack/faststack/ui/provider.py (1 hunks)
  • faststack/pyproject.toml (1 hunks)
  • faststack/requirements.txt (1 hunks)
  • faststack/test.py (1 hunks)
💤 Files with no reviewable changes (1)
  • README.md
🧰 Additional context used
🧬 Code graph analysis (12)
faststack/faststack/imaging/cache.py (1)
faststack/faststack/models.py (1)
  • DecodedImage (30-39)
faststack/faststack/io/sidecar.py (3)
faststack/faststack/models.py (2)
  • Sidecar (22-27)
  • EntryMetadata (15-18)
faststack/faststack/app.py (1)
  • load (65-72)
faststack/faststack/io/watcher.py (3)
  • stop (47-53)
  • start (32-45)
  • is_alive (55-57)
faststack/faststack/tests/test_cache.py (1)
faststack/faststack/imaging/cache.py (1)
  • ByteLRUCache (10-28)
faststack/faststack/config.py (1)
faststack/faststack/logging_setup.py (1)
  • get_app_data_dir (8-13)
faststack/faststack/io/helicon.py (1)
faststack/faststack/config.py (1)
  • get (57-58)
faststack/faststack/io/indexer.py (1)
faststack/faststack/models.py (1)
  • ImageFile (8-12)
faststack/faststack/imaging/prefetch.py (3)
faststack/faststack/models.py (2)
  • ImageFile (8-12)
  • DecodedImage (30-39)
faststack/faststack/imaging/jpeg.py (1)
  • decode_jpeg_rgb (22-39)
faststack/faststack/app.py (1)
  • shutdown (205-216)
faststack/faststack/ui/keystrokes.py (1)
faststack/faststack/app.py (9)
  • next_image (105-109)
  • prev_image (111-115)
  • toggle_grid_view (117-118)
  • toggle_current_flag (136-141)
  • toggle_current_reject (143-148)
  • begin_new_stack (150-153)
  • end_current_stack (155-168)
  • launch_helicon (170-188)
  • clear_all_stacks (198-203)
faststack/faststack/tests/test_sidecar.py (2)
faststack/faststack/io/sidecar.py (4)
  • SidecarManager (12-90)
  • set_last_index (89-90)
  • get_metadata (85-87)
  • save (54-83)
faststack/faststack/models.py (1)
  • EntryMetadata (15-18)
faststack/faststack/app.py (11)
faststack/faststack/logging_setup.py (1)
  • setup_logging (15-35)
faststack/faststack/models.py (3)
  • ImageFile (8-12)
  • DecodedImage (30-39)
  • EntryMetadata (15-18)
faststack/faststack/io/indexer.py (1)
  • find_images (19-54)
faststack/faststack/io/sidecar.py (5)
  • SidecarManager (12-90)
  • load (26-52)
  • get_metadata (85-87)
  • save (54-83)
  • set_last_index (89-90)
faststack/faststack/io/watcher.py (3)
  • Watcher (24-57)
  • start (32-45)
  • stop (47-53)
faststack/faststack/io/helicon.py (1)
  • launch_helicon_focus (13-52)
faststack/faststack/imaging/cache.py (2)
  • ByteLRUCache (10-28)
  • get_decoded_image_size (31-38)
faststack/faststack/imaging/prefetch.py (5)
  • Prefetcher (13-108)
  • update_prefetch (29-49)
  • set_image_files (25-27)
  • submit_task (51-60)
  • shutdown (104-108)
faststack/faststack/ui/provider.py (2)
  • ImageProvider (15-49)
  • UIState (51-106)
faststack/faststack/ui/keystrokes.py (2)
  • Keybinder (8-44)
  • handle_key_press (37-44)
faststack/faststack/config.py (3)
  • getint (60-61)
  • load (28-44)
  • save (47-55)
faststack/faststack/ui/provider.py (2)
faststack/faststack/models.py (1)
  • DecodedImage (30-39)
faststack/faststack/app.py (5)
  • get_decoded_image (80-94)
  • get_current_metadata (120-134)
  • next_image (105-109)
  • prev_image (111-115)
  • toggle_current_flag (136-141)
faststack/faststack/tests/test_pairing.py (1)
faststack/faststack/io/indexer.py (2)
  • find_images (19-54)
  • _find_raw_pair (56-80)
🪛 LanguageTool
faststack/README.md

[style] ~3-~3: Some style guides suggest that commas should set off the year in a month-day-year date.
Context: ... FastStack # Version 0.1 - October 31, 2025 # By Alan Rockefeller Ultra-fast, cach...

(MISSING_COMMA_AFTER_YEAR)

🪛 Ruff (0.14.2)
faststack/faststack/io/sidecar.py

50-50: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


80-80: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

faststack/faststack/config.py

55-55: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

faststack/faststack/io/helicon.py

48-48: subprocess call: check for execution of untrusted input

(S603)


49-49: Consider moving this statement to an else block

(TRY300)


50-50: Do not catch blind exception: Exception

(BLE001)


51-51: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

faststack/faststack/io/indexer.py

38-38: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

faststack/faststack/imaging/prefetch.py

87-87: Do not catch blind exception: Exception

(BLE001)


88-88: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

faststack/faststack/imaging/jpeg.py

28-28: Do not catch blind exception: Exception

(BLE001)


29-29: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


37-37: Do not catch blind exception: Exception

(BLE001)


38-38: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


55-55: Do not catch blind exception: Exception

(BLE001)


56-56: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


64-64: Do not catch blind exception: Exception

(BLE001)


65-65: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

faststack/faststack/io/watcher.py

27-27: Undefined name Optional

(F821)

faststack/faststack/tests/test_sidecar.py

14-14: PEP 484 prohibits implicit Optional

Convert to T | None

(RUF013)

faststack/faststack/app.py

176-176: Loop control variable i not used within loop body

Rename unused i to _i

(B007)


196-196: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


231-231: Do not perform function call typer.Argument in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

faststack/faststack/ui/provider.py

22-22: Unused method argument: size

(ARG002)


22-22: Unused method argument: requestedSize

(ARG002)


47-47: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

faststack/faststack/tests/test_pairing.py

55-55: Multiple statements on one line (semicolon)

(E702)


58-58: Multiple statements on one line (semicolon)

(E702)


58-58: Multiple statements on one line (semicolon)

(E702)


63-63: Multiple statements on one line (semicolon)

(E702)


63-63: Multiple statements on one line (semicolon)

(E702)


68-68: Multiple statements on one line (semicolon)

(E702)


68-68: Multiple statements on one line (semicolon)

(E702)


69-69: Multiple statements on one line (semicolon)

(E702)


69-69: Multiple statements on one line (semicolon)

(E702)

🔇 Additional comments (14)
faststack/LICENSE (1)

1-25: MIT License is well-suited for this project.

The license file is correctly formatted with the standard MIT License text and includes an appropriate copyright notice. MIT is a permissive open-source license that aligns well with a community-driven image viewer application.

.gitignore (1)

1-25: Well-structured .gitignore with comprehensive Python patterns.

The file is well-organized with clear sections covering virtual environments, Python cache, build outputs, logs, OS files, and IDE configurations. The patterns follow Python project best practices and the addition of prompt.md appropriately excludes project-specific documentation.

faststack/faststack/io/watcher.py (1)

32-57: LGTM! Watcher lifecycle management is well-implemented.

The implementation correctly:

  • Validates directory existence before starting
  • Prevents multiple concurrent observers
  • Creates fresh Observer instances (avoiding restart issues)
  • Properly stops and cleans up resources
faststack/faststack/io/sidecar.py (2)

54-83: Excellent atomic save implementation with minor logging improvement.

The atomic save pattern (write to temp file, then rename) is robust and prevents corruption. Stopping the watcher during save prevents recursive events.

Minor improvement for consistency:

 except (IOError, TypeError) as e:
-    log.error(f"Failed to save sidecar file {self.path}: {e}")
+    log.exception(f"Failed to save sidecar file {self.path}")
 finally:

85-90: LGTM! Clean metadata accessor methods.

The use of setdefault for get-or-create pattern in get_metadata is idiomatic and efficient.

faststack/faststack/imaging/jpeg.py (3)

22-39: Robust fallback implementation with minor logging improvement.

The two-tier fallback (TurboJPEG → Pillow → None) provides excellent resilience. Catching broad Exception is justified here since any decode failure should trigger fallback.

Consider using logging.exception for automatic traceback capture:

 except Exception as e:
-    log.error(f"PyTurboJPEG failed to decode image: {e}. Trying Pillow.")
+    log.exception("PyTurboJPEG failed to decode image. Trying Pillow.")
 except Exception as e:
-    log.error(f"Pillow also failed to decode image: {e}")
+    log.exception("Pillow also failed to decode image")
     return None

Optional: Move BytesIO import to module level (line 4) for minor efficiency gain.


41-66: Well-implemented thumbnail decoding with scaling optimization.

Leveraging TurboJPEG's native scaling (N/8 factors) for thumbnail generation is efficient.

Apply the same logging improvements:

 except Exception as e:
-    log.error(f"PyTurboJPEG failed to decode thumbnail: {e}. Trying Pillow.")
+    log.exception("PyTurboJPEG failed to decode thumbnail. Trying Pillow.")
 except Exception as e:
-    log.error(f"Pillow also failed to decode thumbnail: {e}")
+    log.exception("Pillow also failed to decode thumbnail")
     return None

68-74: LGTM! Correct implementation of TurboJPEG scaling factor selection.

The algorithm correctly iterates from largest to smallest scaling factor to find the best fit within max_dim.

faststack/faststack/models.py (1)

1-39: LGTM! Clean data model definitions.

The dataclasses are well-structured with appropriate type hints and defaults. The __sizeof__ implementation on DecodedImage correctly returns the buffer size for memory accounting.

faststack/faststack/tests/test_cache.py (1)

1-61: LGTM! Comprehensive cache test coverage.

The tests thoroughly exercise the ByteLRUCache behavior, including initialization, size tracking, eviction policy, and item updates. The MockItem approach for testing size accounting is clean and effective.

faststack/faststack/logging_setup.py (1)

1-35: LGTM! Well-configured logging setup.

The logging configuration is appropriate for a desktop application with rotating file handler (10MB, 5 backups) and sensible defaults. The platform-specific app data directory logic correctly handles Windows and Unix-like systems.

faststack/faststack/tests/test_pairing.py (1)

36-51: LGTM! Test coverage is thorough.

The test_find_images function effectively validates the RAW-JPG pairing logic with realistic scenarios.

faststack/faststack/imaging/cache.py (1)

10-28: LGTM! Well-implemented byte-aware cache.

The ByteLRUCache properly extends LRUCache with size-based eviction and informative logging. The implementation correctly delegates to the parent class for eviction logic.

faststack/faststack/io/helicon.py (1)

33-49: No action required. The review comment is incorrect.

Cleanup of the temporary file is properly implemented in the caller code (app.py lines 183-186). The function launch_helicon_focus returns the tmp_path, which the caller schedules for deletion via QTimer.singleShot(5000, lambda: self._delete_temp_file(tmp_path)). The _delete_temp_file method (lines 190-195) then removes the file using os.remove() with proper error handling. This is a valid architectural pattern where the caller takes ownership of the temporary file lifecycle.

Likely an incorrect or invalid review comment.

Comment thread .gitignore
Comment thread faststack/faststack/app.py
Comment thread faststack/faststack/app.py
Comment thread faststack/faststack/imaging/prefetch.py
Comment thread faststack/faststack/io/helicon.py
Comment thread faststack/faststack/tests/test_sidecar.py
Comment thread faststack/faststack/ui/keystrokes.py
Comment thread faststack/pyproject.toml
Comment thread faststack/README.md
Comment thread faststack/test.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant