Skip to content

Exploring the Report

Open your report in one browser tab and keep this page in another. No report of your own yet? Open the example dataset used for the screenshots below and follow along with that instead.

The example screenshots below are from the WHOI plankton microscopy dataset, which we deliberately tampered with to create four synthetic conditions: original images, blurred images, JPEG-compressed images, and noisy images. This makes it easy to see what each widget catches.

A few things to know before you start:

  • Some charts collapse into a table. If all your images share the same value for a property (e.g. they're all the same size, or all the same dtype), Pixel Patrol shows a summary table instead of a chart.
  • Widget availability depends on what was processed. Widgets that need data not collected simply don't appear. The colored pill on each card tells you what's required.
  • Everything reacts to the sidebar. Grouping, filters, and dimension selectors update all widgets instantly - we'll cover the sidebar at the end.

Reading the cards:

always shown  visible in every report    needs loader  requires an image loader (e.g. pixel-patrol-loader-bio)    needs pixel-patrol-image  quality metrics package    multidim only  non-spatial dimensions (e.g. Z/T/C/S) with >1 slice

Each widget also carries a small scope badge telling you what one datapoint represents - hover it for details:

πŸ“„ per file  one datapoint = one file    πŸ–ΌοΈ per image  one datapoint = one image, even if its file holds several    🧩 per slice  one datapoint = one channel / Z-plane / timepoint within an image    πŸ–ΌοΈ/🧩  switchable with the Slice by toggle

Opening your report

pixel-patrol view report.parquet

This starts a local server with native DuckDB and opens the viewer in your browser - recommended for any non-trivial dataset. You can also go to ida-mdc.github.io/pixel-patrol/viewer/ and drag your own .parquet in directly (browser-WASM, practical limit ~2 GB).

Useful launch flags:

pixel-patrol view report.parquet --group-by file_extension   # start grouped differently
pixel-patrol view report.parquet --filter-col dtype --filter-op eq --filter-val uint16
pixel-patrol view report.parquet --dim z=5                   # lock to a Z slice on load
pixel-patrol view report.parquet --significance              # show stat brackets from the start

Widget walkthrough

βš™ What's in your report?

Answer the three questions below and the cards for widgets that don't apply to your setup will dim out as you scroll through the walkthrough - so you can see at a glance which ones are actually relevant to you.

Did you run processing with an image loader (e.g. pixel-patrol-loader-bio)?
Did you have pixel-patrol-image installed at processing time?
Are your images multidimensional (e.g. Z, T, C, or S axes)?

Click the βœ“ in the corner of each widget card to mark it as reviewed and track your progress through the walkthrough.

0 / 13 widgets reviewed

πŸ“‹ File Data Summary πŸ“„ per file always shown

Your first sanity check: a summary for the whole dataset (files, images, total size, file extensions) - when grouped, a per-group breakdown table of file count and size.

File Data Summary
Summary tiles for the dataset, plus a per-group table - all four groups have the same file count, but condition3_comp is 10Γ— smaller on disk. Already suspicious before looking at a single pixel.
🚩
Uneven group sizes can skew statistics and significance tests.
⚠️
One condition being much smaller on disk than the others (same file count, much less data) is a hint of a possible issue - images that differ in size or dtype, or compression. The next few widgets will help you tell which it is.

πŸ“‹ Image Table πŸ–ΌοΈ per image always shown

A sortable, searchable table with one row per image, showing every column except thumbnails and other binary/array data. Click a column header to sort; press Enter in the search box to search by substring - across all columns for datasets under 10,000 images, or just path/child_id for larger ones.

πŸ’‘
The fastest way to find a specific file or check raw values before trusting a chart - sort by a metric to see the exact numbers behind the most extreme points in the violin plots.
πŸ’‘
Use Save as CSV in the sidebar if you want the full table (filtered or not) outside the browser.

πŸ“ File Statistics πŸ“„ per file always shown

File count and total size by extension, file size distribution, and a modification timeline.

πŸ”¬ How it's computed
File metadata only - no pixel data. Sizes from the filesystem; modification timestamps from each file's mtime.
File Count by Extension
File count by extension - condition3_comp is the only group with .jpeg files. JPEG compression probably explains the smaller file sizes.
File Count by Size Bin
File size distribution - the compressed JPEG images cluster in the small-size bins on the left; originals and noisy images spread to the right.
🚩
One condition has a different file format than the others. In this example, only condition3_comp has .jpeg files. Mixed extensions across groups can mean a mixed dataset - images exported in two formats, or even saved twice.
⚠️
Modification dates spread across weeks or months - e.g. one condition acquired in March and another in May - you may want to check for a batch effect.
βœ…
Most properties collapsed to tables and modification dates are clustered together - in many cases, that points to consistent acquisition.

πŸŒ€ File System Structure πŸ“„ per file always shown

An interactive sunburst of your folder hierarchy. A toggle lets you size the segments by file count or by total size - the screenshot below shows the by-size view. Only files that were actually scanned into the report are included. Click any segment to zoom in; click the center ring to zoom out.

File Structure Sunburst
The 4 conditions at the second ring, each split into species subfolders - each file appears at the outer edge. Hover any segment to see the path and count.
πŸ’‘
Use this to verify your dataset is structured as expected.

🧬 Metadata πŸ–ΌοΈ per image needs loader

Distribution of dtype and dim_order across groups, plus a summary table of other properties - ndim and pixel size, where available - for the cases where every file shares the same value.

🚩
If dtype, dim_order, or ndim vary across your files, that points to inconsistent acquisition - possibly different instruments or different acquisition parameters - and is worth verifying before you compare groups. Mixed-dtype example: uint8 lives in [0, 255] while uint16 lives in [0, 65535], so a pixel value of 100 means something completely different in each.
βœ…
Single dtype, dim_order, and ndim across the whole dataset - format-level consistency confirmed.

πŸ“ Dimension Size Distribution πŸ–ΌοΈ per image needs loader

X/Y scatter plot (width vs. height, one point per image) plus strip plots for any non-spatial dimensions present (e.g. Z, T, C, S - names depend on your loader). Quickly reveals outliers and whether your images are consistently sized across groups.

X vs Y size scatter
X vs Y size - plankton images vary widely (some organisms are much larger). No tight cluster here is expected for a species-diverse dataset.
Y size distribution
Y size violin - condition1_org has the widest spread, including one very tall image (~580px). That outlier is worth investigating.
🚩
Two distinct clusters in the X/Y scatter - e.g. a 1024Γ—1024 group and a separate 512Γ—512 group - often indicates images from different instruments, different acquisition parameters, or accidentally included thumbnails.
πŸ’‘
Hover over any point to see the file path. A single size outlier is often a misplaced metadata file or a preview image saved alongside real data.

πŸ–ΌοΈ Image Mosaic πŸ–ΌοΈ per image needs loader

A thumbnail grid - one patch per image, border color = group. Sort by any metric and your statistics become something you can actually look at and verify with your own eyes.

πŸ”¬ How it's computed
The thumbnail processor extracts a 64Γ—64-pixel patch per image. For large images it samples across the full spatial extent. For Z-stacks it takes the middle slice. For multi-channel images it picks the most informative channel (RGB if present, otherwise the first). Each patch is independently intensity-normalized so details are visible regardless of absolute brightness.
Image Mosaic
Plankton thumbnails from all four conditions - border colors indicate group. Sort by laplacian_variance ascending and the blurred/out-of-focus images will float to the top.
πŸ’‘
Sort by laplacian_variance ascending β†’ blurriest images appear first. If they look visually out-of-focus, they are. This is the fastest path to finding focus-drift candidates for exclusion.
πŸ’‘
Sort by blocking_index descending to surface JPEG-artifact images. Sort by max_intensity descending to surface potentially saturated ones.
πŸ’‘
Hover over any thumbnail to see the exact file path.

πŸ“ˆ Pixel Value Statistics πŸ–ΌοΈ/🧩 per image / slice needs loader

Violin + box plots for four per-image statistics: mean_intensity, std_intensity, min_intensity, max_intensity. Each dot is one image; each violin is one group. Use the Slice by toggles above the plots to switch to one point per (image Γ— dimension slice) instead.

πŸ”¬ How it's computed
mean: np.nanmean over all pixels, pixel-count-weighted across 2D planes.
std: pooled - √(Ξ£nα΅’(Οƒα΅’Β² + (ΞΌα΅’ βˆ’ ΞΌΜ„)Β²) / Ξ£nα΅’) - accounts for both within-plane and between-plane variance.
min / max: np.nanmin / np.nanmax per plane, then min/max across planes.
NaN pixels are excluded from all calculations.
Max Intensity violin
max_intensity distribution - condition2_bl (blurred) has a wide, low distribution. condition1_org clusters tightly near 255. Each dot is one image; hover to identify the file.
🚩
max_intensity sitting exactly at 255 (or 65535) is worth a closer look - it can mean those images hit the sensor's ceiling and got clipped. Check the histogram for a spike at the max value to confirm real saturation (a single bright pixel can also legitimately land at 255), then verify visually in the mosaic.
⚠️
Large spread in mean_intensity within a single group: variable illumination, inconsistent staining, or genuinely variable signal.
πŸ’‘
Enable Show significance in the sidebar for Mann-Whitney U brackets (Bonferroni corrected). A bracket = statistically distinguishable groups - whether that's meaningful is your call.

πŸ“Š Pixel Value Histograms πŸ–ΌοΈ per image needs loader

The mean pixel intensity histogram per group - a more in-depth look at whether your pixel intensity distribution looks the way you'd expect. Toggle between a normalized 0-255 view and the native range of your data.

πŸ”¬ How it's computed
Each image gets a 256-bin histogram of its pixel values, normalized to sum to 1. Per-group, histograms are averaged using pixel-count weighting - larger images contribute proportionally more.
Intensity histograms
The four conditions have strikingly different distributions: condition2_bl (blurred, orange) peaks around 190 - much brighter on average. condition3_comp (JPEG, cyan) is spread unusually flat. All four peak at 0 (dark background), but to very different degrees.
πŸ’‘
Comparing histograms across groups side by side is a quick way to look at conditions in more depth than a single summary statistic can show.
⚠️
Distributions shifted between groups: could be real biology, or different acquisition settings on different days.
βœ…
Distribution looks the way you'd expect for your sample, and is consistent across groups that should be comparable. (Not every dataset should be bell-shaped, and not every condition should look alike - judge against your own expectations, not a generic template.)

🎯 Image Quality Metrics πŸ–ΌοΈ/🧩 per image / slice needs loader needs pixel-patrol-image

Five quality metrics computed per leaf slice, aggregated by pixel-count-weighted mean. Together they surface a wide range of issues - focus problems, low contrast, sensor noise, uneven texture, and compression artifacts - not just one of these. Only compare images at the same magnification and pixel size - all these metrics are scale-dependent.

Laplacian Variance - sharpness / focus quality

The go-to blur detector. High = sharp; low = blurry or out-of-focus.

πŸ”¬ How it's computed
Apply the discrete Laplacian at every pixel: left + right + up + down βˆ’ 4 Γ— center. This second-derivative filter fires strongly at edges and fine texture, near-zero in smooth regions. Blurry image β†’ small response β†’ low variance. Sharp image β†’ large response β†’ high variance.
🚩
Low outliers in the violin β†’ go to the mosaic, sort by laplacian_variance ascending, and confirm visually. If they look out-of-focus, maybe you want to exclude them.
⚠️
One condition consistently blurrier: different imaging days, focus protocols, or objective settings?
Michelson Contrast - local dynamic range

How much local contrast the image has - whether pixel values vary substantially within small neighborhoods.

πŸ”¬ How it's computed
For every 3Γ—3 window, compute max βˆ’ min (local intensity range). Average all local ranges across the image. Divide by the global spatial standard deviation. Higher = more local contrast relative to overall spread.
⚠️
Very low Michelson contrast: image is "flat" - possible underexposure, oversaturation, or genuinely low-contrast sample. Compare with the histogram to distinguish.
MSCN Variance - noise sensitivity (BRISQUE)

A no-reference quality metric sensitive to both noise and blur.

πŸ”¬ How it's computed
Normalize each pixel by subtracting its 3Γ—3 local mean and dividing by local std (+ small stabilizer). In a well-structured image this normalized map approaches zero everywhere. The variance of that map measures residual complexity: noisy β†’ high; blurry β†’ low.
⚠️
Abnormally high: pixel-level noise - possibly high sensor gain or short exposure.
⚠️
Very low: smooth and featureless - blur, low signal, or a genuinely uniform sample.
Texture Heterogeneity - spatial uniformity of texture

How unevenly texture is distributed - high means some regions are rich while others are flat.

πŸ”¬ How it's computed
Compute local std in every 3Γ—3 window. Then compute the coefficient of variation (std / mean) of all those local stds. High = texture concentrated in patches; low = texture uniformly distributed.
πŸ’‘
High heterogeneity is expected for sparse fluorescence images (textured cells on flat background). Interesting only when it differs unexpectedly between conditions.
Blocking Index - JPEG compression artifacts

Detects 8Γ—8-pixel block boundaries - the hallmark signature of JPEG compression.

πŸ”¬ How it's computed
Measure the average absolute pixel jump across every 8-pixel grid boundary (horizontal and vertical). JPEG encodes in 8Γ—8 blocks, creating subtle discontinuities at those boundaries.
Blocking Index violin
condition3_comp (JPEG) has the highest blocking index - confirming what the file extension chart already hinted at. condition2_bl (blurred) has the lowest, because blurring smooths the block edges.
🚩
Non-zero blocking in data that should be lossless (TIFF, ND2, CZI): the data likely went through lossy compression somewhere - an export, upload, or storage step. Lossy compression should generally be avoided for scientific data: it can't be undone, and it distorts quantitative measurements.
🚩
High blocking in one condition but not another: they were processed differently. A systematic artifact that will skew any comparison between them.
Ringing Index - edge oscillation artifacts

High-frequency oscillations near edges - ringing from lossy compression or aggressive filtering.

πŸ”¬ How it's computed
Subtract a 3Γ—3 box-average from each pixel (high-pass filter). Compute variance of the residual. High variance = fine oscillations on top of the main image structure.
⚠️
High ringing + high blocking = strong evidence of lossy compression. High ringing alone can also come from aggressive spatial filtering (e.g. unsharp masking) applied during acquisition or export.

πŸ“‰ Basic Statistics Across Dimensions 🧩 per slice multidim only

How mean, std, min, and max change as you move along your non-spatial dimensions (e.g. Z, T, C, or S - the exact names depend on your loader) - averaged across all images in each group.

Max intensity per S slice
Max intensity across S (channel) slices - different images peak at different channels, and the spread widens at slice 2. The shaded band is the per-group std across images.
🚩
mean_intensity declining over T: photobleaching. Fluorophores are bleaching over the time course - any time-series analysis must account for this.
⚠️
std_intensity increasing over T: growing noise - sample movement, degradation, or instrument drift.
πŸ’‘
A channel with very low mean and high relative std likely has no real signal - just noise. Confirm visually in the mosaic for that channel.
⚠️
Each plot also has a dashed line - the percentage of images that still have a slice at that position, per group. A line dropping below 100% means images in that group don't all have the same number of slices.

🎨 Quality Metrics Across Dimensions 🧩 per slice needs loader needs pixel-patrol-image multidim only

How quality metrics change across your non-spatial dimensions (e.g. Z, T, C, or S - names depend on your loader). Best for detecting focus drift across a Z-like dimension or photobleaching effects on contrast over a T-like one.

Blocking index per S slice
Blocking index across S (channel) slices - condition4_nois (noisy, dark green) increases sharply at slice 2, while condition2_bl (blurred, orange) stays near zero across all slices.
🚩
Laplacian variance dropping across Z: focus is unstable across the stack. Find the Z range where sharpness peaks and limit your analysis to that range.
⚠️
Michelson contrast dropping over T: signal-to-background shrinking - consistent with photobleaching.
⚠️
MSCN variance spiking at one specific T frame: sample motion, a bubble, or a transient autofluorescence event.
πŸ’‘
Find the Z of peak Laplacian variance here, then set that Z in the sidebar dimension selector - all widgets update to show your data at its sharpest.
⚠️
As with Basic Statistics Across Dimensions, the dashed line shows the per-group percentage of images that still have a slice at that position - check it before reading too much into a quality metric at the far end of a dimension.

πŸ§ͺ Custom Plot πŸ“„/πŸ–ΌοΈ/🧩 varies always shown

Build your own plot from any columns in the report - pick an X column, a Y column (or (count)), and Pixel Patrol picks a sensible chart type:

  • Two numerics β†’ scatter
  • Categorical Γ— numeric β†’ violin or bar (mean Β± sd)
  • Any column Γ— (count) β†’ count bar
  • Two categoricals β†’ count heatmap

Color by defaults to the app-wide group column, but you can color/split by any other column instead - or, for scatter plots, by a numeric column on a continuous colormap. Each plot has its own Slice by toggles and a per-image / per-slice badge, just like Pixel Value Statistics. Click οΌ‹ Add plot for as many independent plots as you need.

πŸ’‘
Use this for anything not covered by the built-in widgets - e.g. plotting a loader-specific metadata column against a quality metric, or checking whether two metrics correlate.
πŸ’‘
↓ Export plugin downloads the current plot as a standalone viewer plugin .js file - drop it into an extension's viewer/ folder and list it in extension.json to make it a permanent part of your reports. See Create an Extension.

Working with the sidebar

πŸ—‚οΈ Group by
Split images into groups by any column. Default: path (your -p conditions). Try file_extension, dtype, or any loader metadata column to ask different questions.
πŸ” Filter
Restrict all widgets to rows matching a column + operator + value. Filters stack with grouping - filter to a dtype, then group by path to compare conditions within it.
πŸ“ Dimension selectors
For multidimensional data: a slider per non-spatial dimension (e.g. Z, T, C, S - names depend on your loader) that sets which slice all widgets display. Set the Z-like dimension to its peak Laplacian variance for the sharpest cross-condition comparison.
✨ Show significance
Mann-Whitney U test brackets on violin plots, Bonferroni corrected. Non-parametric - no normality assumption. A bracket = statistically distinguishable. Biological meaningfulness is your judgment.
πŸ’Ύ Save
CSV: the filtered file list with all metrics, in a plain spreadsheet format - use it to pick files to include or exclude in your pipeline, or just to browse and sort the full table yourself.
Parquet: a new, fully-interactive PP report of only the images that passed your filters.
☰ Widget list
Collapse or expand individual widgets. Grayed-out = required columns not in this report (processor wasn't run).
πŸ’¬ Feedback
Send feedback directly from the viewer. If something looks wrong, a metric is confusing, or you'd like a new feature - tell us here. All feedback is read.

Common filter examples:

Goal Column Op Value
Only 16-bit images dtype eq uint16
One condition only path eq condition_A
Sharp images only laplacian_variance gt 500
Remove dark images mean_intensity gt 10
Find saturated images max_intensity ge 254
Exclude a format file_extension not_contains .tif

Saving filtered subsets: Save as CSV gives you the filtered data as a spreadsheet - the same table as the parquet, in a plain, human-readable format. Use it to build an include or exclude list for your pipeline, or just open it and explore the numbers yourself. Save as Parquet creates a new fully-interactive report with only the images that passed your filters - the standard way to share a curated, clean version of your dataset report.