Metadata-Version: 2.3
Name: fundus-camera-watchdog
Version: 0.5.0
Summary: File watchdog for fundus camera
Requires-Dist: requests>=2.34.2
Requires-Dist: tzdata>=2026.2
Requires-Dist: watchdog>=6.0.0
Requires-Python: >=3.12, <3.15
Project-URL: Homepage, https://github.com/erikvw/fundus-camera-watchdog
Description-Content-Type: text/x-rst

|pypi| |actions| |codecov| |downloads| |clinicedc|

Fundus Camera Watchdog
======================

``fundus-camera-watchdog`` monitors a folder on the fundus camera
workstation and uploads files to a clinicedc project using the
``edc-retinopathy`` API.

It runs on the camera's workstation.  When the camera finishes an
examination and writes files to disk, the watchdog detects them,
confirms the subject has a camera session on the server, uploads each
file, and moves the completed folder to an archive.

Prerequisites
-------------

1. A running clinicedc server with ``edc-retinopathy`` installed and an
   API token created.

2. The camera software must write its output into the watched folder
   using the expected layout (see `Folder layout`_ below).

Folder layout
-------------

The camera creates one subfolder per subject.  Folder names follow a
configurable pattern (see ``subject_folder_pattern``).  Filenames
contain the eye laterality (``OD`` for right, ``OS`` for left),
extracted via ``filename_eye_pattern``.

**Combined report** (default)::

    C:\RetCamOutput\
        105-10-0989-3_\
            105-10-0989-3_Retina_OD_20260602_121802.jpg
            105-10-0989-3_Retina_OS_20260602_121803.jpg
            105-10-0989-3_Retina_OD_20260602_121802.dcm
            105-10-0989-3_Retina_OS_20260602_121803.dcm
            105-10-0989-3_Report_20260602.html
        105-10-0001-2_\
            ...

**Per-eye report**::

    C:\RetCamOutput\
        105-10-0989-3_\
            105-10-0989-3_Retina_OD_20260602_121802.jpg
            105-10-0989-3_Retina_OS_20260602_121803.jpg
            105-10-0989-3_Report_OD_20260602.html
            105-10-0989-3_Report_OS_20260602.html

Processing is triggered once a subject folder contains the expected
number of files:

- **Combined** (default): at least **2 JPEG** and **1 HTML** file.
- **Per-eye**: at least **2 JPEG** and **2 HTML** files.

DICOM (``.dcm``) files are uploaded when present but are not required
for triggering.

Quick start
-----------

1. Generate a sample config in the camera output folder::

    cd C:\RetCamOutput
    uvx fundus-camera-watchdog --create-config

   This creates ``fundus_camera_watchdog.json`` with sensible defaults.

2. Edit the config to fill in ``api_url``, ``subject_folder_pattern``,
   and optionally ``filename_eye_pattern``, ``device_id``, etc.

3. Set the API token as an environment variable (recommended)::

    REM Windows (persists across reboots)
    setx FUNDUS_CAMERA_WATCHDOG_TOKEN "YOUR_DRF_TOKEN"

4. Start the watchdog::

    cd C:\RetCamOutput
    uvx fundus-camera-watchdog

Configuration
-------------

Settings are resolved in the following order (highest priority first):

1. CLI flags (e.g. ``--api-url``)
2. JSON config file
3. ``FUNDUS_CAMERA_WATCHDOG_TOKEN`` environment variable (token only)
4. Built-in defaults

Config file discovery
~~~~~~~~~~~~~~~~~~~~~

If ``--config`` is not provided, the watchdog looks for
``fundus_camera_watchdog.json`` in the watch directory (or the current
directory if ``--watch-dir`` is also omitted).  Use ``--create-config``
to generate a sample.

Example ``fundus_camera_watchdog.json``::

    {
        "watch_dir": "C:\\RetCamOutput",
        "api_url": "https://edc.example.com",
        "device_id": "RET-CAM-001",
        "site_id": "40",
        "report_type": "combined",
        "subject_folder_pattern": "^(?P<subject_identifier>\\d{3}-\\d{2}-\\d{4}-\\d)_$",
        "filename_eye_pattern": "(?P<eye>OD|OS)"
    }

Required settings
~~~~~~~~~~~~~~~~~

``watch_dir``
    Folder the camera writes subject subfolders to.  Defaults to the
    current directory if not provided.

