Skip to content

fix(fizarrita): classify codecs by size-preservation, not by compressor name - #9

Merged
thewtex merged 2 commits into
fideus-labs:mainfrom
xinaesthete:fix/probe-decompressed-size-codec-classification
Aug 6, 2026
Merged

fix(fizarrita): classify codecs by size-preservation, not by compressor name#9
thewtex merged 2 commits into
fideus-labs:mainfrom
xinaesthete:fix/probe-decompressed-size-codec-classification

Conversation

@xinaesthete

@xinaesthete xinaesthete commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Hi — found this while wiring fizarrita's chunk cache into SpatialData.ts, where we use getWorker as codec offload for OME-Zarr imagery. Happy to adjust anything here to taste, including the test placement.

The bug

#5

probeDecompressedSize decides "is this chunk compressed?" by testing the codec names against a fixed list of compressors:

const hasCompression = codecMeta.codecs.some((c) => {
  const name = c.name.toLowerCase()
  return name === "gzip" || name === "zlib" || name === "blosc" || name === "zstd"
    || name === "lz4" || name === "bz2" || name === "lzma" || name === "snappy"
})
if (!hasCompression) {
  return rawBytes.byteLength
}

Any codec not on that list takes the not-compressed branch and returns rawBytes.byteLength — the compressed length — as the decompressed size. inferChunkShape then takes that number as fact.

For JPEG 2000 / HTJ2K imagery that is wrong by an order of magnitude or more, and it fails quietly. Usually the bogus size fails to divide cleanly and inferChunkShape falls back to the metadata shape, so nothing visibly breaks — but it can emit spurious chunk_shape does not match warnings, and in the worst case adopt an inferred shape derived from a nonsense number.

It is not only exotic codecs. Several already in zarrita's registry are missed by the current list, and all of them currently report their raw length as their decoded length:

Codec Why the raw length isn't the decoded length
crc32c appends a 4-byte checksum
scale_offset, cast_value re-type the values
vlen-utf8, json2 variable-length
sharding_indexed wraps an index plus inner chunks, usually compressed

The change

Invert the test: allowlist the codecs known to preserve byte count (bytes, transpose) and treat anything unrecognised as size-changing.

The argument for the inversion rather than "add imagecodecs_jpeg2k and HTJ2K to the list" is that the two mistakes are not symmetric:

  • Calling a size-changing codec size-preserving returns a silently wrong number that downstream code trusts.
  • Calling a size-preserving codec size-changing costs one decode through the step-4 fallback that already exists, and still returns the right answer.

So the safe default is the one that can only ever cost time — and it stays correct for codecs that don't exist yet, rather than needing a new entry each time one appears.

The classification is extracted as hasSizeChangingCodec and exported. v2-style numcodecs. prefixes are stripped before lookup, matching how readArrayMetadata builds v2 codec entries from id.

Tests

Nine cases added to chunk-shape-inference.spec.ts, following the existing pattern for readZstdFrameContentSize and inferChunkShape — exported, exposed on window in the test app, exercised via page.evaluate. No new tooling.

They cover the size-preserving chains, the numcodecs. prefix and case-insensitivity, the known compressors, the unrecognised-codec case that motivated this (including HTJ2K), the resize-without-compressing codecs in the table above, and sharding.

Full suite passes locally: 119/119.

One unrelated note in case it's useful: the suite only ran for me after pointing it at a free port. playwright.config.ts uses reuseExistingServer: !process.env.CI with a hardcoded localhost:5173, so if anything else is already serving 5173 — very common with Vite — Playwright silently reuses that server and every test fails at waitForFunction, with no hint as to why. Not touched here since it's orthogonal, but happy to send a follow-up if you'd like strictPort or a less-common default.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Improvements

    • Improved chunk-size detection across codec chains, including byte-preserving transformations such as byte conversion and transposition.
    • Codec names are now handled consistently regardless of capitalization or prefixes.
    • Unknown, compression, resizing, image, and sharding codecs are correctly recognized as potentially changing data size.
    • Added a public utility for checking whether a codec chain changes size.
  • Tests

    • Added coverage for size-preserving and size-changing codec combinations.

…or name

`probeDecompressedSize` decided "is this chunk compressed?" with an allowlist of
compressor names (gzip|zlib|blosc|zstd|lz4|bz2|lzma|snappy). Any codec outside
that list took the not-compressed branch and returned `rawBytes.byteLength` —
the *compressed* length — as the decompressed size, which `inferChunkShape` then
takes as fact. For a JPEG 2000 / HTJ2K chunk that is wrong by an order of
magnitude or more, and it fails silently: usually the bogus size fails to divide
cleanly and the metadata shape is kept, but it can emit spurious
`chunk_shape does not match` warnings and, worst case, adopt an inferred shape
built from a nonsense number.

Inverted to allowlist the codecs known to preserve byte count (`bytes`,
`transpose`), so an unrecognised codec counts as size-changing. The two mistakes
are not symmetric: calling a size-changing codec size-preserving yields a
silently wrong number, while calling a size-preserving codec size-changing costs
one decode through the existing fallback and still returns the right answer. The
safe default is the one that can only cost time.

This also corrects some codecs already in the registry that the old list missed:
`crc32c` appends a 4-byte checksum, `scale_offset` and `cast_value` re-type the
values, `vlen-utf8` and `json2` are variable-length, and `sharding_indexed`
wraps an index plus inner chunks that are usually compressed themselves — all of
which previously reported their raw length as their decoded length.

