Metadata-Version: 2.4
Name: simsh5
Version: 0.3.2
Summary: Reader and analysis recipes for open SIMS-H5 (tof-sims-sparse-hdf5) TOF-SIMS data
Project-URL: Homepage, https://github.com/PhysicalElectronics/simsh5
Project-URL: Source, https://github.com/PhysicalElectronics/simsh5
Project-URL: Format spec, https://github.com/PhysicalElectronics/simsh5/blob/main/docs/format-spec.md
Author: ULVAC-PHI
License-Expression: MIT
License-File: LICENSE
Keywords: HDF5,SIMS,TOF-SIMS,hyperspectral,imaging,mass-spectrometry
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Scientific/Engineering
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: h5py>=3.10
Requires-Dist: hdf5plugin>=4.1
Requires-Dist: numpy>=1.26
Provides-Extra: dev
Requires-Dist: mypy>=1.8; extra == 'dev'
Requires-Dist: pytest>=7.4; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Provides-Extra: notebook
Requires-Dist: ipywidgets>=8; extra == 'notebook'
Requires-Dist: matplotlib>=3.7; extra == 'notebook'
Requires-Dist: notebook>=7; extra == 'notebook'
Description-Content-Type: text/markdown

# simsh5

Reader and analysis recipes for **open SIMS-H5** (`tof-sims-sparse-hdf5`) TOF-SIMS
data. Small and dependency-light — a SIMS-H5 archive is self-contained, so reading
it needs only `numpy` + `h5py` (plus `hdf5plugin` for the compression filters).