``api_url``
    Base URL of the EDC server (e.g. ``https://edc.example.com``).

``token``
    DRF authentication token.  Can be provided via ``--token``, the
    config file, or ``FUNDUS_CAMERA_WATCHDOG_TOKEN`` env var
    (recommended).

Optional settings
~~~~~~~~~~~~~~~~~

``device_id``
    Identifier for this camera (sent to the server on resolve).

``site_id``
    Study site identifier.

``report_type``
    How the camera produces report files.  ``combined`` (default) means
    a single HTML file covers both eyes; ``per_eye`` means one HTML per
    eye.

``subject_folder_pattern``
    Regex to match subject folder names in the watch directory.  Use a
    named group ``subject_identifier`` to extract the ID from the folder
    name.  Default: ``^(?P<subject_identifier>.+)$`` (matches
    everything, folder name used as-is).

    Example for folders like ``105-40-1232-0_``::

        "subject_folder_pattern": "^(?P<subject_identifier>\\d{3}-\\d{2}-\\d{4}-\\d)_$"

    This matches the folder and extracts ``105-40-1232-0`` as the
    subject identifier (stripping the trailing underscore).

``filename_eye_pattern``
    Regex to extract eye laterality from filenames.  Use a named group
    ``eye``.  Default: ``(?P<eye>OD|OS)``.

    The extracted value is normalised automatically.  All of the
    following are recognised:

    - **Left eye**: ``L``, ``LE``, ``OS``, ``LEFT``
    - **Right eye**: ``R``, ``RE``, ``OD``, ``RIGHT``

``log_level``
    One of ``DEBUG``, ``INFO`` (default), ``WARNING``, ``ERROR``.

Usage
-----

Run from the camera output folder::

    cd C:\RetCamOutput
    uvx fundus-camera-watchdog

With an explicit config file::

    uvx fundus-camera-watchdog --config C:\path\to\fundus_camera_watchdog.json

CLI flags override any value from the config file::

    uvx fundus-camera-watchdog --log-level DEBUG --report-type per_eye

Without a config file::

    uvx fundus-camera-watchdog ^
        --watch-dir C:\RetCamOutput ^
        --api-url https://edc.example.com ^
        --token YOUR_TOKEN ^
        --device-id RET-CAM-001 ^
        --subject-folder-pattern "^(?P<subject_identifier>\d{3}-\d{2}-\d{4}-\d)_$"

Stop with ``Ctrl+C``.

What it does
------------

The watchdog runs continuously and performs the following for each
subject:

1. **Detect** -- watches for new files in subject subfolders using
   filesystem events (``watchdog``), plus a periodic sweep every
   60 seconds as a safety net.

2. **Wait** -- each file is given up to 30 seconds to stabilise (stop
   growing) before being registered.

3. **Classify** -- extracts eye laterality from each filename using the
   configured pattern.  Maps files to API types:

   - ``*.jpg`` + OD -> ``right``
   - ``*.jpg`` + OS -> ``left``
   - ``*.dcm`` + OD -> ``right_dicom``
   - ``*.dcm`` + OS -> ``left_dicom``
   - ``*.html`` (combined) -> ``report``
   - ``*.html`` + OD (per_eye) -> ``right_report``
   - ``*.html`` + OS (per_eye) -> ``left_report``

4. **Resolve** -- ``POST /api/retinopathy/resolve/`` confirms a
   CameraSession exists on the server for this subject.

5. **Upload** -- sends each file to the server.  Original filenames are
   preserved.  Each upload includes a SHA-256 checksum.  Multiple files
   per eye are supported.

6. **Verify** -- ``GET .../status/`` confirms the session received the
   expected files.

7. **Archive** -- the subject folder is moved to
   ``<watch-dir>/processed/<subject_id>_<timestamp>/``.

Error handling
--------------

- **Retries** -- every API call is retried up to 3 times with a
  5-second delay.

- **Failed subjects** -- if any step fails, the subject is left in
  place and retried on the next 60-second sweep.

- **Startup scan** -- on (re)start the watchdog scans all existing
  subject folders, picking up where it left off.

- **Thread safety** -- file detection and upload run on separate threads
  with proper locking.

.. |pypi| image:: https://img.shields.io/pypi/v/fundus-camera-watchdog.svg
    :target: https://pypi.python.org/pypi/fundus-camera-watchdog

.. |actions| image:: https://github.com/erikvw/fundus-camera-watchdog/actions/workflows/build.yml/badge.svg
  :target: https://github.com/erikvw/fundus-camera-watchdog/actions/workflows/build.yml

.. |codecov| image:: https://codecov.io/gh/erikvw/fundus-camera-watchdog/branch/develop/graph/badge.svg
  :target: https://codecov.io/gh/erikvw/fundus-camera-watchdog

.. |downloads| image:: https://pepy.tech/badge/fundus-camera-watchdog
   :target: https://pepy.tech/project/fundus-camera-watchdog

.. |clinicedc| image:: https://img.shields.io/badge/framework-Clinic_EDC-green
   :alt:Made with clinicedc
   :target: https://github.com/clinicedc
