You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 02b6456
Browse filesBrowse the repository at this point in the historyBrowse files
authored and
Aaron Kanzer
committed
0.2.0: fixes and bounded previews from a full-archive stress test
Fixed: percent-encoding of paths with #, spaces, non-ASCII (39 datasets
were unreachable); decoded listing names; one- and two-word dataset ids;
a temp-file race on concurrent cold catalog loads; unbounded crawls in
find_zarr/thumbnail on million-file trees (20+ minutes); manifest size
guard (710 MB gzipped on a 4.7 M-file dataset); strip-sampled preview
shape; clear errors for renamed and landing-zone paths; tiff_info on
files without a buildable series.
Added: preview_plane (bounded reads for any file size: pyramid level,
row sampling by offset, strip sampling, tiled centre region), read_region,
tiff_info, terafly_levels/terafly_thumbnail for fMOST trees,
first_images, extensions_under, manifest_size. HttpFile: 256 KB blocks
for metadata, direct range requests for large reads (21.7 GB OME-TIFF
open 81 MB to 25 MB; 1 GB page 108 s to 27 s). Parallel pool for
latency-bound sampling only. imagecodecs and pillow become dependencies.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GDpV8346GxohHCft1jrrGT
Copy file name to clipboardExpand all lines: README.md
+34-8Lines changed: 34 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -36,14 +36,18 @@ The Brain Image Library is the BRAIN Initiative's repository for whole-brain mic
36
36
37
37
BIL's own [`brainimagelibrary`](https://pypi.org/project/brainimagelibrary/) package (py-brain-sdk, 0.0.23, GPL-3.0) wraps the metadata API and downloads datasets, with resumable transfers and citation lookups. Its source (read, not assumed, on 2026-09-08) contains no image reading at all: the only route from a BIL id to pixels is `DatasetInventory.download()`, which fetches the files to disk. For a 27 GB light-sheet stack that is the whole stack, to look at one slice.
38
38
39
+
This package was then run against the archive rather than a handful of friendly datasets: random samples across every technique, the largest trees (4.7 million files), the largest single files (a 21.7 GB OME-TIFF, a 10 GB single-strip plane, 199 GB TIFFs), paths with `#` and spaces and non-ASCII characters, two-word and one-word dataset ids, stale inventory paths, landing-zone paths, and sixteen threads on a cold cache. What that found is fixed and covered by tests; what it cannot read yet is stated below.
40
+
39
41
This package is the other half. It reads BIL in place:
40
42
41
43
-**A typed, in-memory catalog of the whole archive** from BIL's daily inventory TSV, with structured filters and a light-sheet finder that unions the inventory's technique field with BIL's fulltext index. The technique field alone says light sheet for 348 datasets; the union finds 808. The rest are tagged `other` and only say light sheet in their abstract or instrument record.
42
44
-**Single-slice TIFF reads** straight from the download server. The dominant BIL layout is one TIFF per z-plane, about 16 MB each; one slice is one request.
43
45
-**OME-Zarr stores** opened lazily through zarr's HTTP store, with pyramid levels checked against what the server actually has (one BIL store declares eight levels and serves seven).
44
46
-**Thumbnails that read as little as possible**: the middle slice of a TIFF stack, or the coarsest level of a zarr pyramid.
45
-
-**A seekable HTTP file object** (`HttpFile`) so tifffile fetches only the pages you ask for from a large multi-page TIFF.
46
-
-**BIL's per-dataset manifest** (path, size, MD5, URL for every file) as one gzipped GET, so a deep tree lists in one request.
47
+
-**Bounded previews of files of any size.**`preview_plane()` reads the coarsest pyramid level, or every k-th row by offset from an uncompressed plane, or a sample of strips, or a centre region of a tiled page. A 10 GB single-strip MERFISH mosaic previews from 63 MB of reads; `asarray()` would fetch all 10 GB.
48
+
-**fMOST TeraFly trees** (`RES_<x>x<y>x<z>_/` folders, 985 datasets) previewed from the coarsest resolution folder, stitched. The full-resolution folder of one such brain holds 1.6 million files and is never listed.
49
+
-**A seekable HTTP file object** (`HttpFile`) so tifffile fetches only the IFDs, pages, tiles or regions you ask for from a large TIFF, with `read_region()` for a rectangle of a tiled page.
50
+
-**BIL's per-dataset manifest** (path, size, MD5, URL for every file) as one gzipped GET, so a deep tree lists in one request. Guarded by size: a 4.7 million file dataset's manifest is 710 MB gzipped, and `walk()` crawls instead of downloading that.
47
51
48
52
Measured on 2026-09-08 from a residential connection, against the live archive:
49
53
@@ -52,12 +56,19 @@ Measured on 2026-09-08 from a residential connection, against the live archive:
52
56
| Load the full catalog (14,224 datasets) | 1.0 s cold, 0.3 s from the on-disk copy |
53
57
| Light-sheet datasets found | 348 by technique field, 808 with fulltext union |
54
58
| Read one 16 MB light-sheet slice (4501 x 3828 uint16) | 0.40 s, 45 MB/s single stream |
55
-
| Thumbnail of a 1,923-slice, 27 GB stack | 0.25 s to 0.56 s, one file read |
59
+
| Thumbnail of a 1,923-slice, 27 GB stack | 0.3 s, one file read |
56
60
| Thumbnail of a 50 GB OME-Zarr store (848 x 6300 x 9600) | 0.65 s, coarsest level only |
57
-
| Eight parallel streams | 19 MB/s aggregate, slower than one stream, so reads are sequential |
61
+
| Thumbnail of a 4.5 TB fMOST TeraFly brain | 4 to 5 s, coarsest resolution folder, about 25 small block reads |
62
+
| Preview of a 10 GB single-strip plane (84289 x 61974 uint16) | 11 s, 512 rows fetched by offset |
63
+
| Open a 21.7 GB, 92-page BigTIFF OME-TIFF and read a 512 x 512 region | 7 s to walk the IFDs, 1.3 s for the region |
64
+
| Read one full 1 GB page of that file | 27 s at 38 MB/s in 6 range requests |
65
+
| Eight parallel streams on 16 MB slices | 32 MB/s aggregate against 39 MB/s for one stream |
58
66
| Manifest for a 1,923-file dataset | 352 KB gzipped, one request |
67
+
| 1,000 full metadata records | 1.4 s via batched POST |
68
+
69
+
Bulk transfers are sequential because parallel streams measured slower. Sampling reads (rows 20 MB apart, TeraFly blocks) are latency-bound and go through a small pool: 512 sequential range requests took 68 s for 63 MB, eight workers take a few seconds.
59
70
60
-
Dependencies are `requests`, `numpy`and `tifffile`. zarr and pandas are extras.
71
+
Dependencies are `requests`, `numpy`, `tifffile`, `imagecodecs` (LZW and Deflate strips are common in BIL TIFFs) and `pillow` (a second decoder for ImageJ Deflate stacks that libdeflate rejects). zarr and pandas are extras.
bil.download(entry, "out/") # the one function that writes image bytes to disk
114
125
```
115
126
116
-
Any of these accept a BIL id, a `Dataset`, a `/bil/data/...` path from the metadata, or a download-server URL.
127
+
Any of these accept a BIL id, a `Dataset`, a `/bil/data/...` path from the metadata, or a download-server URL. Paths are percent-encoded for you: 39 inventory paths carry spaces, `#` or non-ASCII characters, and an unencoded `#` is a URL fragment. Two things the inventory gets wrong are reported rather than hidden: a directory renamed on the server since the inventory was built (the error names the parent to list) and a dataset still under BIL's landing zone, which is not public.
Formats this package does not decode raise `UnsupportedFormatError` naming what does: JPEG 2000 (`.jp2`, 5,787 datasets, mostly STPT and fMOST sections; use `download()` then glymur), Imaris (`.ims`, h5py), NIfTI. Reading `.jp2` in place is the obvious next addition; the format has resolution levels built in, so a thumbnail should not need the whole file.
139
+
### Previews of large files
140
+
141
+
```python
142
+
bil.tiff_info(entry) # pages, shape, dtype, tiled, pyramid levels, from the IFDs only
143
+
bil.preview_plane(entry, max_size=512) # bounded read whatever the file size, see the table above
144
+
bil.read_region(entry, rows=(16000, 18048), cols=(7000, 9048), page=0) # tiles covering a rectangle, [zarr] extra
145
+
bil.terafly_levels("ace-cap-cop") # [((793, 1268, 350), url), ...] coarsest first, for fMOST trees
146
+
bil.terafly_thumbnail("ace-cap-cop") # one stitched plane through the middle of the coarsest level
147
+
bil.first_images("ace-owl-cot") # the first folder of TIFFs by bounded descent, never a crawl
148
+
```
149
+
150
+
`thumbnail()` tries these in order: a TeraFly tree, an OME-Zarr store, then the first folder of TIFFs. It never lists a whole tree; the unbounded version sat on a 1.6 million file fMOST dataset for over twenty minutes before the bound existed.
151
+
152
+
### What it does not read
153
+
154
+
Formats this package does not decode raise `UnsupportedFormatError` naming what does. Across the archive's techniques the big one is JPEG 2000 (`.jp2`, 5,787 datasets: most STPT sections, viral tracing and enhancer-labelling brains): `download()` then glymur. Also Imaris `.ims` (HDF5, h5py), NIfTI (nibabel), MERFISH `.dax` frames (raw uint16, numpy), SWC morphologies (navis), and tables (pandas). Reading `.jp2` in place is the obvious next addition; the format has resolution levels built in, so a preview should not need the whole file.
Every test runs live against BIL, no mocks, the same philosophy as the rest of the scigantic-* packages. The suite takes about 15 seconds. CI runs Python 3.10 through 3.14 plus `mypy --strict`.
191
+
Every test runs live against BIL, no mocks, the same philosophy as the rest of the scigantic-* packages. The suite takes one to two minutes depending on the server and includes the stress cases above: the 21 GB OME-TIFF, the 10 GB plane, the `#` path, the 4.7 million file manifest guard, the TeraFly tree, the Pillow fallback, six threads loading the catalog cold. CI runs Python 3.10 through 3.14 plus `mypy --strict`.
0 commit comments