The file format is defined by the **open TOF-SIMS format** spec, vendored here at
[`docs/format-spec.md`](https://github.com/PhysicalElectronics/simsh5/blob/main/docs/format-spec.md);
this library implements its §10 analyst recipes behind a small API.

## Install

```bash
pip install simsh5                # core reader (numpy + h5py + hdf5plugin)
pip install "simsh5[notebook]"    # + matplotlib/Jupyter for examples/
```

## Usage

```python
import simsh5

arc = simsh5.open_archive("scan.h5")     # or use as a context manager
print(arc.acquisition_mode, arc.polarity, arc.row_shape)

img  = simsh5.total_ion_image(arc)            # total counts; (ny, nx), or stitched mosaic
mz, intensity = simsh5.total_spectrum(arc)    # summed spectrum
fe   = simsh5.ion_image(arc, 55.0, 57.0)      # ion image for an m/z window
mz_p, counts_p = simsh5.spectrum_at_pixel(arc, 0)
arc.close()
```

For `depth_profile` archives, `simsh5.depth_profile(arc)` returns
`(sputter_time_s, counts_per_cycle)`.

For `mosaic` archives, `total_ion_image` / `ion_image` **stitch** the tiles onto
the stage grid using `/mosaic/tile_index_xy`, returning a `(gy*ny, gx*nx)` panorama
(e.g. a 6×6 grid of 256×256 tiles → `(1536, 1536)`) rather than `(ny, nx)`.

Images use a **lower-left origin** (PHI convention: row 0 is the bottom row), so
display them with `plt.imshow(img, origin="lower")`. Both image recipes accept
`bin_factor=N` to sum non-overlapping `N×N` pixel blocks (2→1, 4→1, …; must divide
the image dimensions) for coarser, higher-SNR images.

### What it handles for you

- Sparse **CSR-on-mass-axis** decoding (`mass_bin` / `indptr` / `pixel_id` / `count`).
- `count > 255` exceptions patched back in.
- Absent `@mass_bin_width_ps` → treated as lossless (`1`).
- Per-mode geometry: `image_2d` / `depth_profile` collapse to `(ny, nx)`; `mosaic`
  tiles are **stitched** onto the stage grid → `(gy*ny, gx*nx)` (lower-left origin).
- Optional `bin_factor` pixel binning on `total_ion_image` / `ion_image`.
- Quadratic m/z calibration (`tof_ps_to_mz` / `mz_to_tof_ps`).

## API reference

Everything below is re-exported flat from the top-level `simsh5` package
(`from simsh5 import open_archive, total_ion_image, ...`).

### `open_archive(path) -> Archive`

Open a SIMS-H5 archive, eagerly loading its mass CSR cube and calibration. Handles
the format gotchas for you: a missing `@mass_bin_width_ps` falls back to `1`
(lossless / raw picoseconds), and `count` values clamped at 255 in the `u8` dataset
are restored from `count_exceptions/`. Keeps the underlying `h5py.File` open for
optional groups, so close it when done (or use it as a context manager).

- `path` — `str | pathlib.Path` to the `.h5` file.
- Returns an [`Archive`](#archive). Raises if the file is missing required groups
  (`/data/mass_csr`, `/calibration/mass/coefficients`, `/geometry`).

### `Archive`

Dataclass holding an opened archive with its CSR-on-mass-axis cube in memory.
Supports the context-manager protocol (`with open_archive(p) as arc: ...`).

**Attributes**

| Attribute | Type | Description |
|---|---|---|
| `path` | `pathlib.Path` | Source file path. |
| `acquisition_mode` | `str` | `"image_2d"`, `"depth_profile"`, or `"mosaic"`. |
| `polarity` | `str` | Ion polarity, e.g. `"Pos"` / `"Neg"`. |
| `row_shape` | `tuple[int, ...]` | Per-mode leading axes + raster: `image_2d` → `(n_frames, ny, nx)`; `depth_profile` → `(n_cycles, n_frames, ny, nx)`; `mosaic` → `(n_tiles, n_frames, ny, nx)`. |
| `mass_bin` | `ndarray[uint32]` | Occupied mass-bin indices, strictly ascending `(K,)`. |
| `indptr` | `ndarray[uint64]` | CSR row pointer `(K+1,)`; bin `k` owns `pixel_id[indptr[k]:indptr[k+1]]`. |
| `pixel_id` | `ndarray[uint32]` | Flat (C-order) pixel of each non-zero `(N,)`, ascending within each row. |
| `count` | `ndarray[uint32]` | True ion count per non-zero `(N,)`, with `count_exceptions` patched in. |
| `mass_bin_width_ps` | `float` | `tof_ps = mass_bin * mass_bin_width_ps` (`1` == lossless). |
| `cal_a`, `cal_b` | `float` | Quadratic mass-calibration coefficients `[a, b]`. |

**Properties**

- `ny` / `nx` — raster height / width (last two axes of `row_shape`).
- `n_rows` — total rows in the cube (`prod(row_shape)`); `pixel_id` indexes into this.

**Methods**

- `tof_ps_to_mz(tof_ps)` / `mz_to_tof_ps(mz)` — bound wrappers over the module-level
  calibration functions using this archive's `cal_a` / `cal_b`.
- `close()` — close the underlying `h5py.File`.

### Analysis recipes

Each takes an `Archive` and reduces the sparse cube with vectorized numpy.

- **`total_ion_image(arc, bin_factor=1) -> ndarray[float64]`** — total counts per
  pixel, summed over mass and frames. `(ny, nx)` for `image_2d` / `depth_profile`;
  a stitched `(gy*ny, gx*nx)` panorama for `mosaic`. `bin_factor=N` sums
  non-overlapping `N×N` blocks (must evenly divide the dims). Lower-left origin.
- **`total_spectrum(arc) -> (mz, intensity)`** — summed spectrum. Uses the
  `/derived/total_spectrum` view when present, else sums the CSR. Both arrays are
  `float64`, one entry per occupied mass bin.
- **`ion_image(arc, m_lo, m_hi, bin_factor=1) -> ndarray[float64]`** — ion image for
  the m/z window `[m_lo, m_hi]`. Same shape / `bin_factor` / origin rules as
  `total_ion_image`.
- **`spectrum_at_pixel(arc, pixel) -> (mz, counts)`** — spectrum at a single flat
  `pixel` id. Uses the `/data/pixel_index` back-index when present, else an O(N)
  scan. `counts` is `uint32`.
- **`depth_profile(arc) -> (sputter_time_s, counts)`** — counts per sputter cycle.
  Only valid for `depth_profile` archives; raises `ValueError` otherwise.

### Calibration converters

Module-level functions taking explicit coefficients (the `Archive` methods wrap
these with its own `cal_a` / `cal_b`):

- **`tof_ps_to_mz(tof_ps, a, b) -> ndarray[float64]`** — flight time (ps) → m/z.
- **`mz_to_tof_ps(mz, a, b) -> ndarray[float64]`** — m/z → flight time (ps).

### `__version__`

The installed package version string (`"0.0.0"` when run from an uninstalled tree).

## Development

```bash
pip install -e ".[dev]"
ruff check . && mypy && pytest
```

Tests run against any `tests/fixtures/*.h5` archive (an `image_2d` sample and a
synthetic `mosaic` are bundled).

## License

MIT — see [`LICENSE`](https://github.com/PhysicalElectronics/simsh5/blob/main/LICENSE).
