Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ updates:
- '/documentation'
- '/segmenter'
- '/controller'
- '/reporter'
patterns: ['*']
multi-ecosystem-group: 'deps'

Expand Down
55 changes: 55 additions & 0 deletions .github/workflows/build-reporter.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: Build the reporter

on:
push:
branches:
- 'main'
- 'software/beta'
- 'software/stable'
tags:
- 'reporter/v*'
paths:
- 'reporter/**'
- '.github/workflows/build-reporter.yml'
pull_request:
paths:
- 'reporter/**'
- '.github/workflows/build-reporter.yml'
merge_group:
workflow_dispatch:
inputs:
git-ref:
description: 'Git ref (optional)'
required: false

env:
IMAGE_REGISTRY: ghcr.io/${{ github.repository_owner }}
IMAGE_NAME: 'reporter'
MAIN_BRANCH: 'main' # pushing to this branch will update the "edge" tag on the image
BETA_BRANCH: 'software/beta' # pushing to this branch will update the "beta" tag on the image
STABLE_BRANCH: 'software/stable' # pushing to this branch will update the "stable" tag on the image
TAG_PREFIX: 'reporter/v' # pushing tags with this prefix will add a version tag to the image and update the "latest" tag on the image
PUSH_IMAGE: ${{ (github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork) || github.event_name == 'push' || github.event_name == 'push tag' }}

jobs:
ci-checks:
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v5

- name: Install uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true

- name: Set up Python
run: uv python install

- name: Install dependencies
working-directory: ./reporter
run: |
uv sync --locked --dev

