Skip to content
Merged
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ magic.odm.yaml
# This directory contains the database from fluorophores.org
# To fill it, run ./util/getfluodb.py
/install/linux/usr/share/odemis/fluodb/
/install/linux/usr/share/odemis/datacollector.key

# External drivers
/src/odemis/driver/hitachi.py
Expand Down
2 changes: 2 additions & 0 deletions debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ Depends: ${shlibs:Depends},
python3-libusb1,
# Needed for acq.fastem
python3-shapely,
# Needed for communicating with AWS S3
python3-boto3,
Comment on lines +78 to +79

@coderabbitai coderabbitai Bot Apr 13, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Declared Python compatibility in debian/control:\n'
sed -n '18,20p' debian/control

printf '\nBuiltin generic annotations introduced by this PR:\n'
rg -n '\b(list|dict|tuple|set)\[[^]]+\]' \
  src/odemis/util/dc_fetch.py \
  src/odemis/util/test/dc_fetch_test.py \
  src/odemis/util/test/datacollector_test.py

Repository: delmic/odemis

Length of output: 600


🏁 Script executed:

#!/bin/bash

# Check for other Python 3.9+ features (e.g., type hints, match statements, etc.)
echo "=== Checking for union type hints (|) - Python 3.10+ syntax ==="
rg -n '\|\s*None' src/odemis/util/dc_fetch.py src/odemis/util/test/datacollector_test.py

echo "=== Checking for zoneinfo - Python 3.9+ ==="
rg -n 'from zoneinfo|import zoneinfo' src/odemis/util/dc_fetch.py src/odemis/util/test/datacollector_test.py

echo "=== All annotations added to dc_fetch.py and related tests ==="
grep -n 'def \|: ' src/odemis/util/dc_fetch.py | head -30

Repository: delmic/odemis

Length of output: 2102


Update debian/control to reflect Python 3.9+ requirement.

