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
18 changes: 18 additions & 0 deletions src/ndr/format/blosc/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""Blosc container codec -- MATLAB-side surface only.

Mirrors ``+ndr/+format/+blosc/`` in NDR-matlab. The MATLAB package
exists so callers there can encode/decode Blosc v1 containers without
a system ``zstd`` binary; under the hood it calls ``numcodecs.Blosc``
in a private subprocess venv.

There is no Python mirror. Python callers should use ``numcodecs``
directly:

from numcodecs import Blosc
codec = Blosc(cname='zstd', clevel=5, shuffle=Blosc.SHUFFLE)
container = codec.encode(numpy_buffer)
raw = codec.decode(container)

See ``ndr_matlab_python_bridge.yaml`` in this directory for the
per-function contract.
"""
94 changes: 94 additions & 0 deletions src/ndr/format/blosc/ndr_matlab_python_bridge.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# ndr_matlab_python_bridge.yaml -- src/ndr/format/blosc/
# The Primary Contract for the +ndr/+format/+blosc MATLAB package.

project_metadata:
bridge_version: "1.1"
naming_policy: "Strict MATLAB Mirror"
indexing_policy: "Semantic Parity (1-based for user concepts, 0-based for internal data)"

# =========================================================================
# +ndr/+format/+blosc -- Blosc v1 container codec (encode + decode)
# (VH-Lab/NDR-matlab claude/lightsheet-zarr-ndi-viewer-djp5vk).
#
# The MATLAB package is deliberately a thin surface over `numcodecs.Blosc`
# invoked as a subprocess against a private venv NDR-matlab sets up on
# first use (see +ndr/+util/+blosc). It exists so that MATLAB callers
# (chiefly the lightsheet document builder in NDI-matlab) do not need a
# system `zstd` binary and do not need to touch MATLAB's `pyenv`, which
# is process-global and would fight the customer's Python setup.
#
# THE PYTHON SIDE HAS NO MIRROR AND IS NOT PLANNED. Python callers that
# need to write or read a Blosc container should use `numcodecs.Blosc`
# directly:
#
# from numcodecs import Blosc
# codec = Blosc(cname='zstd', clevel=5, shuffle=Blosc.SHUFFLE)
# container_bytes = codec.encode(numpy_buffer) # bytes-out
# raw_bytes = codec.decode(container_bytes) # bytes-in
#
# The MATLAB `encode` / `decode` calls are byte-identical to the
# corresponding numcodecs Blosc.encode / Blosc.decode calls, because
# they ARE those calls (over a subprocess). A wrapper here would only
# add a rename.
# =========================================================================
functions:

- name: encode
matlab_path: "+ndr/+format/+blosc/encode.m"
matlab_last_sync_hash: "aa89463"
status: matlab_only
decision_log: >-
Delegates to numcodecs.Blosc.encode via a subprocess. The MATLAB
argument list mirrors the numcodecs constructor: cname (default
'zstd'), clevel (default 5), shuffle (0/1/2, default 1),
blocksize (default 0 = auto), typesize (element size in bytes,
inferred from a typed array's class or supplied explicitly for
raw uint8). Python callers use numcodecs.Blosc().encode(buf)
directly; no port planned.

Hash bumped 2026-09-16 across four MATLAB-only commits
(f9cb0a2 args-string cleanup, b5ae537 clevel cap at 9,
8bea2d2 batch API + persistent server plumbing, aa89463 blosc
MEX preference). Every one is MATLAB-side subprocess or MEX
plumbing; Python uses numcodecs.Blosc directly, so there is
nothing to port.

- name: decode
matlab_path: "+ndr/+format/+blosc/decode.m"
matlab_last_sync_hash: "aa89463"
status: matlab_only
decision_log: >-
Delegates to numcodecs.Blosc.decode via a subprocess. Reads
typesize out of the container header (numcodecs handles it), so
the MATLAB signature is just `decode(container)`. Python callers
use numcodecs.Blosc().decode(bytes) directly; no port planned.

Hash bumped 2026-09-16 across two MATLAB-only commits
(8bea2d2 batch API + persistent server plumbing,
aa89463 blosc MEX preference). Both are MATLAB-side subprocess
or MEX plumbing; Python uses numcodecs.Blosc directly, so there
is nothing to port.

- name: isBlosc
matlab_path: "+ndr/+format/+blosc/isBlosc.m"
matlab_last_sync_hash: "9a87c0a"
status: matlab_only
decision_log: >-
Informed header sniff: checks the first byte is a supported Blosc
version (1 or 2) and that the cbytes field in the header equals
the buffer length. Blosc v1 has no fixed magic number so this is
a probe, not a proof. Python callers usually decide from
.zarray.compressor.id and would inline the same check with
numpy.frombuffer; no port planned.

- name: header
matlab_path: "+ndr/+format/+blosc/header.m"
matlab_last_sync_hash: "9a87c0a"
status: matlab_only
decision_log: >-
Metadata-only header parser: returns version, versionlz, flags,
shuffle bits, memcpy bit, typesize, nbytes, blocksize, cbytes.
Codec identity is NOT reported because the Blosc flag encoding of
it has drifted across versions and .zarray.compressor.cname is
the source of truth. Python callers should read the header with
struct.unpack when they need it; no port planned.
4 changes: 4 additions & 0 deletions src/ndr/format/omezarr/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@

from ndr.format.omezarr.isOMEZarr import isOMEZarr
from ndr.format.omezarr.listPyramids import listPyramids
from ndr.format.omezarr.probe import probe
from ndr.format.omezarr.readArray import readArray
from ndr.format.omezarr.readAttrs import readAttrs
from ndr.format.omezarr.reduce import reduce
from ndr.format.omezarr.resolveArrayPath import resolveArrayPath

__all__ = [
"isOMEZarr",
"listPyramids",
"probe",
"readArray",
"readAttrs",
"reduce",
"resolveArrayPath",
]
18 changes: 16 additions & 2 deletions src/ndr/format/omezarr/ndr_matlab_python_bridge.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ functions:
- name: readArray
matlab_path: "+ndr/+format/+omezarr/readArray.m"
status: regular_port
matlab_last_sync_hash: "a847431"
matlab_last_sync_hash: "8bea2d2"
python_path: "ndr/format/omezarr/readArray.py"
decision_log: >-
Reads pixels for one region of one pyramid level and returns an n-D
Expand All @@ -80,6 +80,13 @@ functions:
(v2, C-order, Blosc/Zstd or uncompressed, fixed-length numeric
dtypes). A store the MATLAB reader accepts is also accepted here.

Hash bumped 2026-09-16 to include 8bea2d2, which restructured
the MATLAB reader into three passes (plan, batched fread,
batched blosc.decodeMany) to amortize Python-subprocess spawn
cost. Python's zarr.open() already reads and decodes chunks in
one call with no subprocess round-trip, so there is no MATLAB
change to port here.

- name: probe
matlab_path: "+ndr/+format/+omezarr/probe.m"
status: regular_port
Expand Down Expand Up @@ -112,7 +119,7 @@ functions:

- name: decompressBlosc
matlab_path: "+ndr/+format/+omezarr/private/decompressBlosc.m"
matlab_last_sync_hash: "a847431"
matlab_last_sync_hash: "5a44303"
status: ported_differently
python_path: "ndr/format/omezarr/readArray.py"
decision_log: >-
Expand All @@ -122,6 +129,13 @@ functions:
`numcodecs.Blosc`, so readArray never needs to see the container
bytes. Blosc-compressed stores read correctly in Python.

Hash bumped 2026-09-16 to include 5a44303, which replaces the
MATLAB hand-rolled codec-per-block loop with a one-line
delegation to ndr.format.blosc.decode (which itself routes
through numcodecs.Blosc in a MATLAB-owned venv). Python has
always been on numcodecs.Blosc directly, so there is nothing
new to port.

- name: decompressZstd
matlab_path: "+ndr/+format/+omezarr/private/decompressZstd.m"
matlab_last_sync_hash: "248f7f2"
Expand Down
Loading
Loading