- name: Run checks
working-directory: ./reporter
run: uv run poe check
1 change: 1 addition & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export default defineConfig([
globalIgnores([
"controller/.venv",
"segmenter/.venv",
"reporter/.venv",
"frontend/dist",
"frontend/public",
]),
Expand Down
3 changes: 3 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ setup:
just --justfile node-red/justfile setup
just --justfile controller/justfile setup
just --justfile segmenter/justfile setup
just --justfile reporter/justfile setup
just --justfile os/justfile setup
just --justfile backend/justfile setup
just --justfile frontend/justfile setup
Expand All @@ -24,6 +25,7 @@ setup-dev:
just --justfile node-red/justfile setup-dev
just --justfile controller/justfile setup-dev
just --justfile segmenter/justfile setup-dev
just --justfile reporter/justfile setup-dev
just --justfile os/justfile setup-dev
just --justfile backend/justfile setup-dev
just --justfile frontend/justfile setup-dev
Expand All @@ -41,6 +43,7 @@ test:
just --justfile node-red/justfile test
just --justfile controller/justfile test
just --justfile segmenter/justfile test
just --justfile reporter/justfile test
just --justfile os/justfile test
just --justfile backend/justfile test
just --justfile frontend/justfile test
Expand Down
2 changes: 1 addition & 1 deletion node-red/projects/dashboard
Submodule dashboard updated 1 files
+125 −16 flows.json
1 change: 0 additions & 1 deletion os/image/rpios.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

import assert from "node:assert"
import { basename, join } from "node:path"
import { fileURLToPath } from "node:url"
import { access, readFile, readlink } from "node:fs/promises"
import { once } from "node:events"

Expand Down
140 changes: 140 additions & 0 deletions reporter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# reporter

The PlanktoScope's PDF report generator.

## Introduction

This service renders a self-contained PDF acquisition report for one or more
segmented acquisitions. It reads each acquisition's `metadata.json`, its EcoTaxa
TSV, the flat-field image and a selection of ROI thumbnails, recomputes the same
quality-control statistics the Validation dashboard shows, draws the charts with
matplotlib, and converts an HTML template to PDF with WeasyPrint. Everything —
fonts, images and charts — is embedded, so the report renders offline in the
field and needs no external assets.

The report is triggered from the dashboard's "Report export" dialog over MQTT and
written to `/home/pi/data/reports/`, where the dashboard serves it for download.

## Usage

### Development

Install runtime dependencies and (re)install the systemd service:

```sh
cd reporter
just setup
```

Install all dependencies including development tooling:

```sh
just setup-dev
```

Start the reporter for development (stops the service first):

```sh
just dev
# make changes and restart
```

Run the code auto-formatter on the project:

```sh
just format
```

Run all checks (formatting and linting):

```sh
just test
```

### Prerequisites

To use this project, you'll need:

- Python >= 3.13.5
- uv
- Pango and harfbuzz-subset (WeasyPrint's native dependencies — `just setup`
installs them)

An MQTT broker must be reachable on port 1883 of the host. We recommend
[Mosquitto](https://mosquitto.org/).

### Rendering without MQTT

For local iteration you can render straight from a dialog payload, bypassing the
broker:

```sh
uv run main.py --payload '{"acquisition_paths":["/home/pi/data/img/<date>/<sample>/<acq>"]}'
```

The output directory defaults to `/home/pi/data/reports` and can be overridden
with the `REPORTER_OUTPUT` environment variable.

## API

The reporter subscribes to `actuator/reporter/generate` and publishes progress to
`status/reporter`.

### Generate a report

**topic** `actuator/reporter/generate`

**payload** (combined report — one PDF covering a selection of acquisitions, as
sent by the "Report export" dialog):

```json
{
"action": "generate",
"acquisition_paths": [
"/home/pi/data/img/<date>/<sample>/<acq>"
],
"sections": { "...": "which report sections to include (optional)" },
"gallery": { "...": "object-gallery options (optional)" }
}
```

A legacy single-acquisition payload is also accepted; it produces the full report
plus a one-page summary:

```json
{
"action": "generate",
"acquisition_path": "/home/pi/data/img/<date>/<sample>/<acq>"
}
```

### Status

**topic** `status/reporter`

While working the reporter publishes `{"status": "generating", ...}`. On success
it publishes the download location:

```json
{
"status": "done",
"filename": "<project>_<sample>_<acq>_report.pdf",
"url": "/api/files/reports/<project>_<sample>_<acq>_report.pdf"
}
```

On failure it publishes `{"status": "error", "error": "<message>"}`.

## Licensing

Except where otherwise indicated, source code provided here is covered by the
following information:

Copyright PlanktoScope project contributors

SPDX-License-Identifier: GPL-3.0-or-later

You can use the source code provided here under the [GPL 3.0 License](https://www.gnu.org/licenses/gpl-3.0.en.html).

Bundled IBM Plex fonts in `fonts/` are licensed under the SIL Open Font License;
see `fonts/LICENSE-IBMPlex.txt`.
Binary file added reporter/fonts/IBMPlexMono-Medium.ttf
Binary file not shown.
Binary file added reporter/fonts/IBMPlexMono-Regular.ttf
Binary file not shown.
Binary file added reporter/fonts/IBMPlexMono-SemiBold.ttf
Binary file not shown.
Binary file added reporter/fonts/IBMPlexSans-Medium.ttf
Binary file not shown.
Binary file added reporter/fonts/IBMPlexSans-Regular.ttf
Binary file not shown.
Binary file added reporter/fonts/IBMPlexSans-SemiBold.ttf
Binary file not shown.
93 changes: 93 additions & 0 deletions reporter/fonts/LICENSE-IBMPlex.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
Copyright © 2017 IBM Corp. with Reserved Font Name "Plex"

This Font Software is licensed under the SIL Open Font License, Version 1.1.

This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL


-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
20 changes: 20 additions & 0 deletions reporter/justfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
setup:
# WeasyPrint renders through Pango; harfbuzz-subset embeds subset fonts in the PDF
sudo apt install -y --no-install-recommends libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz-subset0
uv sync --no-group dev
sudo cp planktoscope-org.reporter.service /etc/systemd/system/
sudo systemctl reenable planktoscope-org.reporter
sudo systemctl restart planktoscope-org.reporter

setup-dev:
uv sync --group dev

dev:
-sudo systemctl stop planktoscope-org.reporter
uv run main.py

format:
uv run poe fmt

test:
uv run poe check
Binary file added reporter/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading