Skip to content

Configurable per-channel rendering settings (release v0.6.0) - #15

Merged
jwindhager merged 3 commits into
mainfrom
feat/render-settings
Sep 17, 2026
Merged

jwindhager merged 3 commits into
mainfrom
feat/render-settings

Conversation

@jwindhager

@jwindhager jwindhager commented Sep 17, 2026 •

Copy link
Copy Markdown
Member

Prepares the v0.6.0 release.

Highlights

  • Per-channel rendering settings: range, color, lutOrColorMap and inverted, as a single value for all rendered channels or one value per channel, overriding the omero metadata
  • c now accepts an array of channel indices composited in the given order
  • New getters for the resolved values: loaded, t, z, cs, channels, ranges, colors, lutsOrColorMaps, inverteds, plus getWidth(level)/getHeight(level)
  • Tiles are always passed to OpenSeadragon as raw ome-zarr data and rendered by the registered converter; OMEZarrTileData holds one chunk per rendered channel (chunks) together with the fully resolved rendering settings for that tile, instead of a single chunk and channel. The dataType option is gone
  • OMEZarrTileSource.loadOMEZarr loads metadata and arrays once for sharing across tile sources; the constructor and open take the resulting OMEZarr instead of an NgffImage
  • getNumTiles is derived from the actual array shape of each resolution level instead of the width-based scale, and tile requests outside a level are rejected
  • equals and getTileHashKey now cover the same components (URL, zip, resolved t/z/cs and a hash of the configured rendering settings), so tile sources rendering the same image identically share cached tiles and differently rendered ones no longer do
  • Fixed: color LUTs are copied before rendering, so the in-place reverse() in ome-zarr.js renderChunks no longer corrupts the LUT of an inverted channel
  • Example page accepts GET parameters (demo only, not part of the published package)

Breaking changes and the full list are in the changelog.

Release

  • package.json bumped to 0.6.0, changelog rolled over to [0.6.0] - 2026-09-17
  • Tag v0.6.0 still needs to be pushed after merging to trigger the publish workflow

Checks

pnpm run lint, fmt:check, test (no test files) and build:lib pass.

@jwindhager jwindhager self-assigned this Sep 17, 2026
@jwindhager
jwindhager marked this pull request as ready for review September 17, 2026 14:14
Copilot AI lite review requested due to automatic review settings September 17, 2026 14:14
@codecov

codecov Bot commented Sep 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 0.00%. Comparing base (b15faa1) to head (94ee0fa).

Additional details and impacted files
@@    Coverage Diff     @@
##   main   #15   +/-   ##
==========================
==========================

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Multiple unresolved moderate issues remain in rendering, caching, validation, and URL handling.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Prepares the v0.6.0 release with configurable per-channel rendering, shared OME-Zarr loading, raw tile conversion, and updated documentation.

Changes:

  • Adds channel arrays and rendering settings.
  • Refactors tile loading, caching, and converters.
  • Updates exports, demo parameters, documentation, and release metadata.
File summaries
File Description
src/utils.ts URL and data-range utilities
src/OMEZarrTileSourceOptions.ts Expanded configuration options
src/OMEZarrTileSource.ts Loading, rendering, validation, caching, and tile handling
src/OMEZarrTileData.ts Raw multi-channel tile data contract
src/OMEZarr.ts Shared loaded OME-Zarr data
src/main.ts Public API exports
README.md Updated API and usage documentation
package.json Version bumped to 0.6.0
index.html Query-parameter-driven demo configuration
CHANGELOG.md v0.6.0 release notes
Review details

Suppressed comments (8)

src/OMEZarrTileSource.ts:674

  • The channel range check has the same gap: c: [NaN] or a fractional channel index passes and is sent to getSlices as an invalid selection. Add an integer/non-negative condition so every explicit channel index is validated before it is used.
        if (this._c !== undefined && this._c.some((c) => c < 0 || c >= sizeC)) {

src/OMEZarrTileSource.ts:827

  • This stores the LUT/color-map objects returned by the metadata/configuration directly in cached tile data. render only shallow-copies LUT arrays immediately before rendering, so mutating a loaded channel LUT, map, or its color entries after download changes tiles that are already cached, contrary to the documented snapshot behavior. Clone the LUT/map and nested color arrays when constructing OMEZarrTileData.
            lutsOrColorMaps: chunks.map((_, i) => lutsOrColorMaps?.[i]),

src/OMEZarrTileSource.ts:268

  • Unlike the array form, instanceof Map accepts any keys and values, so malformed maps (for example new Map([['x', 'y']])) pass constructor validation and later reach the sort/render path, where they can produce invalid data or errors. Validate finite numeric keys and RGB/RGBA array values before accepting a color map.
      (lutOrColorMap) =>
        lutOrColorMap instanceof Map ||
        (Array.isArray(lutOrColorMap) &&
          lutOrColorMap.length > 0 &&
          lutOrColorMap.every(

src/OMEZarrTileSource.ts:796

  • The cache identity uses only a 32-bit FNV-1a hash for all rendering settings. Two different user-provided settings can collide, making equals and this cache key identical and causing one rendering to reuse another rendering's raw tile data. Include a collision-free canonical representation in the key, or use a sufficiently collision-resistant identity rather than relying solely on this 32-bit hash.
    url.searchParams.append("render", this._renderSettingsHash.toString(16));

src/OMEZarrTileSource.ts:927

  • When c is omitted for an image without omero metadata, channels is undefined even though loadChunks returns every channel. A scalar range, color, LUT, or inverted value therefore remains a one-entry array here and downloadTileStart applies it only to chunks[0]; the other channels silently use defaults, contrary to the option contract. Repeat these settings using the loaded c-axis size (or expand them when building the tile data).
    if (values === undefined) {
      return channels?.map(() => undefined);
    }
    if (values.length === 1 && channels !== undefined) {
      return channels.map(() => values[0]);

src/OMEZarrTileSource.ts:547

  • When tile is undefined, this method never validates level; an out-of-range level is then indexed through arrays and fails with an incidental TypeError instead of the documented level out of bounds error. Validate the level before the tile branch so both whole-plane and tile loads follow the same contract.
    const indices: { [k: string]: number | [number, number] | undefined } = {
      t: this.t, // undefined = middle plane
      z: this.z, // undefined = middle plane
    };

src/OMEZarrTileSource.ts:307

  • Assigning the supplied loaded object here makes loaded and all metadata-resolved getters visible immediately, before getImageInfo has validated the object or raised ready; invalid preloaded data can therefore be observed as ready. Keep the constructor argument in a separate pending field and assign _loaded only after validation succeeds so the documented ready-only contract is preserved.
    this._loaded = loaded;

src/utils.ts:53

  • The fallback does not strip query strings or fragments, so in a runtime without document (for example SSR or Node), isOZX("image.ozx?download=1") returns false even though the documented path suffix is .ozx and resolveUrl has already failed only because no base URL exists. Apply the same query/fragment stripping in the fallback.
    return url.toString().endsWith(".ozx"); // not a URL (e.g. an unrelated tile source)
  • Files reviewed: 10/10 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/OMEZarrTileSource.ts Resolved
@jwindhager
jwindhager merged commit 71afa46 into main Sep 17, 2026
6 checks passed
@jwindhager
jwindhager deleted the feat/render-settings branch September 17, 2026 14:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants