iivs-lib 0.1.0__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.
- iivs_lib-0.1.0/LICENSE +21 -0
- iivs_lib-0.1.0/PKG-INFO +182 -0
- iivs_lib-0.1.0/README.md +154 -0
- iivs_lib-0.1.0/iivs/__init__.py +0 -0
- iivs_lib-0.1.0/iivs/dhm/__init__.py +0 -0
- iivs_lib-0.1.0/iivs/dhm/analysis/__init__.py +24 -0
- iivs_lib-0.1.0/iivs/dhm/analysis/drymass.py +233 -0
- iivs_lib-0.1.0/iivs/dhm/analysis/opd.py +111 -0
- iivs_lib-0.1.0/iivs/dhm/analysis/pytorch/__init__.py +37 -0
- iivs_lib-0.1.0/iivs/dhm/analysis/pytorch/drymass.py +212 -0
- iivs_lib-0.1.0/iivs/dhm/analysis/pytorch/opd.py +90 -0
- iivs_lib-0.1.0/iivs/dhm/data/__init__.py +19 -0
- iivs_lib-0.1.0/iivs/dhm/data/common/__init__.py +65 -0
- iivs_lib-0.1.0/iivs/dhm/data/common/bin.py +244 -0
- iivs_lib-0.1.0/iivs/dhm/data/common/float.py +160 -0
- iivs_lib-0.1.0/iivs/dhm/data/common/image.py +177 -0
- iivs_lib-0.1.0/iivs/dhm/data/common/npy.py +48 -0
- iivs_lib-0.1.0/iivs/dhm/data/common/sequence.py +122 -0
- iivs_lib-0.1.0/iivs/dhm/data/common/txt.py +168 -0
- iivs_lib-0.1.0/iivs/dhm/data/common/utils.py +13 -0
- iivs_lib-0.1.0/iivs/dhm/data/common/validation.py +114 -0
- iivs_lib-0.1.0/iivs/dhm/data/constants.py +40 -0
- iivs_lib-0.1.0/iivs/dhm/data/hologram/__init__.py +39 -0
- iivs_lib-0.1.0/iivs/dhm/data/hologram/base.py +21 -0
- iivs_lib-0.1.0/iivs/dhm/data/hologram/convert.py +65 -0
- iivs_lib-0.1.0/iivs/dhm/data/hologram/npy.py +68 -0
- iivs_lib-0.1.0/iivs/dhm/data/hologram/raw.py +306 -0
- iivs_lib-0.1.0/iivs/dhm/data/hologram/tif.py +105 -0
- iivs_lib-0.1.0/iivs/dhm/data/intensity/__init__.py +58 -0
- iivs_lib-0.1.0/iivs/dhm/data/intensity/base.py +82 -0
- iivs_lib-0.1.0/iivs/dhm/data/intensity/bin.py +274 -0
- iivs_lib-0.1.0/iivs/dhm/data/intensity/convert.py +107 -0
- iivs_lib-0.1.0/iivs/dhm/data/intensity/npy.py +114 -0
- iivs_lib-0.1.0/iivs/dhm/data/intensity/tif.py +44 -0
- iivs_lib-0.1.0/iivs/dhm/data/intensity/txt.py +218 -0
- iivs_lib-0.1.0/iivs/dhm/data/phase/__init__.py +62 -0
- iivs_lib-0.1.0/iivs/dhm/data/phase/base.py +342 -0
- iivs_lib-0.1.0/iivs/dhm/data/phase/bin.py +370 -0
- iivs_lib-0.1.0/iivs/dhm/data/phase/bounds.py +162 -0
- iivs_lib-0.1.0/iivs/dhm/data/phase/convert.py +116 -0
- iivs_lib-0.1.0/iivs/dhm/data/phase/npy.py +137 -0
- iivs_lib-0.1.0/iivs/dhm/data/phase/tif.py +42 -0
- iivs_lib-0.1.0/iivs/dhm/data/phase/txt.py +319 -0
- iivs_lib-0.1.0/iivs/dhm/data/phase/unit.py +103 -0
- iivs_lib-0.1.0/iivs/dhm/data/timestamp.py +257 -0
- iivs_lib-0.1.0/iivs/py.typed +0 -0
- iivs_lib-0.1.0/pyproject.toml +325 -0
iivs_lib-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jaewoo Park
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
iivs_lib-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: iivs-lib
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python toolkit for multi-modal holographic systems and cellular analysis.
|
|
5
|
+
Keywords:
|
|
6
|
+
Author: Jaewoo Park
|
|
7
|
+
Author-email: Jaewoo Park <kaparoo2001@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
16
|
+
Classifier: Typing :: Typed
|
|
17
|
+
Requires-Dist: kaparoo-python>=0.7.0
|
|
18
|
+
Requires-Dist: natsort>=8.4.0
|
|
19
|
+
Requires-Dist: numpy>=2.4.6
|
|
20
|
+
Requires-Dist: tifffile>=2026.6.1
|
|
21
|
+
Requires-Dist: imagecodecs>=2026.5.10 ; extra == 'image'
|
|
22
|
+
Requires-Dist: torch>=2.6 ; extra == 'torch'
|
|
23
|
+
Requires-Python: >=3.14
|
|
24
|
+
Project-URL: GitHub, https://www.github.com/iivs-lab/iivs-lib
|
|
25
|
+
Provides-Extra: image
|
|
26
|
+
Provides-Extra: torch
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# iivs-lib
|
|
30
|
+
|
|
31
|
+
[](https://pypi.org/project/iivs-lib/)
|
|
32
|
+
[](https://pypi.org/project/iivs-lib/)
|
|
33
|
+
[](https://www.python.org/)
|
|
34
|
+
[](./LICENSE)
|
|
35
|
+
[](https://github.com/astral-sh/uv)
|
|
36
|
+
[](https://github.com/astral-sh/ruff)
|
|
37
|
+
[](https://github.com/astral-sh/ty)
|
|
38
|
+
[](https://github.com/copier-org/copier)
|
|
39
|
+
|
|
40
|
+
*A Python toolkit for multi-modal holographic systems and cellular analysis.*
|
|
41
|
+
|
|
42
|
+
## 📦 Installation
|
|
43
|
+
|
|
44
|
+
Requires Python 3.14+.
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# With uv (recommended)
|
|
48
|
+
uv add iivs-lib
|
|
49
|
+
|
|
50
|
+
# With pip
|
|
51
|
+
pip install iivs-lib
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The `[image]` extra adds `imagecodecs`, needed only to decode the
|
|
55
|
+
LZW-compressed Koala `Image/*.tif` uint8 previews (`uv add "iivs-lib[image]"`).
|
|
56
|
+
The `[torch]` extra adds PyTorch for `iivs.dhm.analysis.pytorch` — tensor-in /
|
|
57
|
+
tensor-out OPD / dry-mass twins with autograd (`uv add "iivs-lib[torch]"`).
|
|
58
|
+
|
|
59
|
+
## 🚀 Quick start
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from iivs.dhm.data.phase import PhaseBinFolder, PhaseUnit
|
|
63
|
+
from iivs.dhm.analysis import DryMassCalculator
|
|
64
|
+
|
|
65
|
+
# Lazily open a phase acquisition (a folder of numbered .bin frames).
|
|
66
|
+
phase = PhaseBinFolder("scan/Phase/Float/Bin", target_unit=PhaseUnit.RADIANS)
|
|
67
|
+
phase.frame_shape # (H, W), shared across frames
|
|
68
|
+
img = phase[0] # first frame as a float32 array (decoded on access)
|
|
69
|
+
|
|
70
|
+
# Per-frame dry mass over a segmented cell:
|
|
71
|
+
calc = DryMassCalculator(pixel_size=phase.header.pixel_size)
|
|
72
|
+
mass_pg = calc.calc_from_phase(img, mask=cell_mask)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## 🧩 Modules
|
|
76
|
+
|
|
77
|
+
Each package ships a detailed README in the source tree — the endpoints,
|
|
78
|
+
examples, and the inherited sequence interface:
|
|
79
|
+
[`hologram`](https://github.com/iivs-lab/iivs-lib/tree/main/iivs/dhm/data/hologram),
|
|
80
|
+
[`phase`](https://github.com/iivs-lab/iivs-lib/tree/main/iivs/dhm/data/phase),
|
|
81
|
+
[`intensity`](https://github.com/iivs-lab/iivs-lib/tree/main/iivs/dhm/data/intensity),
|
|
82
|
+
[`data`](https://github.com/iivs-lab/iivs-lib/tree/main/iivs/dhm/data) (overview
|
|
83
|
+
and `timestamp`),
|
|
84
|
+
[`analysis`](https://github.com/iivs-lab/iivs-lib/tree/main/iivs/dhm/analysis).
|
|
85
|
+
|
|
86
|
+
### `iivs.dhm.data`
|
|
87
|
+
|
|
88
|
+
Readers, writers, and lazy sequences for Lyncée Tec Koala acquisition data
|
|
89
|
+
(validated end-to-end against a real Koala acquisition — every format, the
|
|
90
|
+
sequences, and the round trips between them).
|
|
91
|
+
|
|
92
|
+
- **`hologram`** — uint8 holograms: `.tif` via `load_hologram_tif` /
|
|
93
|
+
`save_hologram_tif` with `HologramTifFolder` / `HologramTifList`; a single
|
|
94
|
+
multi-frame `.raw` via `HologramRawFile` (a lazy `np.memmap`) and
|
|
95
|
+
`read_hologram_raw_header`; header-less `.npy` frames via `HologramNpyFolder`.
|
|
96
|
+
- **`phase`** — float32 `.bin` phase images: `load_phase_bin` /
|
|
97
|
+
`save_phase_bin` / `read_phase_bin_header`, the typed `PhaseBinHeader` and
|
|
98
|
+
`PhaseUnit`, and `convert_phase_unit`; folder/list sequences
|
|
99
|
+
`PhaseBinFolder` / `PhaseBinList`. The same quantitative phase from Koala's
|
|
100
|
+
`Float/Txt` export via `load_phase_txt`, `PhaseTxtFolder` / `PhaseTxtList`.
|
|
101
|
+
The uint8 `Image/*.tif` display previews (not quantitative) via
|
|
102
|
+
`PhaseTifFolder` / `PhaseTifList`. Header-less `.npy` frames via
|
|
103
|
+
`PhaseNpyFolder` (`pixel_size` / `unit` / `height_scale` passed to the
|
|
104
|
+
constructor; `numpy.load`, pickle disabled).
|
|
105
|
+
- **`intensity`** — float32 `.bin` intensity reconstructions (exported
|
|
106
|
+
alongside phase): `load_intensity_bin` / `save_intensity_bin` /
|
|
107
|
+
`read_intensity_bin_header`, the typed `IntensityBinHeader`, and folder/list
|
|
108
|
+
sequences `IntensityBinFolder` / `IntensityBinList`; plus the `Float/Txt`
|
|
109
|
+
twins `load_intensity_txt`, `IntensityTxtFolder` / `IntensityTxtList`, the
|
|
110
|
+
uint8 `Image/*.tif` previews `IntensityTifFolder` / `IntensityTifList`, and
|
|
111
|
+
header-less `.npy` frames via `IntensityNpyFolder` (`pixel_size` passed in).
|
|
112
|
+
The phase and intensity `.bin` formats share the `common.KoalaBinHeader` base.
|
|
113
|
+
- **`timestamp`** — per-frame acquisition timing: the `Timestamp` record,
|
|
114
|
+
`TimestampsTxtFile` (Koala `timestamps.txt`), and `TimestampsFixedFPS`
|
|
115
|
+
(synthesized from a frame rate).
|
|
116
|
+
|
|
117
|
+
Every sequence is a `kaparoo.data.sequences.DataSequence`, so it indexes,
|
|
118
|
+
slices, and iterates lazily; same-shape sources also expose `frame_shape` by
|
|
119
|
+
mixing in `common.FrameShapedMixin` (so a uniform source is its
|
|
120
|
+
`<Modality>FloatSequence` / `<Modality>ImageSequence` plus that mixin). For
|
|
121
|
+
phase and intensity the quantitative float32 sources are
|
|
122
|
+
`<Modality>FloatSequence` and the uint8 `Image/*.tif` previews are
|
|
123
|
+
`<Modality>ImageSequence`, both under the `<Modality>Sequence` base.
|
|
124
|
+
Numbered-folder sequences share the `common.SequentialFileFolder`
|
|
125
|
+
discovery/validation base, and validate their arrays via
|
|
126
|
+
`common.validate_float32_image` / `validate_uint8_image`. These cross-modality
|
|
127
|
+
building blocks live in `iivs.dhm.data.common`.
|
|
128
|
+
|
|
129
|
+
### `iivs.dhm.analysis`
|
|
130
|
+
|
|
131
|
+
Physical quantities derived from phase, each via an engine object that
|
|
132
|
+
precomputes its conversion factor (with one-shot function conveniences):
|
|
133
|
+
|
|
134
|
+
- **`opd`** — optical path difference (`OPD = phase * wavelength / (2*pi)`, in
|
|
135
|
+
nm). `OPDConverter` (`convert_to_opd` / `convert_to_phase`, scale
|
|
136
|
+
`opd_scale`); `phase_to_opd` / `opd_to_phase`.
|
|
137
|
+
- **`drymass`** — dry mass (pg) via the Barer relation. `DryMassCalculator`
|
|
138
|
+
(`calc_from_opd` / `calc_from_phase` over a background-corrected, optionally
|
|
139
|
+
masked map; scale `drymass_scale`); `calc_drymass` / `calc_drymass_from_phase`.
|
|
140
|
+
|
|
141
|
+
#### Using with PyTorch (autograd)
|
|
142
|
+
|
|
143
|
+
The `convert_*` / `calc_*` methods operate on NumPy arrays. Install the
|
|
144
|
+
`iivs-lib[torch]` extra for `iivs.dhm.analysis.pytorch` — tensor-in / tensor-out
|
|
145
|
+
twins that keep the input tensor's device, dtype, and autograd graph (the
|
|
146
|
+
calibration scalars are shared with the NumPy engines):
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
from iivs.dhm.analysis.pytorch.opd import phase_to_opd
|
|
150
|
+
from iivs.dhm.analysis.pytorch.drymass import calc_drymass_from_phase
|
|
151
|
+
|
|
152
|
+
opd = phase_to_opd(phase, wavelength=666e-9) # Tensor (CPU/GPU), grad kept
|
|
153
|
+
mass = calc_drymass_from_phase(phase, pixel_size=px, mask=cell) # 0-dim Tensor, grad kept
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Or, without the dependency, multiply by the cached scale factors (plain floats)
|
|
157
|
+
with native ops yourself:
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
opd = phase * conv.opd_scale # phase: Tensor -> OPD (nm), grad kept
|
|
161
|
+
mass = opd[mask].sum() * calc.drymass_scale # OPD -> dry mass (pg), grad kept
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## 📋 TODO
|
|
165
|
+
|
|
166
|
+
See [TODO.md](./TODO.md) for tracked open items.
|
|
167
|
+
|
|
168
|
+
## 📜 Changelog
|
|
169
|
+
|
|
170
|
+
See [CHANGELOG.md](./CHANGELOG.md) for the version history.
|
|
171
|
+
|
|
172
|
+
## 🙏 Acknowledgements
|
|
173
|
+
|
|
174
|
+
The Koala file formats read and written by `iivs.dhm.data` originate from
|
|
175
|
+
[Lyncée Tec](https://www.lynceetec.com/)'s Koala software. The phase `.bin`
|
|
176
|
+
format was cross-checked against their reference implementation,
|
|
177
|
+
[`pyKoalaUtils`](https://github.com/lynceetec/pyKoalaUtils) (MIT). iivs-lib is
|
|
178
|
+
an independent reimplementation and contains no code from it.
|
|
179
|
+
|
|
180
|
+
## ⚖️ License
|
|
181
|
+
|
|
182
|
+
This project is distributed under the terms of the [MIT](./LICENSE) license.
|
iivs_lib-0.1.0/README.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# iivs-lib
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/iivs-lib/)
|
|
4
|
+
[](https://pypi.org/project/iivs-lib/)
|
|
5
|
+
[](https://www.python.org/)
|
|
6
|
+
[](./LICENSE)
|
|
7
|
+
[](https://github.com/astral-sh/uv)
|
|
8
|
+
[](https://github.com/astral-sh/ruff)
|
|
9
|
+
[](https://github.com/astral-sh/ty)
|
|
10
|
+
[](https://github.com/copier-org/copier)
|
|
11
|
+
|
|
12
|
+
*A Python toolkit for multi-modal holographic systems and cellular analysis.*
|
|
13
|
+
|
|
14
|
+
## 📦 Installation
|
|
15
|
+
|
|
16
|
+
Requires Python 3.14+.
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# With uv (recommended)
|
|
20
|
+
uv add iivs-lib
|
|
21
|
+
|
|
22
|
+
# With pip
|
|
23
|
+
pip install iivs-lib
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The `[image]` extra adds `imagecodecs`, needed only to decode the
|
|
27
|
+
LZW-compressed Koala `Image/*.tif` uint8 previews (`uv add "iivs-lib[image]"`).
|
|
28
|
+
The `[torch]` extra adds PyTorch for `iivs.dhm.analysis.pytorch` — tensor-in /
|
|
29
|
+
tensor-out OPD / dry-mass twins with autograd (`uv add "iivs-lib[torch]"`).
|
|
30
|
+
|
|
31
|
+
## 🚀 Quick start
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from iivs.dhm.data.phase import PhaseBinFolder, PhaseUnit
|
|
35
|
+
from iivs.dhm.analysis import DryMassCalculator
|
|
36
|
+
|
|
37
|
+
# Lazily open a phase acquisition (a folder of numbered .bin frames).
|
|
38
|
+
phase = PhaseBinFolder("scan/Phase/Float/Bin", target_unit=PhaseUnit.RADIANS)
|
|
39
|
+
phase.frame_shape # (H, W), shared across frames
|
|
40
|
+
img = phase[0] # first frame as a float32 array (decoded on access)
|
|
41
|
+
|
|
42
|
+
# Per-frame dry mass over a segmented cell:
|
|
43
|
+
calc = DryMassCalculator(pixel_size=phase.header.pixel_size)
|
|
44
|
+
mass_pg = calc.calc_from_phase(img, mask=cell_mask)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 🧩 Modules
|
|
48
|
+
|
|
49
|
+
Each package ships a detailed README in the source tree — the endpoints,
|
|
50
|
+
examples, and the inherited sequence interface:
|
|
51
|
+
[`hologram`](https://github.com/iivs-lab/iivs-lib/tree/main/iivs/dhm/data/hologram),
|
|
52
|
+
[`phase`](https://github.com/iivs-lab/iivs-lib/tree/main/iivs/dhm/data/phase),
|
|
53
|
+
[`intensity`](https://github.com/iivs-lab/iivs-lib/tree/main/iivs/dhm/data/intensity),
|
|
54
|
+
[`data`](https://github.com/iivs-lab/iivs-lib/tree/main/iivs/dhm/data) (overview
|
|
55
|
+
and `timestamp`),
|
|
56
|
+
[`analysis`](https://github.com/iivs-lab/iivs-lib/tree/main/iivs/dhm/analysis).
|
|
57
|
+
|
|
58
|
+
### `iivs.dhm.data`
|
|
59
|
+
|
|
60
|
+
Readers, writers, and lazy sequences for Lyncée Tec Koala acquisition data
|
|
61
|
+
(validated end-to-end against a real Koala acquisition — every format, the
|
|
62
|
+
sequences, and the round trips between them).
|
|
63
|
+
|
|
64
|
+
- **`hologram`** — uint8 holograms: `.tif` via `load_hologram_tif` /
|
|
65
|
+
`save_hologram_tif` with `HologramTifFolder` / `HologramTifList`; a single
|
|
66
|
+
multi-frame `.raw` via `HologramRawFile` (a lazy `np.memmap`) and
|
|
67
|
+
`read_hologram_raw_header`; header-less `.npy` frames via `HologramNpyFolder`.
|
|
68
|
+
- **`phase`** — float32 `.bin` phase images: `load_phase_bin` /
|
|
69
|
+
`save_phase_bin` / `read_phase_bin_header`, the typed `PhaseBinHeader` and
|
|
70
|
+
`PhaseUnit`, and `convert_phase_unit`; folder/list sequences
|
|
71
|
+
`PhaseBinFolder` / `PhaseBinList`. The same quantitative phase from Koala's
|
|
72
|
+
`Float/Txt` export via `load_phase_txt`, `PhaseTxtFolder` / `PhaseTxtList`.
|
|
73
|
+
The uint8 `Image/*.tif` display previews (not quantitative) via
|
|
74
|
+
`PhaseTifFolder` / `PhaseTifList`. Header-less `.npy` frames via
|
|
75
|
+
`PhaseNpyFolder` (`pixel_size` / `unit` / `height_scale` passed to the
|
|
76
|
+
constructor; `numpy.load`, pickle disabled).
|
|
77
|
+
- **`intensity`** — float32 `.bin` intensity reconstructions (exported
|
|
78
|
+
alongside phase): `load_intensity_bin` / `save_intensity_bin` /
|
|
79
|
+
`read_intensity_bin_header`, the typed `IntensityBinHeader`, and folder/list
|
|
80
|
+
sequences `IntensityBinFolder` / `IntensityBinList`; plus the `Float/Txt`
|
|
81
|
+
twins `load_intensity_txt`, `IntensityTxtFolder` / `IntensityTxtList`, the
|
|
82
|
+
uint8 `Image/*.tif` previews `IntensityTifFolder` / `IntensityTifList`, and
|
|
83
|
+
header-less `.npy` frames via `IntensityNpyFolder` (`pixel_size` passed in).
|
|
84
|
+
The phase and intensity `.bin` formats share the `common.KoalaBinHeader` base.
|
|
85
|
+
- **`timestamp`** — per-frame acquisition timing: the `Timestamp` record,
|
|
86
|
+
`TimestampsTxtFile` (Koala `timestamps.txt`), and `TimestampsFixedFPS`
|
|
87
|
+
(synthesized from a frame rate).
|
|
88
|
+
|
|
89
|
+
Every sequence is a `kaparoo.data.sequences.DataSequence`, so it indexes,
|
|
90
|
+
slices, and iterates lazily; same-shape sources also expose `frame_shape` by
|
|
91
|
+
mixing in `common.FrameShapedMixin` (so a uniform source is its
|
|
92
|
+
`<Modality>FloatSequence` / `<Modality>ImageSequence` plus that mixin). For
|
|
93
|
+
phase and intensity the quantitative float32 sources are
|
|
94
|
+
`<Modality>FloatSequence` and the uint8 `Image/*.tif` previews are
|
|
95
|
+
`<Modality>ImageSequence`, both under the `<Modality>Sequence` base.
|
|
96
|
+
Numbered-folder sequences share the `common.SequentialFileFolder`
|
|
97
|
+
discovery/validation base, and validate their arrays via
|
|
98
|
+
`common.validate_float32_image` / `validate_uint8_image`. These cross-modality
|
|
99
|
+
building blocks live in `iivs.dhm.data.common`.
|
|
100
|
+
|
|
101
|
+
### `iivs.dhm.analysis`
|
|
102
|
+
|
|
103
|
+
Physical quantities derived from phase, each via an engine object that
|
|
104
|
+
precomputes its conversion factor (with one-shot function conveniences):
|
|
105
|
+
|
|
106
|
+
- **`opd`** — optical path difference (`OPD = phase * wavelength / (2*pi)`, in
|
|
107
|
+
nm). `OPDConverter` (`convert_to_opd` / `convert_to_phase`, scale
|
|
108
|
+
`opd_scale`); `phase_to_opd` / `opd_to_phase`.
|
|
109
|
+
- **`drymass`** — dry mass (pg) via the Barer relation. `DryMassCalculator`
|
|
110
|
+
(`calc_from_opd` / `calc_from_phase` over a background-corrected, optionally
|
|
111
|
+
masked map; scale `drymass_scale`); `calc_drymass` / `calc_drymass_from_phase`.
|
|
112
|
+
|
|
113
|
+
#### Using with PyTorch (autograd)
|
|
114
|
+
|
|
115
|
+
The `convert_*` / `calc_*` methods operate on NumPy arrays. Install the
|
|
116
|
+
`iivs-lib[torch]` extra for `iivs.dhm.analysis.pytorch` — tensor-in / tensor-out
|
|
117
|
+
twins that keep the input tensor's device, dtype, and autograd graph (the
|
|
118
|
+
calibration scalars are shared with the NumPy engines):
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from iivs.dhm.analysis.pytorch.opd import phase_to_opd
|
|
122
|
+
from iivs.dhm.analysis.pytorch.drymass import calc_drymass_from_phase
|
|
123
|
+
|
|
124
|
+
opd = phase_to_opd(phase, wavelength=666e-9) # Tensor (CPU/GPU), grad kept
|
|
125
|
+
mass = calc_drymass_from_phase(phase, pixel_size=px, mask=cell) # 0-dim Tensor, grad kept
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Or, without the dependency, multiply by the cached scale factors (plain floats)
|
|
129
|
+
with native ops yourself:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
opd = phase * conv.opd_scale # phase: Tensor -> OPD (nm), grad kept
|
|
133
|
+
mass = opd[mask].sum() * calc.drymass_scale # OPD -> dry mass (pg), grad kept
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## 📋 TODO
|
|
137
|
+
|
|
138
|
+
See [TODO.md](./TODO.md) for tracked open items.
|
|
139
|
+
|
|
140
|
+
## 📜 Changelog
|
|
141
|
+
|
|
142
|
+
See [CHANGELOG.md](./CHANGELOG.md) for the version history.
|
|
143
|
+
|
|
144
|
+
## 🙏 Acknowledgements
|
|
145
|
+
|
|
146
|
+
The Koala file formats read and written by `iivs.dhm.data` originate from
|
|
147
|
+
[Lyncée Tec](https://www.lynceetec.com/)'s Koala software. The phase `.bin`
|
|
148
|
+
format was cross-checked against their reference implementation,
|
|
149
|
+
[`pyKoalaUtils`](https://github.com/lynceetec/pyKoalaUtils) (MIT). iivs-lib is
|
|
150
|
+
an independent reimplementation and contains no code from it.
|
|
151
|
+
|
|
152
|
+
## ⚖️ License
|
|
153
|
+
|
|
154
|
+
This project is distributed under the terms of the [MIT](./LICENSE) license.
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Quantitative analysis derived from DHM phase data (OPD, dry mass, ...).
|
|
2
|
+
|
|
3
|
+
The NumPy engines and one-shot helpers are re-exported here; the Torch twins
|
|
4
|
+
live in `iivs.dhm.analysis.pytorch` (install the ``iivs-lib[torch]`` extra) and
|
|
5
|
+
are *not* re-exported, so importing this package never requires PyTorch.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
__all__ = (
|
|
11
|
+
"DryMassCalculator",
|
|
12
|
+
"OPDConverter",
|
|
13
|
+
"calc_drymass",
|
|
14
|
+
"calc_drymass_from_phase",
|
|
15
|
+
"opd_to_phase",
|
|
16
|
+
"phase_to_opd",
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
from iivs.dhm.analysis.drymass import (
|
|
20
|
+
DryMassCalculator,
|
|
21
|
+
calc_drymass,
|
|
22
|
+
calc_drymass_from_phase,
|
|
23
|
+
)
|
|
24
|
+
from iivs.dhm.analysis.opd import OPDConverter, opd_to_phase, phase_to_opd
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
__all__ = ("DryMassCalculator", "calc_drymass", "calc_drymass_from_phase")
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from iivs.dhm.analysis.opd import OPDConverter
|
|
11
|
+
from iivs.dhm.data.constants import (
|
|
12
|
+
DEFAULT_SPECIFIC_REFRACTIVE_INCREMENT,
|
|
13
|
+
DEFAULT_WAVELENGTH,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from typing import Self
|
|
18
|
+
|
|
19
|
+
from numpy.typing import NDArray
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True, slots=True)
|
|
23
|
+
class DryMassCalculator:
|
|
24
|
+
"""Integrate OPD (or phase) into dry mass (pg) at a fixed pixel size and alpha.
|
|
25
|
+
|
|
26
|
+
Bind the pixel size, specific refractive increment, and -- for the phase
|
|
27
|
+
path -- an `OPDConverter` once; the per-pixel mass factor is precomputed::
|
|
28
|
+
|
|
29
|
+
dmc = DryMassCalculator(pixel_size=px) # alpha, wavelength default
|
|
30
|
+
dmc = DryMassCalculator.from_wavelength(pixel_size=px, wavelength=666e-9)
|
|
31
|
+
mass = dmc.calc_from_opd(opd, mask=cell) # opd in nm
|
|
32
|
+
mass = dmc.calc_from_phase(phase, mask=cell) # phase in rad
|
|
33
|
+
|
|
34
|
+
Dry mass is ``(1 / alpha) * sum(OPD * pixel_area)`` (Barer), in pg, summed in
|
|
35
|
+
float64 over the last two axes (H, W) and returned as float32. Inputs are
|
|
36
|
+
batched (``(..., H, W)``)
|
|
37
|
+
and a ``(N, H, W)`` mask adds a trailing channel axis -- see `calc_from_opd`
|
|
38
|
+
for the shape / `reduce` details. The OPD must already be background-corrected
|
|
39
|
+
(≈ 0 outside the object); segmentation and background estimation stay the
|
|
40
|
+
caller's responsibility.
|
|
41
|
+
|
|
42
|
+
The free `calc_drymass` / `calc_drymass_from_phase` are one-shot conveniences
|
|
43
|
+
over this class. For PyTorch, use `iivs.dhm.analysis.pytorch.DryMass`.
|
|
44
|
+
|
|
45
|
+
Attributes:
|
|
46
|
+
pixel_size: Physical size of one (square) pixel, in m.
|
|
47
|
+
alpha: Specific refractive increment, in m^3/kg.
|
|
48
|
+
opd_converter: Phase-to-OPD converter used by `calc_from_phase`.
|
|
49
|
+
Defaults to one at the default wavelength; inject your own or use
|
|
50
|
+
`from_wavelength`.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
pixel_size: float
|
|
54
|
+
alpha: float = DEFAULT_SPECIFIC_REFRACTIVE_INCREMENT
|
|
55
|
+
opd_converter: OPDConverter = field(default_factory=OPDConverter)
|
|
56
|
+
|
|
57
|
+
# pg of dry mass per nm of OPD summed over pixels:
|
|
58
|
+
_scale: float = field(init=False, repr=False, compare=False)
|
|
59
|
+
|
|
60
|
+
def __post_init__(self) -> None:
|
|
61
|
+
"""Validate inputs and precompute the per-pixel mass factor."""
|
|
62
|
+
if self.pixel_size <= 0:
|
|
63
|
+
msg = f"pixel_size must be positive (got {self.pixel_size})"
|
|
64
|
+
raise ValueError(msg)
|
|
65
|
+
|
|
66
|
+
if self.alpha <= 0:
|
|
67
|
+
msg = f"alpha must be positive (got {self.alpha})"
|
|
68
|
+
raise ValueError(msg)
|
|
69
|
+
|
|
70
|
+
# pg per summed-nm OPD: px_area(m^2) * (nm->m 1e-9) * (kg->pg 1e15) / alpha.
|
|
71
|
+
object.__setattr__(self, "_scale", self.pixel_size**2 * 1e6 / self.alpha)
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def from_wavelength(
|
|
75
|
+
cls,
|
|
76
|
+
*,
|
|
77
|
+
pixel_size: float,
|
|
78
|
+
wavelength: float = DEFAULT_WAVELENGTH,
|
|
79
|
+
alpha: float = DEFAULT_SPECIFIC_REFRACTIVE_INCREMENT,
|
|
80
|
+
) -> Self:
|
|
81
|
+
"""Build a calculator whose phase path uses `wavelength` (in m)."""
|
|
82
|
+
return cls(
|
|
83
|
+
pixel_size=pixel_size,
|
|
84
|
+
alpha=alpha,
|
|
85
|
+
opd_converter=OPDConverter(wavelength=wavelength),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def wavelength(self) -> float:
|
|
90
|
+
"""The bound OPD converter's wavelength, in m."""
|
|
91
|
+
return self.opd_converter.wavelength
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def wavelength_nm(self) -> float:
|
|
95
|
+
"""The bound OPD converter's wavelength, in nm."""
|
|
96
|
+
return self.opd_converter.wavelength_nm
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def drymass_scale(self) -> float:
|
|
100
|
+
"""pg of dry mass per nm of OPD summed over pixels.
|
|
101
|
+
|
|
102
|
+
The cached ``pixel_area / alpha`` factor: ``mass == drymass_scale *
|
|
103
|
+
sum(opd_nm)``. The OPD analogue is `OPDConverter.opd_scale`.
|
|
104
|
+
"""
|
|
105
|
+
return self._scale
|
|
106
|
+
|
|
107
|
+
def calc_from_opd(
|
|
108
|
+
self,
|
|
109
|
+
opd: NDArray[np.float32],
|
|
110
|
+
*,
|
|
111
|
+
mask: NDArray[np.bool_] | None = None,
|
|
112
|
+
reduce: bool = True,
|
|
113
|
+
) -> NDArray[np.float32]:
|
|
114
|
+
"""Dry mass [pg] from an OPD map (nm), summed over the last two axes (H, W).
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
opd: OPD map(s), in nm, shape ``(..., H, W)``.
|
|
118
|
+
mask: Optional boolean mask, shape ``(H, W)`` or ``(N, H, W)`` for
|
|
119
|
+
`N` objects; multiplied in (broadcast), the 3-D form adding a
|
|
120
|
+
trailing channel axis.
|
|
121
|
+
reduce: If True (default), sum the per-pixel mass over (H, W) and
|
|
122
|
+
return the dry mass, shape ``(...)`` (or ``(..., N)`` with a
|
|
123
|
+
``(N, H, W)`` mask). If False, return the per-pixel mass-density
|
|
124
|
+
map (``opd * scale``, masked) without summing, shape
|
|
125
|
+
``(..., H, W)`` (or ``(..., N, H, W)``).
|
|
126
|
+
|
|
127
|
+
Raises:
|
|
128
|
+
ValueError: If `opd` is not at least 2-D ``(..., H, W)``; if `mask`
|
|
129
|
+
is not 2-D ``(H, W)`` or 3-D ``(N, H, W)`` (a per-frame /
|
|
130
|
+
higher-rank mask like ``(T, N, H, W)`` is unsupported -- loop
|
|
131
|
+
over its leading axes); or if `mask`'s ``(H, W)`` does not match
|
|
132
|
+
`opd`'s.
|
|
133
|
+
"""
|
|
134
|
+
if opd.ndim < 2:
|
|
135
|
+
msg = f"opd must be at least 2D (..., H, W) (got {opd.ndim}D)"
|
|
136
|
+
raise ValueError(msg)
|
|
137
|
+
|
|
138
|
+
use_mask = mask is not None
|
|
139
|
+
|
|
140
|
+
if use_mask:
|
|
141
|
+
if mask.ndim not in (2, 3):
|
|
142
|
+
msg = f"mask must be (H, W) or (N, H, W) (got {mask.ndim}D); loop over the extra (e.g. time) axis"
|
|
143
|
+
raise ValueError(msg)
|
|
144
|
+
if mask.shape[-2:] != opd.shape[-2:]:
|
|
145
|
+
msg = f"opd and mask (H, W) must match (got {opd.shape[-2:]} vs {mask.shape[-2:]})"
|
|
146
|
+
raise ValueError(msg)
|
|
147
|
+
|
|
148
|
+
if reduce:
|
|
149
|
+
opd = opd.astype(np.float64, copy=False)
|
|
150
|
+
if use_mask:
|
|
151
|
+
result = np.tensordot(opd, mask, axes=([-2, -1], [-2, -1]))
|
|
152
|
+
else:
|
|
153
|
+
result = np.sum(opd, axis=(-2, -1))
|
|
154
|
+
elif use_mask:
|
|
155
|
+
if mask.ndim == 3: # (N, H, W): object axis before (H, W)
|
|
156
|
+
opd = opd[..., None, :, :]
|
|
157
|
+
result = opd * mask
|
|
158
|
+
else:
|
|
159
|
+
result = opd
|
|
160
|
+
|
|
161
|
+
# OPD (nm) -> dry mass (pg); accumulated in float64, returned as float32.
|
|
162
|
+
return (result * self._scale).astype(np.float32, copy=False)
|
|
163
|
+
|
|
164
|
+
def calc_from_phase(
|
|
165
|
+
self,
|
|
166
|
+
phase: NDArray[np.float32],
|
|
167
|
+
*,
|
|
168
|
+
mask: NDArray[np.bool_] | None = None,
|
|
169
|
+
reduce: bool = True,
|
|
170
|
+
) -> NDArray[np.float32]:
|
|
171
|
+
"""Dry mass [pg] from a phase map (rad): to OPD, then `calc_from_opd`."""
|
|
172
|
+
opd = self.opd_converter.convert_to_opd(phase)
|
|
173
|
+
return self.calc_from_opd(opd, mask=mask, reduce=reduce)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def calc_drymass(
|
|
177
|
+
opd: NDArray[np.float32],
|
|
178
|
+
*,
|
|
179
|
+
pixel_size: float,
|
|
180
|
+
alpha: float = DEFAULT_SPECIFIC_REFRACTIVE_INCREMENT,
|
|
181
|
+
mask: NDArray[np.bool_] | None = None,
|
|
182
|
+
reduce: bool = True,
|
|
183
|
+
) -> NDArray[np.float32]:
|
|
184
|
+
"""Dry mass [pg] of an OPD map (nm); one-shot `DryMassCalculator.calc_from_opd`.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
opd: OPD map(s), in nm (e.g. from `phase_to_opd`), shape ``(..., H, W)``,
|
|
188
|
+
already background-corrected.
|
|
189
|
+
pixel_size: Physical size of one (square) pixel, in m.
|
|
190
|
+
alpha: Specific refractive increment, in m^3/kg.
|
|
191
|
+
mask: Optional boolean mask, shape ``(H, W)`` or ``(N, H, W)``.
|
|
192
|
+
reduce: Sum over (H, W) to a dry mass (True), or return the per-pixel
|
|
193
|
+
mass-density map (False). See `DryMassCalculator.calc_from_opd`.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
Dry mass in pg, shape ``(...)`` (or ``(..., N)``); or the unreduced
|
|
197
|
+
density map when `reduce` is False.
|
|
198
|
+
"""
|
|
199
|
+
return DryMassCalculator(pixel_size=pixel_size, alpha=alpha).calc_from_opd(
|
|
200
|
+
opd, mask=mask, reduce=reduce
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def calc_drymass_from_phase(
|
|
205
|
+
phase: NDArray[np.float32],
|
|
206
|
+
*,
|
|
207
|
+
pixel_size: float,
|
|
208
|
+
wavelength: float = DEFAULT_WAVELENGTH,
|
|
209
|
+
alpha: float = DEFAULT_SPECIFIC_REFRACTIVE_INCREMENT,
|
|
210
|
+
mask: NDArray[np.bool_] | None = None,
|
|
211
|
+
reduce: bool = True,
|
|
212
|
+
) -> NDArray[np.float32]:
|
|
213
|
+
"""Dry mass [pg] from a phase map (rad); one-shot `DryMassCalculator`.
|
|
214
|
+
|
|
215
|
+
Converts `phase` to OPD at `wavelength`, then integrates as `calc_drymass`.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
phase: Phase map(s), in rad, shape ``(..., H, W)``, already
|
|
219
|
+
background-corrected.
|
|
220
|
+
pixel_size: Physical size of one (square) pixel, in m.
|
|
221
|
+
wavelength: Illumination wavelength, in m.
|
|
222
|
+
alpha: Specific refractive increment, in m^3/kg.
|
|
223
|
+
mask: Optional boolean mask, shape ``(H, W)`` or ``(N, H, W)``.
|
|
224
|
+
reduce: Sum over (H, W) to a dry mass (True), or return the per-pixel
|
|
225
|
+
mass-density map (False).
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Dry mass in pg, shape ``(...)`` (or ``(..., N)``); or the unreduced
|
|
229
|
+
density map when `reduce` is False.
|
|
230
|
+
"""
|
|
231
|
+
return DryMassCalculator.from_wavelength(
|
|
232
|
+
pixel_size=pixel_size, alpha=alpha, wavelength=wavelength
|
|
233
|
+
).calc_from_phase(phase, mask=mask, reduce=reduce)
|