Skip to content

Exploring the Interactive Report

Open your interactive report in one browser tab and keep this page open in another. No PixelPatrol table of your own yet? Open the example report, used in this tutorial.

The example screenshots below are from a subset of the BBBC006 dataset (human U2OS cells, Hoechst and phalloidin channels, 20x) with four conditions: focus series acquired far below, below, near and far above the focal plane, each stored as a multi-channel .ome.tif stack. The uneven group sizes, varying Z size, 12-bit data stored in 16 bits and changing sharpness across Z make 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), PixelPatrol 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 interactive report
needs loader  requires an image loader (e.g. bioio or tifffile)
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
🧩 per slice  one datapoint = one channel / Z-plane / timepoint within an image, or another sliced dimension
πŸ–ΌοΈ/🧩  switchable with the Slice by toggle

Overview and Full view

The viewer opens in Overview mode: a grid of tiles, each with a one-line summary. Click a tile to expand it, or switch to Full view with the toggle in the top right to see every widget at once.

Each widget has an β“˜ button that briefly explains what it shows and how to read it; a toggle in the topbar opens or closes all of them.


Opening your interactive report

pixel-patrol view results.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 pixelpatrol.app/viewer/ and drag your own .parquet in directly (runs in browser memory, so tables larger than a few GB may not load).

Useful launch flags:

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

Widget walkthrough

βš™ What's in your table?

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. bioio or tifffile)?
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 - the groups have 2, 2, 3 and 6 files, and near_focus holds several times more data on disk than the others. Uneven group sizes, visible 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 Statistics
File statistics - the yellow banner flags that file counts differ between conditions (ratio above 1.5Γ—). All files share the single extension .ome.tif, so it appears in the "shared properties" table instead of a chart.
🚩
Mixed file formats, e.g. one condition has a different format than the others. Mixed extensions can mean a mixed dataset - images exported in two formats, or even saved twice. The example report has a single format, so it does not trigger this.
⚠️
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 Structure Sunburst πŸ“„ 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 table 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 holding its stacks directly with no 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.

πŸ“ Dimensionality πŸ–ΌοΈ 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.

Z size distribution
Z size strip plot - X and Y are the same for every image (696Γ—520), but the stacks have between 5 and 8 Z-planes, so the Z size is not constant across files.
🚩
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 Thumbnails πŸ–ΌοΈ 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 Thumbnails
Thumbnails from all four conditions - border colors indicate group. Sort by laplacian_variance ascending and the blurriest, most out-of-focus images 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 bright_clipping_fraction descending to surface overexposed images. Sort by dark_clipping_fraction descending to surface images with heavy background or underexposure.
πŸ’‘
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 - most images peak at exactly 4095, the 12-bit ceiling, although the data is stored as uint16 (a 16-bit file would reach up to 65535). Only a handful of distinct values exist, so PixelPatrol shows raw points instead of a violin. 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 Image Thumbnails.
⚠️
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. If the dataset has both integer and float images, each type gets its own plot. For integer images, toggle between actual pixel values and a dtype-normalized range to compare across bit depths.

πŸ”¬ How it's computed
Each image gets a 256-bin histogram of its pixel values, normalized to area 1. Per-group, the normalized histograms are averaged equally across images (image size does not affect the result). The bin range uses the actual pixel value range of each image, not a fixed 0-255 range.
Intensity histograms
Average pixel histograms of the four groups, all uint16 so in one plot - most pixels sit in a dark background peak around 130 (the camera offset, not 0), with a long tail toward the bright signal that ends at 4095 instead of spanning the 16-bit range. below_focus (purple) has the strongest background peak.
πŸ’‘
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

Four quality metrics computed per leaf slice, aggregated by pixel-count-weighted mean. They surface blur, noise, and exposure clipping at the whole-image level - not localized defects. Only compare images at the same magnification and pixel size - laplacian_variance and spectral_slope are scale-dependent.

Laplacian Variance - high-frequency content

High-frequency content measure. Blur and heavy compression push values down; sharpness, noise, and fine texture push values up. A smooth, featureless scene will score low regardless of focus quality. Clipped pixels inflate the score.

πŸ”¬ 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. Variance of this response is the score. Clipped pixels create artificial high-frequency edges and inflate it - check clipping fractions when values seem unusually high.
🚩
Low outliers β†’ go to Image Thumbnails, sort by laplacian_variance ascending, and confirm visually. If they look out-of-focus, you may want to exclude them.
⚠️
One condition consistently lower: different imaging days, focus protocols, or objective settings?
Spectral Slope - blur vs noise distinction

Log-log slope of the radially averaged power spectrum, fit over the mid-frequency band (5–40% of Nyquist). Typical range: βˆ’2 to βˆ’4. Closer to 0 indicates noise or uniform content. More negative values indicate blur or stronger low-frequency dominance. Most interpretable when comparing images of similar content type.

πŸ”¬ How it's computed
Apply a 2D Hanning window, compute the FFT, average power in radial frequency bins, then fit a line to log(power) vs log(frequency) over the mid-frequency band (5–40% of Nyquist). The slope of that line is the score.
⚠️
Steeper (more negative) than expected for one condition: more blur or more structured/textured content than others - check laplacian_variance to tell them apart.
⚠️
Flatter (toward zero) than expected: noise or low-signal images. NaN for images smaller than 32Γ—32 pixels.
πŸ’‘
Most interpretable when comparing images of similar content type. Differences between conditions with very different subjects (e.g. cells vs tissue) may reflect content, not quality.
Dark Clipping Fraction - underexposure / background

Fraction of pixels at the dtype's minimum representable value (0 for unsigned integers; NaN for float). Detects underexposure, background, and nodata at the dark end.

πŸ”¬ How it's computed
Count how many pixels equal iinfo(dtype).min (0 for unsigned integers, e.g. βˆ’32768 for int16) and divide by total pixel count.
⚠️
High fraction in unsigned-integer images: heavy dark background, underexposure, or nodata regions. In signed-integer images: signal clipped at the lower bound - real values there are lost.
Bright Clipping Fraction - bright-end clipping

Fraction of pixels at the dtype's maximum representable value (integer types only; NaN for float, which has no fixed upper bound).

πŸ”¬ How it's computed
Count how many pixels equal iinfo(dtype).max (e.g. 255 for uint8, 65535 for uint16) and divide by total pixel count. Sub-dtype clipping (e.g. 12-bit data in uint16 peaking at 4095, not 65535) is flagged by a separate warning below if max_intensity is below the dtype ceiling.
🚩
Non-zero and rising: pixels at the dtype ceiling - sensor saturation. Real signal there is lost. Check exposure/gain settings.
⚠️
The widget shows a separate warning if images peak at a known bit-depth boundary below the dtype ceiling (e.g. uint16 peaking at 4095 = 12-bit) - data stored in a wider type than acquired. This metric won't catch clipping at those sub-dtype ceilings.

πŸ“‰ 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 and mean intensity across C and Z slices
Max and mean intensity across C and Z slices - the second channel is brighter than the first in every group. On the Z side the dashed line (right axis) drops below 100% after the 5th plane, because the stacks have between 5 and 8 planes. 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 Image Thumbnails 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 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.

Laplacian variance across C and Z slices
Laplacian variance across C and Z - in near_focus (green) it climbs steeply to a peak at Z=6 and falls again, the signature of a stack passing through focus. The other groups stay low and flat by comparison.
🚩
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.
⚠️
Spectral slope becoming flatter (toward zero) over T: noise increasing over the time course - possible sample degradation or instrument drift.
⚠️
Dark or bright clipping fraction rising in one condition across C: channel-specific exposure problem or a detector offset artifact.
πŸ’‘
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 table - pick an X column, a Y column (or (count)), and PixelPatrol 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: creates a new parquet file (PixelPatrol table) with only the filtered data. You can open it as an interactive report.
☰ Widget list
Collapse or expand individual widgets. Grayed-out = required columns not in this table (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 parquet file (PixelPatrol table) with only the filtered data. You can open it as an interactive report - the standard way to share a curated, clean version of your dataset. See the privacy policy for what's included before sharing it.