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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ Versions follow [Semantic Versioning](https://semver.org) (`<major>.<minor>.<pat

## Unreleased

### Added

- Add automated QA check results to preprocessing reports ([[#80](https://github.com/DuguidLab/mesoscopy/issues/80)])
- Add timedelta series to preprocessing report.

### Fixed

- Pin pynwb version to pre-3.0, which currently breaks installs.

## [0.7.3]

### Added
Expand Down
24 changes: 24 additions & 0 deletions src/mesoscopy/preprocess/qa.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,30 @@ def plot_timestamps(timestamps: list | npt.NDArray, as_html: bool = False) -> st
return fig


def plot_timestamp_jumps(
timestamps: npt.NDArray | list, std_threshold: float = 2.0, as_html: bool = False
) -> str | go.Figure:
try:
timestamps = np.array([datetime.fromisoformat(str(ts, encoding="utf-8")) for ts in np.array(timestamps)])
except ValueError:
timestamps = np.array([float(ts) for ts in timestamps])
timedeltas = np.diff(timestamps).astype("timedelta64[ms]").astype(float)
zscored_intervals = stats.zscore(timedeltas)

fig = go.Figure(
layout=go.Layout(
xaxis={"title": {"text": "Frame index"}},
yaxis={"title": {"text": "Timedelta (ms)"}},
margin={"l": 20, "r": 20, "t": 20, "b": 20},
)
)

fig.add_trace(go.Scatter(y=timedeltas, mode="lines", name="Timestamp"))
if as_html:
return fig.to_html(full_html=False)
return fig


def plot_raw_timeseries(raw_timeseries: npt.NDArray, as_html: bool = False) -> str | go.Figure:
"""Plots a raw timeseries signal using Plotly.

Expand Down
13 changes: 13 additions & 0 deletions src/mesoscopy/report/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,23 @@ def generate_preprocessing_report(path: str, out_dir: str = ".") -> str:
),
"frame_num": len(preproc.get("F")), # type: ignore[attr-defined]
"filesize": preproc.id.get_filesize() / (1024 * 1024),
"qa_histogram_separation": preproc.attrs.get("/qa/checks/histogram_separation"),
"qa_timestamp_consistency": preproc.attrs.get("/qa/checks/timestamp_consistency"),
"qa_timestamp_jump": preproc.attrs.get("/qa/checks/timestamp_jump"),
"qa_check_noise": preproc.attrs.get("/qa/checks/noise_check"),
"qa_median_noise": np.median(np.array(preproc.get("/qa/noise_levels", 0))),
"qa_check_snr": preproc.attrs.get("/qa/checks/snr_check"),
"qa_snr": preproc.attrs.get("/qa/checks/snr"),
"qa_check_bleaching": preproc.attrs.get("/qa/checks/bleaching_check"),
"qa_bleaching_factor": preproc.attrs.get("/qa/checks/bleaching_factor"),
"fig_integrity_timestamps": preqa.plot_timestamps(
[np.datetime64(ts) for ts in preproc.get("timestamps")], # type: ignore[attr-defined]
as_html=True,
),
"fig_integrity_timestamp_jumps": preqa.plot_timestamp_jumps(
preproc.get("timestamps"), # type: ignore[attr-defined]
as_html=True,
),
"fig_separation_pre_timeseries_mean": preqa.plot_raw_timeseries(
preproc.get("qa").get("frame_means_timeseries"), # type: ignore[attr-defined]
as_html=True,
Expand Down
57 changes: 52 additions & 5 deletions src/mesoscopy/report/templates/preprocessing.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,22 @@
</svg>
Metadata
</a> </li>
<li class="nav-item"> <a class="nav-link d-flex align-items-center gap-2" href="#autoqa">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-flag"
viewBox="0 0 16 16">
<path
d="M14.778.085A.5.5 0 0 1 15 .5V8a.5.5 0 0 1-.314.464L14.5 8l.186.464-.003.001-.006.003-.023.009a12 12 0 0 1-.397.15c-.264.095-.631.223-1.047.35-.816.252-1.879.523-2.71.523-.847 0-1.548-.28-2.158-.525l-.028-.01C7.68 8.71 7.14 8.5 6.5 8.5c-.7 0-1.638.23-2.437.477A20 20 0 0 0 3 9.342V15.5a.5.5 0 0 1-1 0V.5a.5.5 0 0 1 1 0v.282c.226-.079.496-.17.79-.26C4.606.272 5.67 0 6.5 0c.84 0 1.524.277 2.121.519l.043.018C9.286.788 9.828 1 10.5 1c.7 0 1.638-.23 2.437-.477a20 20 0 0 0 1.349-.476l.019-.007.004-.002h.001M14 1.221c-.22.078-.48.167-.766.255-.81.252-1.872.523-2.734.523-.886 0-1.592-.286-2.203-.534l-.008-.003C7.662 1.21 7.139 1 6.5 1c-.669 0-1.606.229-2.415.478A21 21 0 0 0 3 1.845v6.433c.22-.078.48-.167.766-.255C4.576 7.77 5.638 7.5 6.5 7.5c.847 0 1.548.28 2.158.525l.028.01C9.32 8.29 9.86 8.5 10.5 8.5c.668 0 1.606-.229 2.415-.478A21 21 0 0 0 14 7.655V1.222z" />
</svg>
Auto-QA
</a> </li>
<li class="nav-item"> <a class="nav-link d-flex align-items-center gap-2" href="#integriy">
<svg class="bi" aria-hidden="true">
<use xlink:href="#gear-wide-connected"></use>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-stopwatch"
viewBox="0 0 16 16">
<path d="M8.5 5.6a.5.5 0 1 0-1 0v2.9h-3a.5.5 0 0 0 0 1H8a.5.5 0 0 0 .5-.5z" />
<path
d="M6.5 1A.5.5 0 0 1 7 .5h2a.5.5 0 0 1 0 1v.57c1.36.196 2.594.78 3.584 1.64l.012-.013.354-.354-.354-.353a.5.5 0 0 1 .707-.708l1.414 1.415a.5.5 0 1 1-.707.707l-.353-.354-.354.354-.013.012A7 7 0 1 1 7 2.071V1.5a.5.5 0 0 1-.5-.5M8 3a6 6 0 1 0 .001 12A6 6 0 0 0 8 3" />
</svg>
Integrity
Timeseries integriy
</a>
</li>
<li class="nav-item"> <a class="nav-link d-flex align-items-center gap-2" href="#separation">
Expand Down Expand Up @@ -72,12 +83,48 @@ <h2 id="metadata">Session metadata</h2>
</div>

<div class="row mb-3">
<h2 id="integrity">Recording integrity</h2>
<h2 id="autoqa">Preprocessing auto-QA checks</h2>

<div class="col-md-12 mb-3 mt-3">
<div class="row mb-1">
<div class="col-md-4 mb-1">
<strong>Histogram separation:</strong> {{ "✅" if qa_histogram_separation else "❌" }}
</div>
<div class="col-md-4 mb-1">
<strong>Timestamp consistency:</strong> {{ "✅" if qa_timestamp_consistency else "❌" }}
</div>
<div class="col-md-4 mb-1">
<strong>Timestamp jumps:</strong> {{ "✅" if qa_timestamp_jump else "❌" }}
</div>
</div>
<div class="row mb-1">
<div class="col-md-4 mb-1">
<strong>Noise levels:</strong> {{ "✅" if qa_check_noise else "❌" }} (Median noise {{ '%0.2f'|
format(qa_median_noise|float) if qa_median_noise is defined else "Unknown" }} %)
</div>
<div class="col-md-4 mb-1">
<strong>Signal-to-Noise:</strong> {{ "✅" if qa_check_snr else "❌" }} ({{ '%0.2f'| format(qa_snr|float) if
qa_snr is
defined else "Unknown" }} SNR)
</div>
<div class="col-md-4 mb-1">
<strong>Bleaching drift:</strong> {{ "✅" if qa_check_bleaching else "❌" }} ({{ '%0.2f'|
format(qa_bleaching_factor|float) if qa_bleaching_factor is defined else "Unknown" }} BF)
</div>
</div>
</div>

<div class="row mb-3">
<h2 id="integrity">Timeseries integrity</h2>

<div class="col-md-6 mb-3 mt-3">
<h6>Timestamp series</h6>
{{ fig_integrity_timestamps | safe }}
</div>

<div class="col-md-6 mb-3 mt-3">
<h6>Timedelta series</h6>
{{ fig_integrity_timestamp_jumps | safe }}
</div>
</div>

<div class="row mb-3">
Expand Down