Skip to content
Merged
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
37 changes: 37 additions & 0 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Unit Tests

# Runs the Python unit tests in tests/ (pytest). This is intentionally
# separate from build-and-test.yml, whose "test" jobs are container/k8s
# deployment smoke tests rather than pytest.

on:
push:
branches: [develop]
pull_request:
workflow_dispatch:

jobs:
pytest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
# Match the runtime built in the Dockerfile (python=3.11).
python-version: "3.11"
cache: pip
cache-dependency-path: requirements.txt

- name: Install dependencies
run: |
python -m pip install --upgrade pip
# Pinned runtime deps (pyopenms is needed so ParameterManager imports
# cleanly at collection time) plus test-only deps. fakeredis backs the
# QueueManager/WorkflowManager tests, which pytest.importorskip it.
pip install -r requirements.txt
pip install pytest fakeredis

- name: Run unit tests
run: pytest tests/ -v
7 changes: 7 additions & 0 deletions src/render/compression.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ def downsample_heatmap(data, max_datapoints=20000, rt_bins=400, mz_bins=50, logg

# We need to collect here because scipy requires numpy arrays
sorted_data = sorted_data.collect()

# scipy's binned_statistic_2d reduces over the inputs to derive bin edges
# and raises "zero-size array to reduction operation minimum" on empty
# input. With no peaks there is nothing to downsample, so return the input
# unchanged (same schema, zero rows).
if sorted_data.is_empty():
return data

# Count peaks
total_count = sorted_data.select(pl.count()).item()
Expand Down
50 changes: 50 additions & 0 deletions tests/test_render_compression.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""
Tests for downsample_heatmap, the heatmap point-reduction helper.

The deconvolved-heatmap panels are built per MS level, and a level with no
peaks (e.g. an MS1-only run that still shows an MS2 panel) yields an empty
frame. That empty frame used to reach scipy's binned_statistic_2d, which
raises "zero-size array to reduction operation minimum which has no identity"
while computing bin edges. downsample_heatmap must short-circuit on empty
input and return an empty, schema-preserving frame instead of crashing.

The helper is pure polars/numpy/scipy (no Streamlit), so it is unit-testable
without booting the app.
"""

import os
import sys

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

import polars as pl

from src.render.compression import downsample_heatmap


HEATMAP_SCHEMA = {"mass": pl.Float64, "rt": pl.Float64, "intensity": pl.Float64}


def test_empty_dataframe_returns_empty_with_schema():
result = downsample_heatmap(pl.DataFrame(schema=HEATMAP_SCHEMA)).collect()
assert result.is_empty()
assert set(result.columns) >= {"mass", "rt", "intensity"}


def test_empty_lazyframe_returns_empty_with_schema():
result = downsample_heatmap(pl.LazyFrame(schema=HEATMAP_SCHEMA)).collect()
assert result.is_empty()
assert set(result.columns) >= {"mass", "rt", "intensity"}


def test_nonempty_input_passes_through_binning():
df = pl.DataFrame(
{
"mass": [100.0, 200.0, 300.0],
"rt": [1.0, 2.0, 3.0],
"intensity": [10.0, 20.0, 30.0],
}
)
result = downsample_heatmap(df).collect()
assert set(result.columns) >= {"mass", "rt", "intensity"}
assert result.height <= df.height
Loading