micro-reader 0.0.3__tar.gz → 0.0.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {micro_reader-0.0.3 → micro_reader-0.0.4}/PKG-INFO +73 -5
- {micro_reader-0.0.3 → micro_reader-0.0.4}/README.md +72 -4
- {micro_reader-0.0.3 → micro_reader-0.0.4}/pyproject.toml +1 -1
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/__init__.py +5 -4
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/_base.py +52 -1
- micro_reader-0.0.4/src/micro_reader/_cfb.py +200 -0
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/_errors.py +6 -0
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/_fileset.py +49 -1
- micro_reader-0.0.4/src/micro_reader/_groups.py +216 -0
- micro_reader-0.0.4/src/micro_reader/_parts.py +188 -0
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/_registry.py +33 -10
- micro_reader-0.0.4/src/micro_reader/backends/_ets.py +149 -0
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/backends/_tiff_metadata.py +228 -3
- micro_reader-0.0.4/src/micro_reader/backends/_tiff_variants.py +239 -0
- micro_reader-0.0.4/src/micro_reader/backends/_vsi_tree.py +154 -0
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/backends/companion.py +78 -5
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/backends/cv7000.py +67 -0
- micro_reader-0.0.4/src/micro_reader/backends/dv.py +372 -0
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/backends/harmony.py +49 -0
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/backends/incell.py +7 -0
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/backends/lif.py +19 -25
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/backends/metaxpress.py +39 -1
- micro_reader-0.0.4/src/micro_reader/backends/oif.py +479 -0
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/backends/oir.py +57 -7
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/backends/tiff.py +254 -26
- micro_reader-0.0.4/src/micro_reader/backends/vsi.py +448 -0
- {micro_reader-0.0.3 → micro_reader-0.0.4}/.gitignore +0 -0
- {micro_reader-0.0.3 → micro_reader-0.0.4}/LICENSE +0 -0
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/_axes.py +0 -0
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/_codecs.py +0 -0
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/_index.py +0 -0
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/_io.py +0 -0
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/_jpeg_restarts.py +0 -0
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/_memory.py +0 -0
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/_metadata.py +0 -0
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/_outline.py +0 -0
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/_plate.py +0 -0
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/_pool.py +0 -0
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/_stream.py +0 -0
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/backends/__init__.py +0 -0
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/backends/_czi_metadata.py +0 -0
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/backends/_lif_metadata.py +0 -0
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/backends/_nd2_legacy.py +0 -0
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/backends/_nd2_metadata.py +0 -0
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/backends/_nd2_structure.py +0 -0
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/backends/czi.py +0 -0
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/backends/ims.py +0 -0
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/backends/mrc.py +0 -0
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/backends/nd2.py +0 -0
- {micro_reader-0.0.3 → micro_reader-0.0.4}/src/micro_reader/backends/raster.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: micro-reader
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.4
|
|
4
4
|
Summary: Low-level, memory-bounded pixel reading for microscopy formats: read exactly the rectangle you ask for.
|
|
5
5
|
Project-URL: Homepage, https://github.com/bugraoezdemir/micro-reader
|
|
6
6
|
Project-URL: Source, https://github.com/bugraoezdemir/micro-reader
|
|
@@ -54,7 +54,7 @@ Description-Content-Type: text/markdown
|
|
|
54
54
|
|
|
55
55
|
**Memory-bounded pixel reader for microscopy images.**
|
|
56
56
|
|
|
57
|
-
`micro-reader` reads regions of large microscopy files without loading the entire image into memory. It provides a common interface across TIFF, CZI, ND2, Leica, OIR, Imaris, MRC,
|
|
57
|
+
`micro-reader` reads regions of large microscopy files without loading the entire image into memory. It provides a common interface across TIFF, CZI, ND2, Leica, OIR, OIB / OIF, Olympus VSI, Imaris, DeltaVision, MRC, common raster formats, OME companion file sets, and high-content screening plates (Harmony, MetaXpress, IN Cell, CellVoyager).
|
|
58
58
|
|
|
59
59
|
> **Status: pre-alpha (0.0.x)**
|
|
60
60
|
>
|
|
@@ -213,6 +213,31 @@ resolution
|
|
|
213
213
|
|
|
214
214
|
Only dimensions actually present in a file appear in an image's index.
|
|
215
215
|
|
|
216
|
+
### Files made of several files
|
|
217
|
+
|
|
218
|
+
Some formats always come as several files: an OIR file with its continuation files (`name_00001`), a Micro-Manager stack split over `..._MMStack_Pos0.ome.tif`, `..._MMStack_Pos0_1.ome.tif`, an NDTiff folder, an Olympus OIF with its `.oif.files` folder, an Olympus VSI with its `_name_` folder, a DeltaVision image with its softWoRx `.log`, a plate's side files (Harmony `Assaylayout/` and `FFC_Profile/`, CellVoyager settings files, MetaXpress `_thumb` previews). Opening any of these files opens the whole image:
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
f = micro_reader.open("cells_R3D.dv.log")
|
|
222
|
+
f.path # the main file: "cells_R3D.dv"
|
|
223
|
+
f.opened # what was opened: "cells_R3D.dv.log"
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
You can also open the folder. A folder opens the image it belongs to (an NDTiff dataset folder, a Harmony or CellVoyager measurement folder), or the one image it holds. A folder with several images raises an error that lists them.
|
|
227
|
+
|
|
228
|
+
To sort many files at once, for example everything under a folder you want to convert, `group_files` says which files make one image:
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
g = micro_reader.group_files("experiment/")
|
|
232
|
+
for group in g.groups:
|
|
233
|
+
group.main # the file to open: "slide.vsi", "plate/Index.idx.xml"
|
|
234
|
+
group.parts # the other files of a multi-file image
|
|
235
|
+
group.members # for a plate or companion index: the files its images come from
|
|
236
|
+
g.singles # every other file, as given
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
A file that a plate or companion index names is listed only in that index's group. When the main file of a multi-file image is missing, its group has `main=None`.
|
|
240
|
+
|
|
216
241
|
## Axes
|
|
217
242
|
|
|
218
243
|
An image array uses these axes:
|
|
@@ -406,7 +431,7 @@ img_scene0_level2 = f.select(scene=0, resolution=2)[0]
|
|
|
406
431
|
Supported pyramids include:
|
|
407
432
|
|
|
408
433
|
* **Imaris**: all stored levels
|
|
409
|
-
* **TIFF**: OME-TIFF and
|
|
434
|
+
* **TIFF**: OME-TIFF and whole-slide pyramids (SVS, NDPI, SCN, BIF, QPTIFF, Philips)
|
|
410
435
|
* **CZI**: stored pyramids of whole scenes
|
|
411
436
|
|
|
412
437
|
Other formats expose a single resolution level.
|
|
@@ -459,6 +484,12 @@ f = micro_reader.open("plate.czi", plates=True)
|
|
|
459
484
|
|
|
460
485
|
Missing plane files are read as zeros, with a `MissingImagesWarning`. A field without any of its files is left out.
|
|
461
486
|
|
|
487
|
+
A plane file whose size or pixel type differs from what the index describes raises an error when it is read. To find such files before reading anything, check them all at once:
|
|
488
|
+
|
|
489
|
+
```python
|
|
490
|
+
f.check_members() # opens every plane file once; returns how many were checked
|
|
491
|
+
```
|
|
492
|
+
|
|
462
493
|
When the channels of a field have different z sampling, `image.parts` gives one image per group of channels that share it.
|
|
463
494
|
|
|
464
495
|
## Metadata
|
|
@@ -524,13 +555,16 @@ The position is omitted when it cannot be determined reliably.
|
|
|
524
555
|
|
|
525
556
|
| Format | Notes |
|
|
526
557
|
| -------------------- | ----------------------------------------------------- |
|
|
527
|
-
| **TIFF / BigTIFF** | TIFF, OME-TIFF, ImageJ, LSM, STK
|
|
558
|
+
| **TIFF / BigTIFF** | TIFF, OME-TIFF, ImageJ, LSM, STK, Micro-Manager, whole-slide TIFFs |
|
|
528
559
|
| **OME companion** | `.companion.ome` sets of OME-TIFF files |
|
|
529
560
|
| **CZI** | Zeiss CZI, including mosaic tiles and stitched scenes |
|
|
530
561
|
| **ND2** | Nikon ND2, including legacy JPEG 2000 ND2 |
|
|
531
562
|
| **Leica** | LIF, LOF, LIFEXT, XLIF, XLEF, XLCF |
|
|
532
563
|
| **OIR** | Olympus / Evident FluoView |
|
|
564
|
+
| **OIB / OIF** | Olympus FluoView FV1000 / FV1200 / FV3000 |
|
|
565
|
+
| **VSI** | Olympus / Evident cellSens and VS120 / VS200 slide scanners |
|
|
533
566
|
| **Imaris** | `.ims`, with the `[ims]` extra |
|
|
567
|
+
| **DeltaVision** | `.dv`, `.r3d` (softWoRx, OMX, Priism) |
|
|
534
568
|
| **MRC** | Common MRC modes and extended headers |
|
|
535
569
|
| **Plates (HCS)** | Harmony, MetaXpress, IN Cell and CellVoyager plates |
|
|
536
570
|
| **JPEG / PNG / BMP** | Common raster image formats |
|
|
@@ -549,6 +583,14 @@ Supported TIFF formats include:
|
|
|
549
583
|
* ImageJ
|
|
550
584
|
* Zeiss LSM
|
|
551
585
|
* MetaMorph STK
|
|
586
|
+
* Micro-Manager stacks (MMStack and NDTiff), also when one acquisition is spread over several files
|
|
587
|
+
* Whole-slide TIFFs: Aperio SVS, Hamamatsu NDPI, Leica SCN, Ventana BIF, PerkinElmer / Akoya QPTIFF and Philips
|
|
588
|
+
|
|
589
|
+
Micro-Manager stage positions are scenes. NDTiff channel names and pixel sizes come from the dataset's index and frame metadata.
|
|
590
|
+
|
|
591
|
+
A whole-slide file gives the slide first, then the other pictures it stores as further scenes, as Bio-Formats lists them: `label` and `macro` (overview), and for some formats `thumbnail` (QPTIFF) or `probability` (BIF tissue map). Pixel sizes, positions and channel names come from each vendor's own metadata.
|
|
592
|
+
|
|
593
|
+
Ventana BIF scans store overlapping tiles. They are composed as Roche's description of the format places them. Files from scanners other than the VENTANA DP 200 are not supported.
|
|
552
594
|
|
|
553
595
|
Compressed TIFF data is decoded only for the strips or tiles needed by the requested region.
|
|
554
596
|
|
|
@@ -580,7 +622,15 @@ An OME companion file (`.companion.ome`) describes images stored in separate TIF
|
|
|
580
622
|
f = micro_reader.open("data.companion.ome")
|
|
581
623
|
```
|
|
582
624
|
|
|
583
|
-
Opening one of the TIFF files on its own reads only that file, with its pixel sizes, channels and position taken from the companion. If the companion is missing, the file is read without pixel sizes, with a `CompanionWarning`.
|
|
625
|
+
Opening one of the TIFF files on its own reads only that file, with its pixel sizes, channels and position taken from the companion. If the companion is missing, the file is read without pixel sizes, with a `CompanionWarning`. A TIFF whose metadata lives in another OME-TIFF (instead of a `.companion.ome`) takes it from that file in the same way.
|
|
626
|
+
|
|
627
|
+
Some OME-TIFF sets keep the full OME-XML in their TIFF files instead of a companion. Opening one of those files reads the whole set. To read only the planes stored in that file, with the set's metadata:
|
|
628
|
+
|
|
629
|
+
```python
|
|
630
|
+
f = micro_reader.open("set_t001.ome.tif", multifile=False)
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
Micro-Manager stacks always open whole, also with `multifile=False`: they split their files by size, so one file does not hold a complete part of the image.
|
|
584
634
|
|
|
585
635
|
### CZI
|
|
586
636
|
|
|
@@ -643,6 +693,18 @@ These are represented using the common image/index model.
|
|
|
643
693
|
|
|
644
694
|
Olympus / Evident FluoView OIR files are supported, including companion files and spectral scans.
|
|
645
695
|
|
|
696
|
+
### Olympus OIB and OIF
|
|
697
|
+
|
|
698
|
+
Older FluoView files come in two forms with the same content: an OIB is one file, an OIF is a `.oif` settings file plus a `.oif.files` folder with one TIFF per plane. Both open as one `t c z y x` image, with pixel sizes, z and time steps, channel names, colours and wavelengths. Opening any file in the `.oif.files` folder opens the OIF.
|
|
699
|
+
|
|
700
|
+
### Olympus VSI
|
|
701
|
+
|
|
702
|
+
A cellSens VSI is a `.vsi` file plus a `_<name>_` folder that holds the pixels (`stack1/frame_t.ets`, `stack10000/frame_t.ets`, ...). The images are listed as Bio-Formats lists them: one per scanned area, named as in cellSens, with the slide label (`label`) and overview (`overview`) when the scanner took them, and the slide photo (`macro`) last. Each scanned area keeps its resolution levels.
|
|
703
|
+
|
|
704
|
+
Images are `t c z y x` (`t c z y x s` for brightfield RGB scans), with pixel sizes, z and time steps, and each channel's name, colour and emission wavelength. Stage positions are not read.
|
|
705
|
+
|
|
706
|
+
Opening the `_<name>_` folder, a `stack` folder or a file in it opens the `.vsi`. An `.ets` file whose `.vsi` is missing is read alone, with a warning, when it is a plain 2-D image; its size is then rounded up to whole tiles.
|
|
707
|
+
|
|
646
708
|
### Imaris
|
|
647
709
|
|
|
648
710
|
Install Imaris support with:
|
|
@@ -655,6 +717,12 @@ Imaris resolution level 0 is exposed at the actual image size, with Imaris chunk
|
|
|
655
717
|
|
|
656
718
|
Chunked compression is decoded in parallel.
|
|
657
719
|
|
|
720
|
+
### DeltaVision
|
|
721
|
+
|
|
722
|
+
DeltaVision files (`.dv`, `.r3d`, `.r3d_d3d`, also deconvolved and projected ones) are recognised by their content, whatever their extension. The image keeps the file's own order of time, waves and z (for example `ctzyx`).
|
|
723
|
+
|
|
724
|
+
Pixel sizes, and each channel's emission and excitation wavelengths, are read from the file. Channel names come from the softWoRx acquisition log next to it (`<file>.log`); a deconvolved or projected file takes the names from the log of the raw file it was made from. Stage positions are not read.
|
|
725
|
+
|
|
658
726
|
### MRC
|
|
659
727
|
|
|
660
728
|
Supported MRC modes include:
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Memory-bounded pixel reader for microscopy images.**
|
|
4
4
|
|
|
5
|
-
`micro-reader` reads regions of large microscopy files without loading the entire image into memory. It provides a common interface across TIFF, CZI, ND2, Leica, OIR, Imaris, MRC,
|
|
5
|
+
`micro-reader` reads regions of large microscopy files without loading the entire image into memory. It provides a common interface across TIFF, CZI, ND2, Leica, OIR, OIB / OIF, Olympus VSI, Imaris, DeltaVision, MRC, common raster formats, OME companion file sets, and high-content screening plates (Harmony, MetaXpress, IN Cell, CellVoyager).
|
|
6
6
|
|
|
7
7
|
> **Status: pre-alpha (0.0.x)**
|
|
8
8
|
>
|
|
@@ -161,6 +161,31 @@ resolution
|
|
|
161
161
|
|
|
162
162
|
Only dimensions actually present in a file appear in an image's index.
|
|
163
163
|
|
|
164
|
+
### Files made of several files
|
|
165
|
+
|
|
166
|
+
Some formats always come as several files: an OIR file with its continuation files (`name_00001`), a Micro-Manager stack split over `..._MMStack_Pos0.ome.tif`, `..._MMStack_Pos0_1.ome.tif`, an NDTiff folder, an Olympus OIF with its `.oif.files` folder, an Olympus VSI with its `_name_` folder, a DeltaVision image with its softWoRx `.log`, a plate's side files (Harmony `Assaylayout/` and `FFC_Profile/`, CellVoyager settings files, MetaXpress `_thumb` previews). Opening any of these files opens the whole image:
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
f = micro_reader.open("cells_R3D.dv.log")
|
|
170
|
+
f.path # the main file: "cells_R3D.dv"
|
|
171
|
+
f.opened # what was opened: "cells_R3D.dv.log"
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
You can also open the folder. A folder opens the image it belongs to (an NDTiff dataset folder, a Harmony or CellVoyager measurement folder), or the one image it holds. A folder with several images raises an error that lists them.
|
|
175
|
+
|
|
176
|
+
To sort many files at once, for example everything under a folder you want to convert, `group_files` says which files make one image:
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
g = micro_reader.group_files("experiment/")
|
|
180
|
+
for group in g.groups:
|
|
181
|
+
group.main # the file to open: "slide.vsi", "plate/Index.idx.xml"
|
|
182
|
+
group.parts # the other files of a multi-file image
|
|
183
|
+
group.members # for a plate or companion index: the files its images come from
|
|
184
|
+
g.singles # every other file, as given
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
A file that a plate or companion index names is listed only in that index's group. When the main file of a multi-file image is missing, its group has `main=None`.
|
|
188
|
+
|
|
164
189
|
## Axes
|
|
165
190
|
|
|
166
191
|
An image array uses these axes:
|
|
@@ -354,7 +379,7 @@ img_scene0_level2 = f.select(scene=0, resolution=2)[0]
|
|
|
354
379
|
Supported pyramids include:
|
|
355
380
|
|
|
356
381
|
* **Imaris**: all stored levels
|
|
357
|
-
* **TIFF**: OME-TIFF and
|
|
382
|
+
* **TIFF**: OME-TIFF and whole-slide pyramids (SVS, NDPI, SCN, BIF, QPTIFF, Philips)
|
|
358
383
|
* **CZI**: stored pyramids of whole scenes
|
|
359
384
|
|
|
360
385
|
Other formats expose a single resolution level.
|
|
@@ -407,6 +432,12 @@ f = micro_reader.open("plate.czi", plates=True)
|
|
|
407
432
|
|
|
408
433
|
Missing plane files are read as zeros, with a `MissingImagesWarning`. A field without any of its files is left out.
|
|
409
434
|
|
|
435
|
+
A plane file whose size or pixel type differs from what the index describes raises an error when it is read. To find such files before reading anything, check them all at once:
|
|
436
|
+
|
|
437
|
+
```python
|
|
438
|
+
f.check_members() # opens every plane file once; returns how many were checked
|
|
439
|
+
```
|
|
440
|
+
|
|
410
441
|
When the channels of a field have different z sampling, `image.parts` gives one image per group of channels that share it.
|
|
411
442
|
|
|
412
443
|
## Metadata
|
|
@@ -472,13 +503,16 @@ The position is omitted when it cannot be determined reliably.
|
|
|
472
503
|
|
|
473
504
|
| Format | Notes |
|
|
474
505
|
| -------------------- | ----------------------------------------------------- |
|
|
475
|
-
| **TIFF / BigTIFF** | TIFF, OME-TIFF, ImageJ, LSM, STK
|
|
506
|
+
| **TIFF / BigTIFF** | TIFF, OME-TIFF, ImageJ, LSM, STK, Micro-Manager, whole-slide TIFFs |
|
|
476
507
|
| **OME companion** | `.companion.ome` sets of OME-TIFF files |
|
|
477
508
|
| **CZI** | Zeiss CZI, including mosaic tiles and stitched scenes |
|
|
478
509
|
| **ND2** | Nikon ND2, including legacy JPEG 2000 ND2 |
|
|
479
510
|
| **Leica** | LIF, LOF, LIFEXT, XLIF, XLEF, XLCF |
|
|
480
511
|
| **OIR** | Olympus / Evident FluoView |
|
|
512
|
+
| **OIB / OIF** | Olympus FluoView FV1000 / FV1200 / FV3000 |
|
|
513
|
+
| **VSI** | Olympus / Evident cellSens and VS120 / VS200 slide scanners |
|
|
481
514
|
| **Imaris** | `.ims`, with the `[ims]` extra |
|
|
515
|
+
| **DeltaVision** | `.dv`, `.r3d` (softWoRx, OMX, Priism) |
|
|
482
516
|
| **MRC** | Common MRC modes and extended headers |
|
|
483
517
|
| **Plates (HCS)** | Harmony, MetaXpress, IN Cell and CellVoyager plates |
|
|
484
518
|
| **JPEG / PNG / BMP** | Common raster image formats |
|
|
@@ -497,6 +531,14 @@ Supported TIFF formats include:
|
|
|
497
531
|
* ImageJ
|
|
498
532
|
* Zeiss LSM
|
|
499
533
|
* MetaMorph STK
|
|
534
|
+
* Micro-Manager stacks (MMStack and NDTiff), also when one acquisition is spread over several files
|
|
535
|
+
* Whole-slide TIFFs: Aperio SVS, Hamamatsu NDPI, Leica SCN, Ventana BIF, PerkinElmer / Akoya QPTIFF and Philips
|
|
536
|
+
|
|
537
|
+
Micro-Manager stage positions are scenes. NDTiff channel names and pixel sizes come from the dataset's index and frame metadata.
|
|
538
|
+
|
|
539
|
+
A whole-slide file gives the slide first, then the other pictures it stores as further scenes, as Bio-Formats lists them: `label` and `macro` (overview), and for some formats `thumbnail` (QPTIFF) or `probability` (BIF tissue map). Pixel sizes, positions and channel names come from each vendor's own metadata.
|
|
540
|
+
|
|
541
|
+
Ventana BIF scans store overlapping tiles. They are composed as Roche's description of the format places them. Files from scanners other than the VENTANA DP 200 are not supported.
|
|
500
542
|
|
|
501
543
|
Compressed TIFF data is decoded only for the strips or tiles needed by the requested region.
|
|
502
544
|
|
|
@@ -528,7 +570,15 @@ An OME companion file (`.companion.ome`) describes images stored in separate TIF
|
|
|
528
570
|
f = micro_reader.open("data.companion.ome")
|
|
529
571
|
```
|
|
530
572
|
|
|
531
|
-
Opening one of the TIFF files on its own reads only that file, with its pixel sizes, channels and position taken from the companion. If the companion is missing, the file is read without pixel sizes, with a `CompanionWarning`.
|
|
573
|
+
Opening one of the TIFF files on its own reads only that file, with its pixel sizes, channels and position taken from the companion. If the companion is missing, the file is read without pixel sizes, with a `CompanionWarning`. A TIFF whose metadata lives in another OME-TIFF (instead of a `.companion.ome`) takes it from that file in the same way.
|
|
574
|
+
|
|
575
|
+
Some OME-TIFF sets keep the full OME-XML in their TIFF files instead of a companion. Opening one of those files reads the whole set. To read only the planes stored in that file, with the set's metadata:
|
|
576
|
+
|
|
577
|
+
```python
|
|
578
|
+
f = micro_reader.open("set_t001.ome.tif", multifile=False)
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
Micro-Manager stacks always open whole, also with `multifile=False`: they split their files by size, so one file does not hold a complete part of the image.
|
|
532
582
|
|
|
533
583
|
### CZI
|
|
534
584
|
|
|
@@ -591,6 +641,18 @@ These are represented using the common image/index model.
|
|
|
591
641
|
|
|
592
642
|
Olympus / Evident FluoView OIR files are supported, including companion files and spectral scans.
|
|
593
643
|
|
|
644
|
+
### Olympus OIB and OIF
|
|
645
|
+
|
|
646
|
+
Older FluoView files come in two forms with the same content: an OIB is one file, an OIF is a `.oif` settings file plus a `.oif.files` folder with one TIFF per plane. Both open as one `t c z y x` image, with pixel sizes, z and time steps, channel names, colours and wavelengths. Opening any file in the `.oif.files` folder opens the OIF.
|
|
647
|
+
|
|
648
|
+
### Olympus VSI
|
|
649
|
+
|
|
650
|
+
A cellSens VSI is a `.vsi` file plus a `_<name>_` folder that holds the pixels (`stack1/frame_t.ets`, `stack10000/frame_t.ets`, ...). The images are listed as Bio-Formats lists them: one per scanned area, named as in cellSens, with the slide label (`label`) and overview (`overview`) when the scanner took them, and the slide photo (`macro`) last. Each scanned area keeps its resolution levels.
|
|
651
|
+
|
|
652
|
+
Images are `t c z y x` (`t c z y x s` for brightfield RGB scans), with pixel sizes, z and time steps, and each channel's name, colour and emission wavelength. Stage positions are not read.
|
|
653
|
+
|
|
654
|
+
Opening the `_<name>_` folder, a `stack` folder or a file in it opens the `.vsi`. An `.ets` file whose `.vsi` is missing is read alone, with a warning, when it is a plain 2-D image; its size is then rounded up to whole tiles.
|
|
655
|
+
|
|
594
656
|
### Imaris
|
|
595
657
|
|
|
596
658
|
Install Imaris support with:
|
|
@@ -603,6 +665,12 @@ Imaris resolution level 0 is exposed at the actual image size, with Imaris chunk
|
|
|
603
665
|
|
|
604
666
|
Chunked compression is decoded in parallel.
|
|
605
667
|
|
|
668
|
+
### DeltaVision
|
|
669
|
+
|
|
670
|
+
DeltaVision files (`.dv`, `.r3d`, `.r3d_d3d`, also deconvolved and projected ones) are recognised by their content, whatever their extension. The image keeps the file's own order of time, waves and z (for example `ctzyx`).
|
|
671
|
+
|
|
672
|
+
Pixel sizes, and each channel's emission and excitation wavelengths, are read from the file. Channel names come from the softWoRx acquisition log next to it (`<file>.log`); a deconvolved or projected file takes the names from the log of the raw file it was made from. Stage positions are not read.
|
|
673
|
+
|
|
606
674
|
### MRC
|
|
607
675
|
|
|
608
676
|
Supported MRC modes include:
|
|
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "micro-reader"
|
|
9
|
-
version = "0.0.
|
|
9
|
+
version = "0.0.4"
|
|
10
10
|
description = "Low-level, memory-bounded pixel reading for microscopy formats: read exactly the rectangle you ask for."
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
requires-python = ">=3.11"
|
|
@@ -7,22 +7,23 @@
|
|
|
7
7
|
from ._base import INDEX_NAMES, PLATE_INDICES, SAMPLED_INDICES, File, Image, ReadStats
|
|
8
8
|
from ._metadata import Channel, Metadata, Quantity
|
|
9
9
|
from ._plate import Acquisition, Field, Plate, Well
|
|
10
|
-
from ._errors import (CompanionWarning, CorruptFileError, MicroReaderError,
|
|
10
|
+
from ._errors import (CompanionWarning, CompositeWarning, CorruptFileError, MicroReaderError,
|
|
11
11
|
MissingImagesWarning, UnsupportedFormatError)
|
|
12
12
|
from ._memory import MemoryLimitError, memory_limit, set_memory_limit
|
|
13
13
|
from ._io import cache_limit, cached_bytes, set_cache_limit
|
|
14
14
|
from ._pool import decode_threads, set_decode_threads
|
|
15
15
|
from ._registry import BACKENDS, open # noqa: A004 - the public entry point
|
|
16
|
+
from ._groups import FileGroup, Grouping, group_files
|
|
16
17
|
from .backends.czi import DamagedFileWarning
|
|
17
18
|
|
|
18
|
-
__version__ = "0.0.
|
|
19
|
+
__version__ = "0.0.4"
|
|
19
20
|
|
|
20
21
|
__all__ = [
|
|
21
|
-
"open", "File", "Image", "INDEX_NAMES", "PLATE_INDICES", "SAMPLED_INDICES", "ReadStats",
|
|
22
|
+
"open", "group_files", "FileGroup", "Grouping", "File", "Image", "INDEX_NAMES", "PLATE_INDICES", "SAMPLED_INDICES", "ReadStats",
|
|
22
23
|
"BACKENDS", "Metadata", "Channel", "Quantity", "Plate", "Well", "Field", "Acquisition",
|
|
23
24
|
"decode_threads", "set_decode_threads", "memory_limit", "set_memory_limit",
|
|
24
25
|
"cache_limit", "set_cache_limit", "cached_bytes",
|
|
25
26
|
"MicroReaderError", "UnsupportedFormatError", "CorruptFileError", "MemoryLimitError", "DamagedFileWarning",
|
|
26
|
-
"MissingImagesWarning", "CompanionWarning",
|
|
27
|
+
"MissingImagesWarning", "CompanionWarning", "CompositeWarning",
|
|
27
28
|
"__version__",
|
|
28
29
|
]
|
|
@@ -247,9 +247,17 @@ SAMPLED_INDICES = ("illumination", "phase", "block", "emission", "excitation", "
|
|
|
247
247
|
"sequence")
|
|
248
248
|
|
|
249
249
|
|
|
250
|
+
def _counted(result, bad: list) -> int:
|
|
251
|
+
"""One member checked (``File.check_members``): *result* is 1, or the
|
|
252
|
+
text saying why it does not fit (kept in *bad*)."""
|
|
253
|
+
if result != 1:
|
|
254
|
+
bad.append(result)
|
|
255
|
+
return 1
|
|
256
|
+
|
|
257
|
+
|
|
250
258
|
class File:
|
|
251
259
|
"""An opened file: its images, the backend that reads them (``backend``:
|
|
252
|
-
"tiff", "czi", "nd2", "lif", "oir", "ims", "mrc", "raster") and the
|
|
260
|
+
"tiff", "czi", "nd2", "lif", "oir", "oif", "vsi", "ims", "dv", "mrc", "raster") and the
|
|
253
261
|
format variant it is (``format``: e.g. "ome-tiff", "imagej", "lsm",
|
|
254
262
|
"nd2-legacy", "lof", "xlef", "png"). Every format opens as a File; a
|
|
255
263
|
backend's own class is an implementation detail."""
|
|
@@ -257,7 +265,10 @@ class File:
|
|
|
257
265
|
backend: str = "base"
|
|
258
266
|
|
|
259
267
|
def __init__(self, path: str, images: Sequence[Image], plates: Sequence = ()):
|
|
268
|
+
#: the file read: for a composite format its main file (docs/COMPOSITE.md)
|
|
260
269
|
self.path = str(path)
|
|
270
|
+
#: the path ``open()`` was given: a part of a composite, a folder, or ``path``
|
|
271
|
+
self.opened = self.path
|
|
261
272
|
self.images = list(images)
|
|
262
273
|
#: the plates the images belong to (``_plate.Plate``), like ``images``
|
|
263
274
|
#: a list; empty for a file that is not a plate (docs/HCS.md)
|
|
@@ -323,6 +334,46 @@ class File:
|
|
|
323
334
|
return [image for image in self.images
|
|
324
335
|
if all(image.index.get(name) == value for name, value in index.items())]
|
|
325
336
|
|
|
337
|
+
def _member_files(self) -> set:
|
|
338
|
+
"""The other files this file's images are read from -- a file set's
|
|
339
|
+
members -- for ``group_files``. Empty for a file that reads only itself."""
|
|
340
|
+
out = set()
|
|
341
|
+
for image in self.images:
|
|
342
|
+
image = getattr(image, "source", image)
|
|
343
|
+
for each in [image] + [p for p in image.parts if p is not image]:
|
|
344
|
+
paths = getattr(each, "_member_paths", None)
|
|
345
|
+
if paths is not None:
|
|
346
|
+
out.update(paths())
|
|
347
|
+
out.discard(self.path)
|
|
348
|
+
return out
|
|
349
|
+
|
|
350
|
+
def check_members(self) -> int:
|
|
351
|
+
"""For a file set (a plate, an OME companion set): open every member
|
|
352
|
+
file once and check that it is the plane its index describes (size,
|
|
353
|
+
type, one plane). Reading makes the same check plane by plane, at no
|
|
354
|
+
cost; this finds a file that does not fit before anything is read, at
|
|
355
|
+
the cost of opening every member. Returns the number of members
|
|
356
|
+
checked (0 for a file that is not a file set); raises
|
|
357
|
+
``CorruptFileError`` naming the members that do not fit."""
|
|
358
|
+
from . import _pool
|
|
359
|
+
from ._errors import CorruptFileError
|
|
360
|
+
seen, bad = set(), []
|
|
361
|
+
|
|
362
|
+
def tasks(): # lazy: a plate may have millions of members
|
|
363
|
+
for image in self.images:
|
|
364
|
+
image = getattr(image, "source", image) # open(axes=...) wraps it
|
|
365
|
+
for each in [image] + [p for p in image.parts if p is not image]:
|
|
366
|
+
check = getattr(each, "_check_members", None)
|
|
367
|
+
if check is not None:
|
|
368
|
+
for task in check(seen):
|
|
369
|
+
yield lambda task=task: _counted(task(), bad)
|
|
370
|
+
checked = _pool.run(tasks())
|
|
371
|
+
if bad:
|
|
372
|
+
shown = "; ".join(bad[:5]) + (f"; ... ({len(bad)} in all)" if len(bad) > 5 else "")
|
|
373
|
+
raise CorruptFileError(f"{self.path!r}: {len(bad)} of {checked} member files do "
|
|
374
|
+
f"not fit the index: {shown}")
|
|
375
|
+
return checked
|
|
376
|
+
|
|
326
377
|
def close(self) -> None:
|
|
327
378
|
for image in self.images:
|
|
328
379
|
image.close()
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""Compound File Binary (OLE2) containers: Olympus OIB, later Zeiss ZVI.
|
|
2
|
+
|
|
3
|
+
Written from Microsoft's published specification [MS-CFB] (Compound File
|
|
4
|
+
Binary File Format); oiffile's ``CompoundFile`` (Gohlke, BSD-3) was a
|
|
5
|
+
reference. A container is a little file system: fixed-size sectors, a FAT
|
|
6
|
+
chaining them, a directory of storages (folders) and streams (files); streams
|
|
7
|
+
smaller than the mini-stream cutoff live in 64-byte mini sectors inside the
|
|
8
|
+
root entry's stream.
|
|
9
|
+
|
|
10
|
+
Only what reading needs: the directory (stream paths ``Storage/name``), small
|
|
11
|
+
streams read whole, and for a large stream its *runs* -- (offset in the
|
|
12
|
+
stream, offset in the file, length) of each stretch of consecutive sectors --
|
|
13
|
+
so that any byte range of the stream is read with positioned reads, never the
|
|
14
|
+
whole stream.
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import bisect
|
|
19
|
+
import builtins
|
|
20
|
+
import struct
|
|
21
|
+
|
|
22
|
+
from ._errors import CorruptFileError
|
|
23
|
+
|
|
24
|
+
MAGIC = b"\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1"
|
|
25
|
+
FREESECT, ENDOFCHAIN, FATSECT, DIFSECT = 0xFFFFFFFF, 0xFFFFFFFE, 0xFFFFFFFD, 0xFFFFFFFC
|
|
26
|
+
MAXREGSECT = 0xFFFFFFFA
|
|
27
|
+
NOSTREAM = 0xFFFFFFFF
|
|
28
|
+
STORAGE, STREAM, ROOT = 1, 2, 5
|
|
29
|
+
#: directory entries are 128 bytes
|
|
30
|
+
DIR_ENTRY = struct.Struct("<64sHBBIII16sIQQIQ")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Stream:
|
|
34
|
+
"""A stream of the container: *size* bytes, read through *runs*
|
|
35
|
+
(stream offset, file offset, length), or held in memory (*data*:
|
|
36
|
+
streams in the mini stream, which are small)."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, path: str, size: int, runs=None, data: bytes = None, on_disk: str = None):
|
|
39
|
+
self.path, self.size, self.runs, self.data = path, size, runs, data
|
|
40
|
+
#: the compound file the stream is in
|
|
41
|
+
self.path_on_disk = on_disk
|
|
42
|
+
self._starts = [r[0] for r in runs] if runs else []
|
|
43
|
+
|
|
44
|
+
def read_all(self) -> bytes:
|
|
45
|
+
"""The whole stream (for small ones: settings, text)."""
|
|
46
|
+
if self.data is not None:
|
|
47
|
+
return self.data
|
|
48
|
+
out = bytearray()
|
|
49
|
+
with builtins.open(self.path_on_disk, "rb") as fh:
|
|
50
|
+
for at, n in self.pieces(0, self.size):
|
|
51
|
+
fh.seek(at)
|
|
52
|
+
out += fh.read(n)
|
|
53
|
+
return bytes(out)
|
|
54
|
+
|
|
55
|
+
def pieces(self, offset: int, size: int):
|
|
56
|
+
"""(file offset, length) pieces that hold bytes offset:offset+size."""
|
|
57
|
+
if offset < 0 or offset + size > self.size:
|
|
58
|
+
raise CorruptFileError(f"{self.path!r}: bytes {offset}:{offset + size} of a "
|
|
59
|
+
f"{self.size}-byte stream")
|
|
60
|
+
k = bisect.bisect_right(self._starts, offset) - 1
|
|
61
|
+
end = offset + size
|
|
62
|
+
while offset < end:
|
|
63
|
+
start, at, length = self.runs[k]
|
|
64
|
+
take = min(end, start + length) - offset
|
|
65
|
+
yield at + offset - start, take
|
|
66
|
+
offset += take
|
|
67
|
+
k += 1
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class CompoundFile:
|
|
71
|
+
"""The directory of compound file *path*: ``streams`` maps each stream's
|
|
72
|
+
path (``Storage00001/s_C001Z001.tif``, ``/`` between levels) to a
|
|
73
|
+
``Stream``."""
|
|
74
|
+
|
|
75
|
+
def __init__(self, path: str):
|
|
76
|
+
self.path = path
|
|
77
|
+
with builtins.open(path, "rb") as fh:
|
|
78
|
+
head = fh.read(512)
|
|
79
|
+
if head[:8] != MAGIC:
|
|
80
|
+
raise CorruptFileError(f"{path!r} is no compound (OLE) file")
|
|
81
|
+
(_minor, major, byte_order, sector_shift, mini_shift, n_fat, first_dir, self.cutoff,
|
|
82
|
+
first_minifat, n_minifat, first_difat, n_difat) = self._header(head)
|
|
83
|
+
#: version 3 files keep only the low 32 bits of a stream size meaningful
|
|
84
|
+
self._size_mask = 0xFFFFFFFF if major == 3 else (1 << 64) - 1
|
|
85
|
+
if byte_order != 0xFFFE:
|
|
86
|
+
raise CorruptFileError(f"{path!r}: compound file byte order {byte_order:#x}")
|
|
87
|
+
self.sector = 1 << sector_shift
|
|
88
|
+
self.mini = 1 << mini_shift
|
|
89
|
+
fh.seek(0, 2)
|
|
90
|
+
self._file_size = fh.tell()
|
|
91
|
+
difat = list(struct.unpack_from("<109I", head, 76))
|
|
92
|
+
sect = first_difat
|
|
93
|
+
for _ in range(n_difat):
|
|
94
|
+
if sect >= MAXREGSECT:
|
|
95
|
+
break
|
|
96
|
+
data = self._sector(fh, sect)
|
|
97
|
+
values = struct.unpack(f"<{self.sector // 4}I", data)
|
|
98
|
+
difat.extend(values[:-1])
|
|
99
|
+
sect = values[-1]
|
|
100
|
+
fat_sectors = [s for s in difat[:n_fat] if s < MAXREGSECT]
|
|
101
|
+
fat = []
|
|
102
|
+
for s in fat_sectors:
|
|
103
|
+
fat.extend(struct.unpack(f"<{self.sector // 4}I", self._sector(fh, s)))
|
|
104
|
+
self._fat = fat
|
|
105
|
+
entries = []
|
|
106
|
+
for chunk in self._chain_data(fh, first_dir):
|
|
107
|
+
for k in range(0, len(chunk), 128):
|
|
108
|
+
entries.append(DIR_ENTRY.unpack_from(chunk, k))
|
|
109
|
+
minifat = []
|
|
110
|
+
if n_minifat and first_minifat < MAXREGSECT:
|
|
111
|
+
for chunk in self._chain_data(fh, first_minifat):
|
|
112
|
+
minifat.extend(struct.unpack(f"<{len(chunk) // 4}I", chunk))
|
|
113
|
+
self._minifat = minifat
|
|
114
|
+
root = entries[0]
|
|
115
|
+
root_size = root[12] & self._size_mask
|
|
116
|
+
mini_stream = b"".join(self._chain_data(fh, root[11]))[:root_size] if root_size else b""
|
|
117
|
+
self.streams = {}
|
|
118
|
+
self._walk(fh, entries, entries[0][6], "", mini_stream, set())
|
|
119
|
+
|
|
120
|
+
@staticmethod
|
|
121
|
+
def _header(head: bytes):
|
|
122
|
+
minor, major, byte_order, sector_shift, mini_shift = struct.unpack_from("<HHHHH", head, 24)
|
|
123
|
+
n_fat, first_dir = struct.unpack_from("<II", head, 44)
|
|
124
|
+
cutoff, first_minifat, n_minifat, first_difat, n_difat = struct.unpack_from(
|
|
125
|
+
"<IIIII", head, 56)
|
|
126
|
+
return (minor, major, byte_order, sector_shift, mini_shift, n_fat, first_dir, cutoff,
|
|
127
|
+
first_minifat, n_minifat, first_difat, n_difat)
|
|
128
|
+
|
|
129
|
+
def _sector(self, fh, sect: int) -> bytes:
|
|
130
|
+
fh.seek((sect + 1) * self.sector)
|
|
131
|
+
data = fh.read(self.sector)
|
|
132
|
+
if len(data) != self.sector:
|
|
133
|
+
raise CorruptFileError(f"{self.path!r}: sector {sect} beyond the end of the file")
|
|
134
|
+
return data
|
|
135
|
+
|
|
136
|
+
def _chain(self, start: int) -> list:
|
|
137
|
+
chain, sect, seen = [], start, set()
|
|
138
|
+
while sect < MAXREGSECT:
|
|
139
|
+
if sect in seen or sect >= len(self._fat):
|
|
140
|
+
raise CorruptFileError(f"{self.path!r}: broken sector chain at {sect}")
|
|
141
|
+
seen.add(sect)
|
|
142
|
+
chain.append(sect)
|
|
143
|
+
sect = self._fat[sect]
|
|
144
|
+
return chain
|
|
145
|
+
|
|
146
|
+
def _chain_data(self, fh, start: int):
|
|
147
|
+
for sect in self._chain(start):
|
|
148
|
+
yield self._sector(fh, sect)
|
|
149
|
+
|
|
150
|
+
def _runs(self, start: int, size: int) -> list:
|
|
151
|
+
"""(stream offset, file offset, length) of each stretch of consecutive sectors."""
|
|
152
|
+
runs, offset = [], 0
|
|
153
|
+
for sect in self._chain(start):
|
|
154
|
+
at = (sect + 1) * self.sector
|
|
155
|
+
length = min(self.sector, size - offset)
|
|
156
|
+
if length <= 0:
|
|
157
|
+
break
|
|
158
|
+
if runs and runs[-1][1] + runs[-1][2] == at:
|
|
159
|
+
s, a, n = runs[-1]
|
|
160
|
+
runs[-1] = (s, a, n + length)
|
|
161
|
+
else:
|
|
162
|
+
runs.append((offset, at, length))
|
|
163
|
+
offset += length
|
|
164
|
+
if offset < size:
|
|
165
|
+
raise CorruptFileError(f"{self.path!r}: a stream's sectors end before its size")
|
|
166
|
+
return runs
|
|
167
|
+
|
|
168
|
+
def _mini_data(self, mini_stream: bytes, start: int, size: int) -> bytes:
|
|
169
|
+
out, sect, seen = bytearray(), start, set()
|
|
170
|
+
while sect < MAXREGSECT and len(out) < size:
|
|
171
|
+
if sect in seen or sect >= len(self._minifat):
|
|
172
|
+
raise CorruptFileError(f"{self.path!r}: broken mini sector chain at {sect}")
|
|
173
|
+
seen.add(sect)
|
|
174
|
+
out += mini_stream[sect * self.mini:(sect + 1) * self.mini]
|
|
175
|
+
sect = self._minifat[sect]
|
|
176
|
+
return bytes(out[:size])
|
|
177
|
+
|
|
178
|
+
def _walk(self, fh, entries, sid: int, prefix: str, mini_stream: bytes, seen: set):
|
|
179
|
+
"""Every entry of the red-black tree at *sid* (siblings left / right,
|
|
180
|
+
a storage's children below it)."""
|
|
181
|
+
stack = [sid]
|
|
182
|
+
while stack:
|
|
183
|
+
sid = stack.pop()
|
|
184
|
+
if sid == NOSTREAM or sid in seen or sid >= len(entries):
|
|
185
|
+
continue
|
|
186
|
+
seen.add(sid)
|
|
187
|
+
(raw, name_len, kind, _color, left, right, child, _clsid, _state, _times, _times2,
|
|
188
|
+
start, size) = entries[sid]
|
|
189
|
+
size &= self._size_mask
|
|
190
|
+
stack += [left, right]
|
|
191
|
+
name = raw[:max(0, name_len - 2)].decode("utf-16-le", "replace")
|
|
192
|
+
path = prefix + name
|
|
193
|
+
if kind == STORAGE:
|
|
194
|
+
self._walk(fh, entries, child, path + "/", mini_stream, seen)
|
|
195
|
+
elif kind == STREAM:
|
|
196
|
+
if size < self.cutoff:
|
|
197
|
+
self.streams[path] = Stream(path, size, data=self._mini_data(mini_stream, start, size))
|
|
198
|
+
else:
|
|
199
|
+
self.streams[path] = Stream(path, size, runs=self._runs(start, size),
|
|
200
|
+
on_disk=self.path)
|
|
@@ -19,6 +19,12 @@ class MissingImagesWarning(UserWarning):
|
|
|
19
19
|
(docs/HCS.md)."""
|
|
20
20
|
|
|
21
21
|
|
|
22
|
+
class CompositeWarning(UserWarning):
|
|
23
|
+
"""A part of a composite format was read on its own because its main file
|
|
24
|
+
is missing, or a part could not be told apart from another file
|
|
25
|
+
(docs/COMPOSITE.md)."""
|
|
26
|
+
|
|
27
|
+
|
|
22
28
|
class CompanionWarning(UserWarning):
|
|
23
29
|
"""An OME companion set's metadata came from somewhere other than the file
|
|
24
30
|
opened, or a member path had to be resolved differently than written
|