Releases: WSILabs/wsitools
throwaway: dry-run the release binary matrix
Release v0.0.0-rc.test
v0.22.0 — associated-image editing across all formats + JP2K optional
Added
- Associated-image editing extended to COG-WSI —
label/macro/thumbnail/overview removeandreplace(all types) viacogwsiwriterre-finalize. Pyramid tile bytes are copied verbatim (no re-encode); all other associated images and MPP/magnification/ICC are preserved — only the target image changes. - Associated-image editing extended to OME-TIFF (remove + replace, all types) via
streamwriterrebuild — lossy: rebuilds the file and regenerates a minimal OME-XML (instrument/acquisition/channel/vendorOriginalMetadataand pyramid-resolution annotations not preserved; pyramid pixels, geometry/MPP/magnification, ICC, and the other associated images are). Always-on runtime warning on every OME-TIFF edit. Associated replacements are JPEG-only (opentile-go's OME-TIFF reader can only decode JPEG/uncompressed associated images; LZW/Deflate would be unreadable). wsitools' OME-TIFF support is rudimentary — use Bio-Formats for serious OME-TIFF work; see docs/ome-tiff-limitations.md. This completes associated-image editing across all four editable formats: SVS, generic-TIFF, COG-WSI, and OME-TIFF.
Changed
- opentile-go bumped to v0.37.0 — JPEG 2000 decode is now optional. Build
with-tags nojp2kto drop the OpenJPEG dependency; libjpeg-turbo is now the
only required codec library. See docs/INSTALL.md for a
JPEG-only minimal install. JPEG 2000 is a legacy Aperio codec, no longer
marketed as required. - CI: a
Releaseworkflow now auto-creates the GitHub Release onv*tag push,
with notes pulled from the matchingCHANGELOG.mdsection and the title from
the annotated tag's subject (notes-only; no binary artifacts).
v0.21.0 — associated-image editing + convert --factor + OME-TIFF conformance
Added
- Associated-image editing — four command groups, each with
removeand
replacesubcommands:label,macro,thumbnail,overview. Supported
on SVS and generic-TIFF.removestrips the target associated image entirely (label PHI removal);
works for every type on both formats.replaceswaps it with a new image file. Supported for all types on
generic-TIFF; on SVS, only label replace is supported today
(opentile-go reads Aperio thumbnail/macro/overview as abbreviated JPEG, so
re-encoding those is a Slice-2 item — SVS non-labelreplaceerrors
clearly). Replacements carry the reader's classification markers
(SVSNewSubfileType=9for macro/overview;WSIImageTypeprivate tag for
generic-TIFF) so a replaced image is read back as the intended type.- Pyramid tile bytes are copied verbatim (no decode, no re-encode); only
the tail IFD is rewritten via a prefix-copy + tail-re-emit splice. Output
contains no recoverable label PHI. - Output defaults to
<stem>_relabeled<ext>next to the input (auto-numbered
if the path exists);-o/--outputfor an explicit path;--in-placefor
atomic overwrite (temp + fsync + rename). label replacedefaults to LZW + Predictor 2 (lossless, barcode-safe);
macro/thumbnail/overview replacedefault to JPEG.
--compression {jpeg,lzw,deflate,none}overrides.--resize fit|stretch|none(defaultfit),--bg RRGGBBletterbox fill
(defaultF5F5E6),--forceto skip the aspect guard,
--label-dims WxHto override target dimensions.- OME-TIFF and COG-WSI: planned (Slice 2 — SubIFD-range-aware splice +
OME-XML sync). Other formats (DICOM, NDPI, Philips, BIF, IFE, Leica)
are rejected with a pointer toconvert.
- opentile-go bumped to v0.36.0 — adds
AssociatedIFDOffsetused by the
splice engine to locate and excise associated-image IFDs without walking the
full IFD chain.
Dependencies
-
New:
github.com/hhrutter/lzw— pure-Go LZW encoder used for
lossless label replacement (LZW + Predictor 2). -
convert --factor N/--target-mag M— downsample while converting, for
--to svs|tiff|ome-tiff|cog-wsi, with correctly-scaled MPP (×N) and
magnification (÷N).dzi/szinot yet supported. -
downsampleis now format-preserving and works on more sources: it
reduces SVS, OME-TIFF, generic-TIFF, and COG-WSI slides in place (same
container in/out, MPP/mag scaled), instead of SVS-only. Sources with no
matching writer error with a pointer toconvert --to … --factor. Shares the
reduction engine (internal/downscale) withconvert --factor. -
Default soft memory limit: wsitools now sets
GOMEMLIMITto 75% of
physical RAM at startup so memory-heavy conversions degrade under GC
pressure instead of OOM-ing the host. Override with the global
--max-memoryflag (e.g.8000,12GiB,off) or theGOMEMLIMIT
environment variable; precedence is--max-memory>GOMEMLIMIT>
default.wsitools doctornow reports physical RAM and the active soft
limit with its source. -
scripts/bench-dzi.shnow reports peak resident memory (via
/usr/bin/time -l) alongside wall-clock time for both wsitools and
vips, with a memory ratio column.
Changed
- BREAKING — associated-image terminology "kind" → "type" (aligns with
opentile-go'sAssociatedImage.Type()):extract --kindis renamed toextract --type(no alias — the old flag is
removed).info --jsonassociated-image fieldkind→type.dump-ifds --jsonIFD-classification fieldkind→image_type(named
image_typerather thantypeto avoid colliding with--raw's existing
per-tag TIFFtypefield).
- opentile-go upgraded v0.26.0 → v0.31.0. v0.27–v0.29 are internal NDPI
decode-perf work (pixel-frame cache, cross-format decoder-handle pool,
ReadRegion allocation elimination); v0.30 adds a per-Slide read-memory
budget (OPENTILE_READ_MEMORY_BUDGET, default 1 GiB) that byte-bounds
the strip/tile decode caches, lowering peak RSS on wide NDPI slides;
v0.31 exposes raw TIFF tags cross-format (Slide.LevelTIFFTags/
AssociatedTIFFTags/TIFFDirectoriesOf, typedTIFFTag,
pixel-pointer-filtered) — the foundation for upcoming metadata
carry-through. Clean drop-in; no wsitools API changes. Later upgraded
through v0.33.0 (chroma-subsampling JP2K decode fix; separable Lanczos;
codec-domain scaled decode for JPEG2000/HTJ2K;dicom.ListWSMSeries). downsampleprimary reduction is now codec-agnostic: it uses codec-domain
scaled decode (DecodeOptions.Scale) where the source codec supports it and
falls back to full-decode + box otherwise.- JP2K sources now decode via wavelet resolution-reduction (opentile-go
v0.33.0) instead of full-decode + box — faster and sharper, but output
pixels are no longer byte-identical to prior releases for JP2K sources. - Fixes
downsample --factor 16on JPEG sources (previously errored with
scale=16 (want 1,2,4,8)). - Adds
downsamplesupport for AVIF / WebP / HTJ2K sources (previously
unsupported compression).
- JP2K sources now decode via wavelet resolution-reduction (opentile-go
v0.15.0 — striped-format source support
Added
- NDPI, OME-OneFrame, and Leica SCN (single-image) slides are now
supported across all CLI subcommands (info, transcode, downsample,
convert, hash, extract, dump-ifds, region). opentile-go synthesizes
tile geometry from striped MCU streams (NDPI) and single-frame OME
(OME-OneFrame); wsitools' tile-pipeline now operates on
opentile-go-tiled output verbatim.
Changed
- Dropped the "v0.2 sanity gate" in
internal/source/opentile.go
that rejected NDPI / OME-OneFrame / Leica-SCN. Stale since
opentile-go v0.14+ began synthesizing tile geometry. ErrUnsupportedFormat's message updated to drop the "v0.2"
version marker; the sentinel remains for genuinely-unsupported
future formats.
Bit-exact tile-copy caveat (convert)
convert --to cog-wsi from natively-tiled sources (SVS, Philips,
OME-tiled, BIF, IFE, generic-TIFF, COG-WSI, SZI, single-image
Leica-SCN) continues to produce bit-exact tile-copy COG-WSI output
— the source's compressed tile bytes appear verbatim in the
destination.
From striped sources (NDPI, OME-OneFrame), the COG-WSI output
contains opentile-go's synthesized JPEG tile bytes. These bytes
decode to the same pixels as the source region and are
deterministic (same input → same output), but they are NOT the
source's on-disk bytes (NDPI / OneFrame source files don't carry
tile bytes — they carry strip bytes).
Out of scope (deferred)
- Multi-channel fluorescence Leica SCN
(Leica-Fluorescence-1.scn). transcode/downsample assume RGB
channels; multi-channel handling is a future release. - Multi-image OME-TIFF where multiple
<Image>series each carry
their own pyramid.infoshows image 0 only.
Unchanged
- All other CLI surfaces (region, transcode/downsample/convert
on natively-tiled sources, etc.). - Output bytes from natively-tiled-source operations — same bytes
as v0.14.
v0.14.0 — info quality estimate + Makefile nohtj2k drop
Added
wsitools infonow includes a per-level codec quality summary
alongside compression. JPEG levels show estimated Q value +
chroma subsampling (4:4:4 / 4:2:2 / 4:2:0). JPEG 2000 levels show
reversible/irreversible transform + layer count. WebP levels show
lossless flag + estimated Q. Lossless codecs (LZW/Deflate/None)
surface as "lossless". Other codecs (AVIF, JPEG XL, HTJ2K)
currently surface compression only; quality inspectors land in
future releases without info-command changes.- New
cmd/wsitools/quality/package with pluggable Inspector
interface and per-codec subpackages:quality/jpeg,
quality/jpeg2000,quality/webp. New codecs register via
quality.Registerin their init().
Changed
- Dropped
-tags nohtj2kfromMakefile's defaultbuild/
installtargets. Local builds now exercise the full htj2k cgo
path against openjph (brew install openjphon macOS). Opt-out
withgo build -tags nohtj2k ./cmd/wsitoolsif needed.
Unchanged
- All other CLI surfaces (transcode, downsample, convert, dump-ifds,
extract, hash, doctor, version, region). - Output bytes from
transcode/downsample/convert— same
bytes as v0.13.
v0.13.0 — region subcommand
Added
-
wsitools regionsubcommand — extract a rectangular pixel region
from a slide at a chosen pyramid level and write as PNG.Flags:
--level N(required) — pyramid level index.--rect X,Y,W,HOR--x X --y Y --w W --h H(mutually
exclusive; one form required).--image N(default 0) — for multi-image OME-TIFF.--format rgb|rgba(default rgb).-o, --output PATH(required) — PNG output path.-f, --force— overwrite existing output file.
Out-of-bounds regions are white-filled per opentile-go v0.25's
ReadRegion semantics.Examples:
wsitools region --level 0 --rect 1000,1000,512,512 -o patch.png slide.svs wsitools region --level 2 --x 0 --y 0 --w 512 --h 512 -o thumb.png slide.svs
Dependencies
- Bumped
github.com/wsilabs/opentile-goto v0.25.0 (adds the
ReadRegionfamily the new subcommand consumes).
Unchanged
- All other CLI surfaces (transcode, downsample, convert, info,
dump-ifds, extract, hash, doctor, version). - Output bytes from existing commands — pixel-identical to v0.12
(verified viamake goldens-byte-stable).
v0.12.0 — adopt opentile-go v0.24 Level value-type + DecodedTile
Adopts opentile-go v0.24.0 (Level value-type + DecodedTile). No
behavior change for end-users; this is an internal type migration.
Dependencies
- Bumped
github.com/wsilabs/opentile-goto v0.24.0 (BREAKING
upstream: Level/Image interfaces → value-type structs; tile reads
moved to *Slide methods).
Changed (internal)
- Every
slide.Levels()[i].Tile(...)migrated to
slide.RawTile(i, ...). Every Level field access migrated from
method call to struct field. CLI surface unchanged.
Unchanged
- All CLI surfaces (transcode, downsample, convert, info, dump-ifds,
extract, hash, doctor, version). - Output bytes — pixel-identical to v0.11 (verified via
make goldens-byte-stable).
v0.11.0 — adopt opentile-go v0.23 *Slide API
Migrates to opentile-go v0.23.0's new *Slide API. No behavior change
for end-users; this is an internal type migration. CLI surface
unchanged.
Dependencies
- Bumped
github.com/wsilabs/opentile-goto v0.23.0 (BREAKING upstream:
opentile.Tilerinterface replaced by*opentile.Slidestruct).
Changed (internal)
- Every
opentile.OpenTiler(...)call site migrated to
opentile.OpenFile(...). Everyopentile.Tilertyped variable
migrated to*opentile.Slide. Method-call shape preserved exactly.
Unchanged
- All CLI surfaces (transcode, downsample, convert, info, dump-ifds,
extract, hash, doctor, version). - Output bytes — pixel-identical AND byte-identical to v0.10 (verified
viamake goldens-byte-stable).
v0.10.0 — deterministic tile order
Adds deterministic tile-write order. Output of transcode, downsample, and convert is now byte-identical across runs and CPU counts.
Added
--tile-order={row-major|hilbert|morton}CLI flag on transcode, downsample, convert. Format-validated: SVS accepts row-major only; COG-WSI, generic-TIFF, OME-TIFF accept all three.internal/tiff/tileorderpackage with OrderStrategy interface, RowMajor, HilbertCurve, Morton implementations, and a ByName registry.- Reorder buffer in streamwriter Sink (bounded; back-pressures workers when full).
- cogwsiwriter finalize walks the tile spool in the chosen strategy's emission order.
make goldens-byte-stableMakefile target asserting deterministic output across GOMAXPROCS.docs/superpowers/golden-masters-v0.10.0.txtcanonical byte-stable hashes per (format × codec × sample).
Changed
- Output bytes are now stable run-to-run. Existing pre-v0.10 file SHAs in
docs/superpowers/golden-masters-v0.6.0-transcode.txtare historical-only (annotated as such) — they were captured from a non-deterministic writer and are not reproducible.
Unchanged
- All CLI surfaces and Go APIs (additive only).
- Default behavior — unflagged invocations produce row-major output as before.
- Pixel-equality with v0.9 — only on-disk byte order changes, never decoded pixel values.
Install
```sh
go install github.com/wsilabs/wsitools/cmd/wsitools@v0.10.0
```
v0.9.0 — adopt opentile-go v0.22 decoder + resample
Switches wsitools to consume opentile-go v0.22.0's new decoder + resample subpackages. Deletes wsitools' own internal/decoder + internal/resample. Output is pixel-identical to v0.8.1 (verified via wsitools hash --mode pixel — byte-level file SHAs vary run-to-run on both v0.8 and v0.9 due to the concurrent tile-writer, but decoded pixel content is preserved exactly).
Dependencies
- Bumped
github.com/wsilabs/opentile-goto v0.22.0 (adds publicdecoder/+resample/subpackages).
Internal
- Deleted
internal/decoder/(JPEG + JPEG 2000 decoders now sourced fromopentile-go/decoder/{jpeg,jpeg2000}). - Deleted
internal/resample/(area-averaging resampler now sourced fromopentile-go/resampleBox kernel). transcode.go,downsample.go,hash.go,extract.go, andinternal/codec/jpeg/jpeg_test.goall use the registry-based decoder lookup with the new image-awareDecodesignature.cmd/wsitools/main.goblank-importsopentile-go/decoder/allto register every codec at startup.- Makefile passes
-tags nohtj2kby default (htj2k decoder requires openjph; opt back in by removing the tag once openjph is installed).
Unchanged
- All CLI surfaces (transcode, downsample, convert, info, dump-ifds, extract, hash, doctor, version).
- All output formats; decoded pixels byte-identical to v0.8.1.
internal/codec/(encoders) unchanged.
Install
```sh
go install github.com/wsilabs/wsitools/cmd/wsitools@v0.9.0
```