Skip to content

gallery_screen_base

anomaly_match_ui.screens.gallery_screen_base

Base class for screens with a DB-backed paginated gallery.

Provides shared infrastructure for gallery refresh, DB polling, score histogram, stats panel, image detail navigation, and prefetching. Subclasses implement :meth:build to compose their own layout and override hooks for custom progress display and poll intervals.

Threading model

Foreground executor (_executor, thread_name_prefix="am", max_workers=4): User-driven, latency-sensitive tasks. Sized so a click can run even when other foreground work is in flight.

- *page-load* — loads thumbnail images for the current gallery page
- *detail-load* — loads a full image on magnify click

Prefetch executor (_prefetch_executor, thread_name_prefix="am_prefetch", max_workers=1): Background work that must never starve the foreground lane. A wedged Cutana parquet read here can't queue ahead of clicks (#444).

- *prefetch* — warms the image cache for the pages around the
  current one (window prefetch), cancel-on-navigate

The underscore in the prefix is deliberate: the UI conftest joins
every ``am-*``-named thread on test teardown to keep ``am-poll``
from racing the SQLite GC at interpreter shutdown, and an idle
``ThreadPoolExecutor`` worker blocked in ``queue.get()`` would
eat the full join timeout (~2s) per test under that prefix.

Managed threads (explicit threading.Thread): Long-running threads that need Event-based cancellation.

- *poll* (``am-poll``) — polls the DB every N seconds until stopped

GalleryScreenBase(app)

Bases: BaseScreen

Base for screens with a DB-backed paginated image gallery.

Provides gallery refresh, DB polling, histogram, stats panel, image detail navigation, prefetching, and common UI helpers. Subclasses must implement :meth:build and may override hooks like :meth:_get_poll_interval and :meth:_update_poll_progress.

Subclasses must assign the following attributes in their :meth:build before returning:

  • self.gallery — a :class:GalleryWidget
  • self.stats_html — :class:widgets.HTML for statistics
  • self._histogram_image — :class:widgets.Image for the histogram
  • self.status_html — :class:widgets.HTML for status text
  • self.progress — a :class:ProgressPanel
  • self.out — :class:widgets.Output for terminal logging

Parameters:

Name Type Description Default
app AnomalyMatchApp

The parent application instance used for navigation.

required
Source code in anomaly_match_ui/screens/gallery_screen_base.py
def __init__(self, app: AnomalyMatchApp) -> None:
    super().__init__(app)
    self.cfg = BackendInterface.get_config()

    # Gallery state
    self._current_page: int = 0
    # Absolute offset of the first visible cell.  Tracked alongside
    # ``_current_page`` so a slider-driven re-page (effective page
    # size shrinks when cells widen) can land the user near the
    # same item rather than at a stale page index (#432).
    self._gallery_first_offset: int = 0
    self._image_load_page: int = -1
    self._detail_loading: bool = False

    # Polling state
    self._polling: bool = False
    self._poll_stop = threading.Event()
    self._poll_thread: threading.Thread | None = None

    # User-driven foreground work: page-load and detail-load.  Sized
    # so a click for the next page or the magnify view doesn't sit
    # behind an in-flight Cutana batch read (#444).  Keeping it
    # separate from the prefetch lane below means a slow parquet
    # walk on the prefetch side can't starve a click that happens
    # while it's running.
    self._executor = ThreadPoolExecutor(max_workers=4, thread_name_prefix="am")
    # Background prefetch lane — single-threaded so a wedged Cutana
    # read here can never queue ahead of foreground work.  Earlier
    # versions shared a single 3-worker pool and a stuck prefetch
    # could hold 1/3 of the slots indefinitely, leaving foreground
    # clicks blocked behind page-loads queued after it.
    self._prefetch_executor = ThreadPoolExecutor(
        max_workers=1, thread_name_prefix="am_prefetch"
    )
    self._prefetch_future: Future | None = None
    # Bumped on every navigation / sort change / poll so a prefetch task
    # warming a now-stale window bails out between pages the moment the
    # user moves, keeping the single prefetch worker on the live view.
    self._prefetch_generation: int = 0

    # DB / source state
    self._db_path: str = ""
    self._start_time: float = 0.0
    self._last_db_count: int = 0
    self._total_source_count: int = 0
    self._file_type: DataSourceType = DataSourceType.IMAGE_FOLDER

    # UI widgets set by subclass build()
    self._loading_html = widgets.HTML()
    self._countdown_html = widgets.HTML()
    self._action_buttons: list[widgets.Button] = []