This PR introduces builtin generic annotations (list[str], tuple[Any, str]) which require Python 3.9+, but debian/control still declares X-Python3-Version: >= 3.6. The package will fail to import on Python 3.6–3.8. Update line 18 from python3-all-dev (>= 3.6.1) to at least >= 3.9.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@debian/control` around lines 78 - 79, Update the Python version metadata in
debian/control to require Python 3.9+: change the package dependency declaration
"python3-all-dev (>= 3.6.1)" and the "X-Python3-Version: >= 3.6" entry to at
least ">= 3.9" so that the new builtin generic annotations (e.g., list[str],
tuple[Any, str]) are supported; locate these fields in debian/control and bump
their minimum version to 3.9.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not sure how to handle it

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

Let me check the codebase for the minimum Python version already in use and the exact annotations in the new files.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== X-Python3-Version and Build-Depends in debian/control ==="
grep -n 'X-Python3-Version\|python3-all-dev' debian/control

echo ""
echo "=== Builtin generic annotations (requires Python 3.9+) in new files ==="
rg -n '\b(list|dict|tuple|set)\[' src/odemis/util/dc_fetch.py src/odemis/util/datacollector.py 2>/dev/null || echo "(none found)"

echo ""
echo "=== Existing from __future__ import annotations in codebase ==="
rg -rn 'from __future__ import annotations' src/odemis/ | head -10

echo ""
echo "=== Python 3.9+ syntax already in use elsewhere in src/odemis (sample) ==="
rg -rn '\b(list|dict|tuple|set)\[' src/odemis/ --include='*.py' -g '!test' | grep -v 'dc_fetch\|datacollector' | head -20

echo ""
echo "=== Check top of dc_fetch.py for existing __future__ import ==="
head -20 src/odemis/util/dc_fetch.py

Length of output: 544


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Top of dc_fetch.py (imports section) ==="
head -30 src/odemis/util/dc_fetch.py

echo ""
echo "=== Python 3.9+ builtin generics elsewhere in src/odemis (non-test, non-new files) ==="
rg -rn '\b(list|dict|tuple|set)\[' src/odemis/ --include='*.py' \
  --ignore-file=<(printf 'dc_fetch.py\ndatacollector.py\n') \
  -g '!*/test/*' | head -20

echo ""
echo "=== Python version declared in setup.py or pyproject.toml ==="
fd -e py setup.py | xargs grep -n 'python_requires' 2>/dev/null || echo "(not found in setup.py)"
fd pyproject.toml | xargs grep -n 'python_requires\|requires-python' 2>/dev/null || echo "(not found in pyproject.toml)"

echo ""
echo "=== Python 3.10+ union type hints (X | Y) used anywhere in new files ==="
rg -n '\w\s*\|\s*\w' src/odemis/util/dc_fetch.py src/odemis/util/datacollector.py 2>/dev/null | grep -v '#' | head -10 || echo "(none)"

echo ""
echo "=== What Python version does the target OS ship? (check /etc/os-release) ==="
cat /etc/os-release 2>/dev/null || echo "(not available in sandbox)"
python3 --version 2>/dev/null || echo "(python3 not available)"

Length of output: 1480


@K4rishma, no worries — here's a clear explanation of the issue and the easiest fix.


What's the problem?

Your new files use lowercase builtin generics like list[Path] and tuple[Any, str] directly in type annotations. This syntax (PEP 585) was only introduced in Python 3.9+. However, debian/control declares the package requires Python ≥ 3.6, so it would fail to import on older Python versions.


The easiest fix (minimal change)

dc_fetch.py already imports List, Tuple, etc. from typing at the top — you just need to use them consistently in the two function signatures where lowercase was slipped in:

In src/odemis/util/dc_fetch.py (lines 169 and 193), change:

# Before (Python 3.9+ only)
def create_s3_client_from_config(config: DataCollectorConfig) -> tuple[Any, str]:
    ...
def build_s3_client_from_config(...) -> tuple[Any, str]:
# After (Python 3.6+ compatible)
def create_s3_client_from_config(config: DataCollectorConfig) -> Tuple[Any, str]:
    ...
def build_s3_client_from_config(...) -> Tuple[Any, str]:

(Tuple is already imported from typing in that file — no new imports needed.)

In src/odemis/util/datacollector.py (line 527), change:

# Before (Python 3.9+ only)
def _pending_zip_paths(self, queue_dir: Path) -> list[Path]:
# After (Python 3.6+ compatible)
def _pending_zip_paths(self, queue_dir: Path) -> List[Path]:

And add List to the typing import in that file:

from typing import ..., List, ...

Alternative: bump the minimum Python version

If the project is already targeting a modern OS (e.g., Ubuntu 22.04 which ships Python 3.10, or Debian Bookworm which ships Python 3.11), you could instead bump the declared minimum in debian/control:

-X-Python3-Version: >= 3.6
+X-Python3-Version: >= 3.9

 Build-Depends: ...
-               python3-all-dev (>= 3.6.1)
+               python3-all-dev (>= 3.9)

But this is a wider project-level decision — I'd recommend going with the typing-based fix above as it's the smallest and safest change.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@K4rishma the code should stay Python 3.8 compatible. You should change the type annotation to be List instead of list and Dict instead of dict.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Suggests: imagej
Description: Open Delmic Microscope Software
Odemis is the acquisition software for the Delmic microscopes. In particular,
Expand Down
1 change: 1 addition & 0 deletions debian/odemis.install
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
install/linux/etc/logrotate.d/odemisd etc/logrotate.d/
install/linux/etc/sudoers.d/odemis etc/sudoers.d/
install/linux/usr/share/odemis/datacollector.key usr/share/odemis/
install/linux/usr/share/bash-completion/completions/odemis-cli usr/share/bash-completion/completions/
install/linux/usr/share/bash-completion/completions/odemis-convert usr/share/bash-completion/completions/
install/linux/usr/share/fish/vendor_completions.d/odemis.fish usr/share/fish/vendor_completions.d/
Expand Down
3 changes: 3 additions & 0 deletions debian/rules
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ export PYBUILD_DISABLE=test
dh $@ --with python3 --with sphinxdoc --buildsystem=pybuild

override_dh_auto_install:
@if [ ! -f install/linux/usr/share/odemis/datacollector.key ]; then \
echo "WARNING: Missing install/linux/usr/share/odemis/datacollector.key"; \
fi
Comment thread
K4rishma marked this conversation as resolved.
python3 setup.py install --root debian/odemis --install-layout=deb

override_dh_installdocs:
Expand Down
362 changes: 362 additions & 0 deletions doc/develop/data-framework-setup-guide.rst
Comment thread
K4rishma marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,362 @@
Odemis Data Collection Framework — Setup Guide
===============================================

**Audience:** Software and support engineers setting up data collection on an
Odemis installation or on a developer workstation.

.. note::
All commands are Bash. Enter them line by line unless stated otherwise.

This guide covers:

- Prerequisites
- Installing the boto3 package
- Creating and installing the credentials key file
- Setting up the local queue directory (``dc_queue``)
- Verifying the configuration file
- Test-bucket vs production-bucket mode
- Running the unit tests
- Quick smoke-test with ``odemis-dc-fetch``
- Troubleshooting


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

- Ubuntu 22.04 LTS or later
- Odemis installed from the Debian package or a source checkout
- AWS S3 credentials (``access_key`` + ``secret_key``) for the target bucket
(typically upload-only IAM keys).
Obtain these from a Delmic software engineer; they are **not** stored in this
repository.
- Write access to ``/usr/share/odemis/`` (for the key file)


Installing the boto3 Package
-----------------------------

``boto3`` is the AWS SDK for Python used to upload data to S3.

When installed via the Odemis Debian package it is listed as a dependency and
will be pulled in automatically:

.. code-block:: bash

sudo apt update
sudo apt install python3-boto3

To verify the installation:

.. code-block:: bash

python3 -c "import boto3; print(boto3.__version__)"

If the command prints a version string (e.g. ``1.28.0``) the package is ready.
If it raises ``ImportError``, re-run the ``apt install`` command above.


Installing the Credentials Key File
-------------------------------------

The framework reads S3 credentials from a single JSON file at: /usr/share/odemis/datacollector.key
Comment thread
K4rishma marked this conversation as resolved.

The file must contain exactly the following two keys:

Comment thread
K4rishma marked this conversation as resolved.
.. code-block:: json

{ "access_key": "<AWS_ACCESS_KEY_ID>", "secret_key": "<AWS_SECRET_ACCESS_KEY>" }

Obtain the actual key values from a Delmic software engineer. These keys are
typically scoped to upload-only access (no read/retrieve), so internal sharing
over standard channels is lower risk than full-access AWS credentials.

Do **not** commit these keys to git repositories (especially public remotes).
Public repository scrapers continuously harvest AWS key patterns, and committing
keys can also trigger GitHub secret-scanning/security warnings.


Production Setup
~~~~~~~~~~~~~~~~

Contact a software engineer who has access to the AWS IAM console to get the
key pair, then run the following commands line by line:

.. code-block:: bash

# Create the file — replace placeholder values with the real keys
sudo tee /usr/share/odemis/datacollector.key > /dev/null << 'EOF'
{ "access_key": "<PRODUCTION_ACCESS_KEY_ID>", "secret_key": "<PRODUCTION_SECRET_ACCESS_KEY>" }
EOF
Comment thread
K4rishma marked this conversation as resolved.


Setting up the Local Queue Directory (dc_queue)
-------------------------------------------------

.. note::
This section applies to the **test / developer setup** only. Skip it for a
production installation.

The framework stages serialised ZIP archives in a local directory before
uploading them. The default path is:

.. code-block:: text

~/.local/share/odemis/dc_queue

This directory is created automatically by the framework at runtime if it does
not exist, as long as the parent ``~/.local/share/odemis/`` is writable by the
process.

For a standard Odemis installation ``~/.local/share/odemis/`` is already
created by the package post-install script. If that is not the case, create it
manually:

.. code-block:: bash

sudo mkdir -p ~/.local/share/odemis/dc_queue
sudo chown $USER:$USER ~/.local/share/odemis/dc_queue
sudo chmod 750 ~/.local/share/odemis/dc_queue


Queue Disk Limit
~~~~~~~~~~~~~~~~

The framework automatically enforces a soft limit of 10 % of the partition's
total disk space on ``~/.local/share/odemis/dc_queue``. When the limit is
exceeded, the oldest ZIP files are deleted with a ``WARNING`` log entry.

- **Production:** ``~/.local/share/odemis/`` is normally on the main OS
partition. No special configuration is required.
- **Test / developer workstation:** the queue directory may be redirected to a
temporary location by instantiating ``_BackgroundWorker`` with a custom
``queue_dir`` argument (used in unit tests). For manual testing with the real
Odemis GUI, the default path is always used.


Inspecting Queued Files
~~~~~~~~~~~~~~~~~~~~~~~

.. code-block:: bash

ls -lh ~/.local/share/odemis/dc_queue/

Files follow the naming convention::

<event_name>-<YYYYMMDDTHHmmss>-<uuid8>.zip

Any file ending in ``.tmp`` is an incomplete write left over from a crash; the
framework removes such files on startup. You can delete them manually if Odemis
is not running:

.. code-block:: bash

rm -f ~/.local/share/odemis/dc_queue/*.tmp


Verifying the Configuration File
----------------------------------

The per-user consent state is stored in:

.. code-block:: text

~/.config/odemis/datacollector.config

This file is created automatically the first time the user interacts with the
consent dialog from the menu. Its permissions are always set to ``0600``
(owner read/write only) by the framework.

A typical file after opt-in looks like:

.. code-block:: ini

[general]
# Data sharing consent (true / false).
consent = true


.. _test-bucket-mode:

Test-Bucket vs Production-Bucket Mode
---------------------------------------

Production (Default)
~~~~~~~~~~~~~~~~~~~~~

- **Bucket:** ``delmic-odemis-collect``
- **Region:** ``eu-west-1``

This is the default when the framework starts normally. No environment variable
needs to be set. Use the production IAM credentials in the key file
(see `Installing the Credentials Key File`_).


Test / Developer Mode
~~~~~~~~~~~~~~~~~~~~~~

- **Bucket:** ``delmic-odemis-collect-test``
- **Region:** ``eu-west-1``

Set the following environment variable before starting Odemis (or any script
that calls ``record()``):

.. code-block:: bash

export TEST_DATACOLLECTION=1

The framework logs the following ``INFO`` message when test mode is active::

DataCollector: TEST_DATACOLLECTION=1 — using test bucket 'delmic-odemis-collect-test'

.. important::
The test bucket credentials (``access_key`` / ``secret_key`` in the key
file) must be scoped to the **test** bucket by the Delmic AWS admin. If you
install production credentials and set ``TEST_DATACOLLECTION=1``, uploads
will fail with an ``AccessDenied`` error because the IAM policy only permits
writes to the production prefix.


Managing Consent
----------------

The data collection consent dialog is accessed via the Odemis menu. Users have
three options:

- **Opt In:** Enable data collection permanently.
- **Opt Out:** Disable data collection permanently.
- **Consent for One Day:** Enable data collection temporarily for one day only.
After the specified day, consent automatically expires to disabled.

When temporary consent is active and less than one day remains, the collection
sampling rate increases to 100% to prioritize data collection. Otherwise, a
default 10% sampling rate applies.

When consent is active, collected data is staged in the queue directory and
uploaded to S3 in the background. When consent is disabled, no data is collected
or uploaded.


Running the Unit Tests
-----------------------

Unit tests run without any hardware and without a real S3 connection. All
upload calls are mocked.

.. code-block:: bash

# From the repository root
env TEST_NOHW=1 python3 src/odemis/util/test/datacollector_test.py

To run a specific test class or method:

.. code-block:: bash

env TEST_NOHW=1 python3 src/odemis/util/test/datacollector_test.py \
DataCollectorTest.test_record_returns_fast

env TEST_NOHW=1 python3 src/odemis/util/test/datacollector_test.py \
TestSerialize.test_metadata_json_envelope_fields


Real S3 Integration Tests
~~~~~~~~~~~~~~~~~~~~~~~~~~

The class ``TestRealS3Integration`` uploads to the test bucket and cleans up
after itself. It requires:

- ``boto3`` installed
- ``/usr/share/odemis/datacollector.key`` present with test bucket credentials
- ``TEST_DATACOLLECTION=1`` is **not** needed here — the test class hard-codes
the test bucket directly

.. code-block:: bash

env TEST_NOHW=1 python3 src/odemis/util/test/datacollector_test.py \
TestRealS3Integration

If the key file is absent, this class is skipped automatically.


Quick Smoke-Test with odemis-dc-fetch
---------------------------------------

After a successful upload (production or test), use the retrieval script to
confirm objects landed in S3. The retrieval script can only be used with an AWS
profile that has data-analyst read access; the upload key in
``/usr/share/odemis/datacollector.key`` is typically write-only and usually
does not grant retrieval rights.

.. code-block:: bash

odemis-dc-fetch \
--bucket delmic-odemis-collect \
--region eu-west-1 \
--output ./dc_samples_test

Filter by event name and date:

.. code-block:: bash

odemis-dc-fetch \
--bucket delmic-odemis-collect-test \
--region eu-west-1 \
--event feature_collected \
--since 2026-04-01 \
--output ./dc_samples_test

The script prints a one-line summary::

listed=N matched=N downloaded=N skipped_existing=N failed=0

If ``failed`` is non-zero, check the log output for ``AccessDenied`` or network
errors and verify the key file credentials.


Troubleshooting
----------------

**Problem:** ``LookupError: S3 credentials key file not found at /usr/share/odemis/datacollector.key``

**Solution:** Create the key file as described in
`Installing the Credentials Key File`_.

----

**Problem:** ``botocore.exceptions.ClientError: AccessDenied``

**Cause:** The IAM key in the key file does not have permission to write to the
bucket being targeted.

**Solution:** Ensure the key file contains credentials matching the target
bucket (production key → production bucket, test key → test bucket). Check
whether ``TEST_DATACOLLECTION=1`` is set unexpectedly.

----

**Problem:** Uploads never happen; queue fills up.

**Cause:** Network is unavailable or credentials are wrong.

**Solution:** Check the Odemis log for ``DataCollector upload failed`` entries.
The framework retries with exponential back-off (30 s → 60 s → ... up to 1 h).
Pending ZIPs remain in ``~/.local/share/odemis/dc_queue/`` and are flushed
oldest-first once connectivity is restored.

----

**Problem:** ``Queue limit exceeded: removed oldest sample`` appears in the log.

**Cause:** The queue directory has grown beyond 10 % of the partition.

**Solution:** Check disk space with ``df -h ~/.local/share/odemis/dc_queue``.
Investigate why uploads are not succeeding (credentials, network).

----

**Problem:** ``TEST_DATACOLLECTION=1`` is set but uploads still go to
production.

**Solution:** The environment variable is read at the
moment ``get_upload_backend()`` is called, which is lazy (first upload attempt).
Verify the variable is exported in the same shell/environment that runs the
Odemis process.
Loading
Loading