Extracted as `hasSizeChangingCodec` and exported so it can be tested directly,
following the existing pattern for `readZstdFrameContentSize` and
`inferChunkShape`: exposed on `window` in the test app, exercised from
`chunk-shape-inference.spec.ts`. v2-style `numcodecs.` prefixes are stripped
before lookup.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 5, 2026 16:50

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes chunk decompressed-size probing in @fideus-labs/fizarrita by classifying codec chains based on whether they preserve byte count, rather than matching codec names against a compressor allowlist. This makes chunk-shape inference robust to unknown/new codecs (e.g. JPEG2000/HTJ2K) by defaulting to the safe path (full decode) when size preservation can’t be proven.

Changes:

  • Introduces and exports hasSizeChangingCodec, which allowlists only size-preserving codecs (bytes, transpose) and treats all others as size-changing.
  • Updates probeDecompressedSize to use the new size-preservation check instead of a fixed compressor-name list.
  • Adds browser tests for the new classification behavior and exposes the helper on the test app window.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
fizarrita/src/get-worker.ts Adds hasSizeChangingCodec and switches decompressed-size probing to size-preservation logic.
fizarrita/src/index.ts Re-exports hasSizeChangingCodec as part of the public API surface.
test/app/main.ts Exposes hasSizeChangingCodec on window so Playwright browser tests can call it.
test/browser/chunk-shape-inference.spec.ts Adds Playwright tests covering size-preserving vs size-changing codec classification (including unknown codecs).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread fizarrita/src/get-worker.ts Outdated
Comment on lines +355 to +357
// 3. Check if the raw bytes could be an uncompressed chunk.
// For the bytes codec (no compression), rawBytes.byteLength IS the
// decompressed size. We check whether the codec chain is bytes-only
// (no bytes_to_bytes compression codecs).
const hasCompression = codecMeta.codecs.some((c) => {
const name = c.name.toLowerCase()
// array_to_array codecs (transpose, etc.) don't change byte size
// array_to_bytes codecs (bytes, etc.) don't compress
// bytes_to_bytes codecs are the compressors
return (
name === "gzip" ||
name === "zlib" ||
name === "blosc" ||
name === "zstd" ||
name === "lz4" ||
name === "bz2" ||
name === "lzma" ||
name === "snappy"
)
})
if (!hasCompression) {
// No compression codec — raw bytes are the decompressed data
// When every codec preserves byte count, rawBytes.byteLength IS the
// decompressed size.
The step heading still said "uncompressed chunk", which undersells what the
branch now accepts: a transpose + bytes chain preserves byte count without being
uncompressed in any meaningful sense.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@thewtex

thewtex commented Aug 6, 2026

Copy link
Copy Markdown
Member

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 38283f8c-fb28-4346-ac42-0cf20eb05fca

📥 Commits

Reviewing files that changed from the base of the PR and between 8cc95ee and 8429394.

📒 Files selected for processing (4)
  • fizarrita/src/get-worker.ts
  • fizarrita/src/index.ts
  • test/app/main.ts
  • test/browser/chunk-shape-inference.spec.ts

📝 Walkthrough

Walkthrough

The change adds codec-chain size classification, updates chunk-size probing for size-preserving chains, exports the classifier, and adds browser coverage for codec name normalization and classification.

Changes

Codec size classification

Layer / File(s) Summary
Codec-chain analysis and probing
fizarrita/src/get-worker.ts, test/browser/chunk-shape-inference.spec.ts
hasSizeChangingCodec treats only bytes and transpose chains as size-preserving after prefix removal and case normalization. Chunk-size probing uses this classification. Browser tests cover compressors, resizing codecs, sharding, unknown codecs, and preserved-size chains.
Public export and browser test wiring
fizarrita/src/index.ts, test/app/main.ts
The package exports hasSizeChangingCodec. Browser tests import the function and expose it on window.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: copilot

Poem

A rabbit checks each codec byte,
Transpose keeps the size just right.
Unknown paths now raise a flag,
Tests hop through every tag.
The worker probes with clearer sight.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: classifying codecs by size preservation instead of compressor names.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Suppressed comments (3)

fizarrita/src/get-worker.ts:327

  • Using a regex in a hot-path-ish predicate creates avoidable overhead/allocations. Prefer a startsWith('numcodecs.') check with slice(...) after toLowerCase() to avoid regex work while keeping the same behavior.
  return codecs.some((codec) => {
    const name = codec.name.toLowerCase().replace(/^numcodecs\./, "")
    return !SIZE_PRESERVING_CODECS.has(name)
  })

test/browser/chunk-shape-inference.spec.ts:754

  • Inline import('@playwright/test').Page makes the helper signature harder to read and inconsistent with typical typing patterns. Consider adding a top-level import type { Page } from '@playwright/test' (or reusing an existing one if present) and using Page here.
  const classify = (page: import('@playwright/test').Page, names: string[]) =>

test/browser/chunk-shape-inference.spec.ts:780

  • These assertions are awaited sequentially, which can slow the suite noticeably as the list grows (similar loops appear below as well). Consider running them concurrently (e.g., map to promises and await Promise.all(...)) since each classify call is independent.
  test('flags the known compressors', async ({ page }) => {
    for (const name of ['gzip', 'zlib', 'blosc', 'zstd', 'lz4']) {
      expect(await classify(page, ['bytes', name])).toBe(true)
    }
  })

@thewtex thewtex left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@xinaesthete thank you! 🥇

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.

3 participants