zipoop 0.1.0__tar.gz → 0.2.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.
- {zipoop-0.1.0 → zipoop-0.2.0}/PKG-INFO +22 -7
- zipoop-0.2.0/README.md +46 -0
- {zipoop-0.1.0 → zipoop-0.2.0}/pyproject.toml +4 -3
- zipoop-0.2.0/tests/test_v4_format.py +60 -0
- {zipoop-0.1.0 → zipoop-0.2.0}/zipoop/__init__.py +1 -1
- zipoop-0.2.0/zipoop/shard.py +158 -0
- {zipoop-0.1.0 → zipoop-0.2.0}/zipoop.egg-info/PKG-INFO +22 -7
- {zipoop-0.1.0 → zipoop-0.2.0}/zipoop.egg-info/SOURCES.txt +1 -0
- zipoop-0.1.0/README.md +0 -32
- zipoop-0.1.0/zipoop/shard.py +0 -105
- {zipoop-0.1.0 → zipoop-0.2.0}/LICENSE +0 -0
- {zipoop-0.1.0 → zipoop-0.2.0}/setup.cfg +0 -0
- {zipoop-0.1.0 → zipoop-0.2.0}/tests/test_zipoop.py +0 -0
- {zipoop-0.1.0 → zipoop-0.2.0}/zipoop/cli.py +0 -0
- {zipoop-0.1.0 → zipoop-0.2.0}/zipoop/data/encode-panel_1885.json +0 -0
- {zipoop-0.1.0 → zipoop-0.2.0}/zipoop/dataset.py +0 -0
- {zipoop-0.1.0 → zipoop-0.2.0}/zipoop/decoder.py +0 -0
- {zipoop-0.1.0 → zipoop-0.2.0}/zipoop/evaluation.py +0 -0
- {zipoop-0.1.0 → zipoop-0.2.0}/zipoop/masks.py +0 -0
- {zipoop-0.1.0 → zipoop-0.2.0}/zipoop/network.py +0 -0
- {zipoop-0.1.0 → zipoop-0.2.0}/zipoop/panel.py +0 -0
- {zipoop-0.1.0 → zipoop-0.2.0}/zipoop/teacher.py +0 -0
- {zipoop-0.1.0 → zipoop-0.2.0}/zipoop.egg-info/dependency_links.txt +0 -0
- {zipoop-0.1.0 → zipoop-0.2.0}/zipoop.egg-info/entry_points.txt +0 -0
- {zipoop-0.1.0 → zipoop-0.2.0}/zipoop.egg-info/requires.txt +0 -0
- {zipoop-0.1.0 → zipoop-0.2.0}/zipoop.egg-info/top_level.txt +0 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: zipoop
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Decoder and reader for Zipoop teacher shards (per-window neural compression of ENCODE signal tracks)
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Decoder and reader for Zipoop teacher shards (per-window neural compression of ENCODE signal tracks), v3 and v4 archives
|
|
5
5
|
Author: zatumout
|
|
6
6
|
License-Expression: MIT
|
|
7
|
-
Project-URL: Dataset, https://huggingface.co/datasets/zatumout/
|
|
7
|
+
Project-URL: Dataset, https://huggingface.co/datasets/zatumout/zipoop_32k1885_010_v4
|
|
8
|
+
Project-URL: DatasetV3, https://huggingface.co/datasets/zatumout/zipoop_32k1885_010
|
|
8
9
|
Keywords: genomics,ENCODE,neural compression,distillation
|
|
9
10
|
Classifier: Programming Language :: Python :: 3
|
|
10
11
|
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
@@ -19,11 +20,18 @@ Dynamic: license-file
|
|
|
19
20
|
|
|
20
21
|
# zipoop
|
|
21
22
|
|
|
22
|
-
Decoder and reader for the Zipoop teacher shards
|
|
23
|
+
Decoder and reader for the Zipoop teacher shards: 89,438 windows of 32 kb, 1,885 ENCODE tracks (GRCh38), stored as one `<chrom>.npz` per chromosome plus `MANIFEST.tsv`.
|
|
24
|
+
|
|
25
|
+
Two equivalent releases are published:
|
|
26
|
+
|
|
27
|
+
- [zatumout/zipoop_32k1885_010_v4](https://huggingface.co/datasets/zatumout/zipoop_32k1885_010_v4) — `pooper-dna-v4`, 89.3 GiB, **requires `zipoop>=0.2`**;
|
|
28
|
+
- [zatumout/zipoop_32k1885_010](https://huggingface.co/datasets/zatumout/zipoop_32k1885_010) — the older `pooper-dna-v3` encoding, 128.4 GiB, read by 0.1.x as well.
|
|
29
|
+
|
|
30
|
+
Both decode to the same signals; v4 only changes how the arrays are stored (byte-shuffled members, LZMA, and a float16 sparse FFT with one float32 scale per track).
|
|
23
31
|
|
|
24
32
|
## Data layout
|
|
25
33
|
|
|
26
|
-
`<root>/<chrom>.npz` (25 files) plus `MANIFEST.tsv` (chrom, windows, members, bytes, sha256). Each file is a
|
|
34
|
+
`<root>/<chrom>.npz` (25 files) plus `MANIFEST.tsv` (chrom, windows, members, bytes, sha256). Each file is a zip with members `<window>/<key>.npy` (161 arrays per window in v3, 164 in v4), windows in genome order. Window names are `<chrom>_<start>_<end>`.
|
|
27
35
|
|
|
28
36
|
## Usage
|
|
29
37
|
|
|
@@ -42,9 +50,16 @@ Lower level: `Dataset.read(window)` gives the parsed `Shard`, `Decoder(shard).de
|
|
|
42
50
|
|
|
43
51
|
Rules baked into `load_window` (see `zipoop/teacher.py`): RNA-seq tracks are 0 and valid where unrecorded; other tracks stay missing there. Opening a chromosome file parses its whole zip directory (chr1 about 3.6 s and 0.9 GB), so keep the `Dataset` open; forked DataLoader workers reopen their own handles.
|
|
44
52
|
|
|
45
|
-
|
|
53
|
+
## Supported formats
|
|
54
|
+
|
|
55
|
+
| archive | `format` | `hybrid_format` | storage |
|
|
56
|
+
|---|---|---|---|
|
|
57
|
+
| v3 | `pooper-dna-v3` | `fft-plus-dna-residual-v1` | deflate, `fft_coefficients` complex64 |
|
|
58
|
+
| v4 | `pooper-dna-v4` | `fft-plus-dna-residual-v2` | LZMA, byte-shuffled floats, `fft_packed` float16 + `fft_scale` float32 |
|
|
59
|
+
|
|
60
|
+
`read_shard` accepts both and rejects anything else with a clear error. The v4 decode path is verified against v3: parameters, masks and calibrations are bit-identical, the FFT payload differs only by float16 rounding (max relative error about 4.7e-4), and decoded windows reach a per-track Pearson of at least 0.9999997 against the v3 decode.
|
|
46
61
|
|
|
47
|
-
Tests: `ZIPOOP_DATA=<weights_npz> pytest tests` (the pooper-equivalence test also needs the original shards and the pooper package).
|
|
62
|
+
Tests: `ZIPOOP_DATA=<weights_npz> pytest tests` (the pooper-equivalence test also needs the original shards and the pooper package). The v4 storage encoding has dataset-free unit tests in `tests/test_v4_format.py`.
|
|
48
63
|
|
|
49
64
|
## License and data terms
|
|
50
65
|
|
zipoop-0.2.0/README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# zipoop
|
|
2
|
+
|
|
3
|
+
Decoder and reader for the Zipoop teacher shards: 89,438 windows of 32 kb, 1,885 ENCODE tracks (GRCh38), stored as one `<chrom>.npz` per chromosome plus `MANIFEST.tsv`.
|
|
4
|
+
|
|
5
|
+
Two equivalent releases are published:
|
|
6
|
+
|
|
7
|
+
- [zatumout/zipoop_32k1885_010_v4](https://huggingface.co/datasets/zatumout/zipoop_32k1885_010_v4) — `pooper-dna-v4`, 89.3 GiB, **requires `zipoop>=0.2`**;
|
|
8
|
+
- [zatumout/zipoop_32k1885_010](https://huggingface.co/datasets/zatumout/zipoop_32k1885_010) — the older `pooper-dna-v3` encoding, 128.4 GiB, read by 0.1.x as well.
|
|
9
|
+
|
|
10
|
+
Both decode to the same signals; v4 only changes how the arrays are stored (byte-shuffled members, LZMA, and a float16 sparse FFT with one float32 scale per track).
|
|
11
|
+
|
|
12
|
+
## Data layout
|
|
13
|
+
|
|
14
|
+
`<root>/<chrom>.npz` (25 files) plus `MANIFEST.tsv` (chrom, windows, members, bytes, sha256). Each file is a zip with members `<window>/<key>.npy` (161 arrays per window in v3, 164 in v4), windows in genome order. Window names are `<chrom>_<start>_<end>`.
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from zipoop import Dataset
|
|
20
|
+
with Dataset("/path/to/weights_npz") as ds:
|
|
21
|
+
w = ds.load_window(ds.windows("chr21")[0]) # TeacherWindow: key, dna (L,), signal (L, T), valid (L, T)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
zipoop verify /path/to/weights_npz # size + sha256 against MANIFEST.tsv
|
|
26
|
+
zipoop show /path/to/weights_npz chr21_10027008_10059776
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Lower level: `Dataset.read(window)` gives the parsed `Shard`, `Decoder(shard).decode(start, end, tracks)` decodes any slice or track subset. Evaluation helpers (`window_pearson`, `summarize`, `calibrate`, `restore_real`) are in `zipoop.evaluation`.
|
|
30
|
+
|
|
31
|
+
Rules baked into `load_window` (see `zipoop/teacher.py`): RNA-seq tracks are 0 and valid where unrecorded; other tracks stay missing there. Opening a chromosome file parses its whole zip directory (chr1 about 3.6 s and 0.9 GB), so keep the `Dataset` open; forked DataLoader workers reopen their own handles.
|
|
32
|
+
|
|
33
|
+
## Supported formats
|
|
34
|
+
|
|
35
|
+
| archive | `format` | `hybrid_format` | storage |
|
|
36
|
+
|---|---|---|---|
|
|
37
|
+
| v3 | `pooper-dna-v3` | `fft-plus-dna-residual-v1` | deflate, `fft_coefficients` complex64 |
|
|
38
|
+
| v4 | `pooper-dna-v4` | `fft-plus-dna-residual-v2` | LZMA, byte-shuffled floats, `fft_packed` float16 + `fft_scale` float32 |
|
|
39
|
+
|
|
40
|
+
`read_shard` accepts both and rejects anything else with a clear error. The v4 decode path is verified against v3: parameters, masks and calibrations are bit-identical, the FFT payload differs only by float16 rounding (max relative error about 4.7e-4), and decoded windows reach a per-track Pearson of at least 0.9999997 against the v3 decode.
|
|
41
|
+
|
|
42
|
+
Tests: `ZIPOOP_DATA=<weights_npz> pytest tests` (the pooper-equivalence test also needs the original shards and the pooper package). The v4 storage encoding has dataset-free unit tests in `tests/test_v4_format.py`.
|
|
43
|
+
|
|
44
|
+
## License and data terms
|
|
45
|
+
|
|
46
|
+
The code is MIT-licensed (see `LICENSE`; keep the copyright notice "zatumout"). The shard dataset is released separately under CC BY 4.0 (credit: zatumout), commercial use and model distillation allowed. The shards and `zipoop/data/encode-panel_1885.json` are derived from public ENCODE tracks: when you use them, also cite and acknowledge the ENCODE Consortium as described at https://www.encodeproject.org/help/citing-encode.
|
|
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "zipoop"
|
|
7
|
-
version = "0.
|
|
8
|
-
description = "Decoder and reader for Zipoop teacher shards (per-window neural compression of ENCODE signal tracks)"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Decoder and reader for Zipoop teacher shards (per-window neural compression of ENCODE signal tracks), v3 and v4 archives"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
11
11
|
license = "MIT"
|
|
@@ -19,7 +19,8 @@ dependencies = ["numpy>=1.26", "torch>=2.2"]
|
|
|
19
19
|
test = ["pytest>=8"]
|
|
20
20
|
|
|
21
21
|
[project.urls]
|
|
22
|
-
Dataset = "https://huggingface.co/datasets/zatumout/
|
|
22
|
+
Dataset = "https://huggingface.co/datasets/zatumout/zipoop_32k1885_010_v4"
|
|
23
|
+
DatasetV3 = "https://huggingface.co/datasets/zatumout/zipoop_32k1885_010"
|
|
23
24
|
|
|
24
25
|
[project.scripts]
|
|
25
26
|
zipoop = "zipoop.cli:main"
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Unit tests for the v4 storage encoding (no dataset required)."""
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
from zipoop.shard import HYBRID_FORMAT_V1, HYBRID_FORMAT_V2, _Reader
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def shuffle(a):
|
|
8
|
+
"""Mirror of the writer: float/complex arrays -> (itemsize, n) uint8 columns."""
|
|
9
|
+
v = np.asarray(a)
|
|
10
|
+
return np.ascontiguousarray(v).view(np.uint8).reshape(-1, v.dtype.itemsize).T.copy()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def spec_of(**arrays):
|
|
14
|
+
return {k: {"dtype": np.asarray(v).dtype.str, "shape": list(np.asarray(v).shape)} for k, v in arrays.items()}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_byte_shuffle_roundtrip():
|
|
18
|
+
rng = np.random.default_rng(0)
|
|
19
|
+
a = rng.standard_normal((7, 5)).astype(np.float32)
|
|
20
|
+
view = {"gain": shuffle(a)}
|
|
21
|
+
out = _Reader(view, spec_of(gain=a), HYBRID_FORMAT_V1)["gain"]
|
|
22
|
+
assert out.dtype == np.float32
|
|
23
|
+
assert np.array_equal(out, a)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_unshuffled_members_pass_through():
|
|
27
|
+
a = np.arange(12, dtype=np.uint16)
|
|
28
|
+
assert np.array_equal(_Reader({"fft_indices": a}, {}, HYBRID_FORMAT_V1)["fft_indices"], a)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_fft_v2_dequantization():
|
|
32
|
+
rng = np.random.default_rng(1)
|
|
33
|
+
tracks, bins = 4, 6
|
|
34
|
+
coefficients = (rng.standard_normal((tracks, bins)) + 1j * rng.standard_normal((tracks, bins))).astype(np.complex64)
|
|
35
|
+
pairs = np.stack((coefficients.real, coefficients.imag), axis=-1)
|
|
36
|
+
scale = np.abs(pairs).max(axis=(1, 2), keepdims=True).astype(np.float32)
|
|
37
|
+
packed = (pairs / scale).astype(np.float16)
|
|
38
|
+
scale = scale.reshape(tracks, 1, 1)
|
|
39
|
+
indices = np.sort(rng.choice(64, size=bins, replace=False)).astype(np.uint16)[None, :].repeat(tracks, 0)
|
|
40
|
+
|
|
41
|
+
view = {"fft_indices": indices, "fft_packed": shuffle(packed), "fft_scale": shuffle(scale)}
|
|
42
|
+
reader = _Reader(view, spec_of(fft_packed=packed, fft_scale=scale), HYBRID_FORMAT_V2)
|
|
43
|
+
got_i, got_c = reader.fft()
|
|
44
|
+
assert np.array_equal(got_i, indices)
|
|
45
|
+
assert got_c.dtype == np.complex64
|
|
46
|
+
assert got_c.shape == (tracks, bins)
|
|
47
|
+
reference = packed.astype(np.float32) * scale # the same float32 reference the writer used
|
|
48
|
+
reference = reference[..., 0] + 1j * reference[..., 1]
|
|
49
|
+
assert np.allclose(got_c, reference, rtol=0, atol=1e-6)
|
|
50
|
+
# and it stays within float16 rounding of the original complex64 payload
|
|
51
|
+
assert np.max(np.abs(got_c - coefficients)) < 0.02 * np.max(np.abs(coefficients))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_v3_reader_uses_complex_payload():
|
|
55
|
+
tracks, bins = 2, 3
|
|
56
|
+
coefficients = (np.ones((tracks, bins)) * (1 + 2j)).astype(np.complex64)
|
|
57
|
+
indices = np.zeros((tracks, bins), dtype=np.uint16)
|
|
58
|
+
got_i, got_c = _Reader({"fft_indices": indices, "fft_coefficients": coefficients}, {}, HYBRID_FORMAT_V1).fft()
|
|
59
|
+
assert np.array_equal(got_i, indices)
|
|
60
|
+
assert np.array_equal(got_c, coefficients)
|
|
@@ -6,6 +6,6 @@ from .panel import panel_tracks, zero_tracks
|
|
|
6
6
|
from .shard import Shard, read_shard
|
|
7
7
|
from .teacher import TeacherWindow, load_window
|
|
8
8
|
|
|
9
|
-
__version__ = "0.
|
|
9
|
+
__version__ = "0.2.0"
|
|
10
10
|
__all__ = ["Dataset", "verify", "Decoder", "Shard", "read_shard", "TeacherWindow", "load_window",
|
|
11
11
|
"panel_tracks", "zero_tracks", "calibrate", "restore_real", "summarize", "window_pearson"]
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""Parse and validate one teacher shard (a ``pooper-dna-v3`` or ``pooper-dna-v4`` hybrid archive) into a typed record."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
from contextlib import contextmanager
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
from .masks import MASK_FORMAT, decode_mask
|
|
13
|
+
from .network import ModelConfig
|
|
14
|
+
|
|
15
|
+
SHARD_FORMAT_V3 = "pooper-dna-v3"
|
|
16
|
+
SHARD_FORMAT_V4 = "pooper-dna-v4"
|
|
17
|
+
SHARD_FORMATS = (SHARD_FORMAT_V3, SHARD_FORMAT_V4)
|
|
18
|
+
HYBRID_FORMAT_V1 = "fft-plus-dna-residual-v1"
|
|
19
|
+
HYBRID_FORMAT_V2 = "fft-plus-dna-residual-v2"
|
|
20
|
+
HYBRID_FORMATS = (HYBRID_FORMAT_V1, HYBRID_FORMAT_V2)
|
|
21
|
+
STORAGE_ENCODING_V4 = "byte-shuffle-lzma-v1"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ShardView:
|
|
25
|
+
"""Read-only mapping over one window inside a merged per-chromosome npz (members "<window>/<key>")."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, npz, key: str, members):
|
|
28
|
+
self._npz, self.key, self._members = npz, key, frozenset(members)
|
|
29
|
+
|
|
30
|
+
def __contains__(self, name):
|
|
31
|
+
return name in self._members
|
|
32
|
+
|
|
33
|
+
def __getitem__(self, name):
|
|
34
|
+
if name not in self._members:
|
|
35
|
+
raise KeyError(name)
|
|
36
|
+
return self._npz[f"{self.key}/{name}"]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class Shard:
|
|
41
|
+
key: str
|
|
42
|
+
shape: tuple[int, int] # (length, tracks)
|
|
43
|
+
metadata: dict
|
|
44
|
+
config: ModelConfig
|
|
45
|
+
dna: np.ndarray # (L,) uint8
|
|
46
|
+
valid: np.ndarray # (L, T) bool
|
|
47
|
+
parameters: dict[str, np.ndarray]
|
|
48
|
+
mean: np.ndarray # per-track target scaling and calibration, (T,)
|
|
49
|
+
power: np.ndarray
|
|
50
|
+
gain: np.ndarray
|
|
51
|
+
offset: np.ndarray
|
|
52
|
+
fft_indices: np.ndarray # (T, K) retained rfft bins
|
|
53
|
+
fft_coefficients: np.ndarray # (T, K) complex64
|
|
54
|
+
final_gain: np.ndarray
|
|
55
|
+
final_offset: np.ndarray
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@contextmanager
|
|
59
|
+
def _opened(source):
|
|
60
|
+
if isinstance(source, (str, os.PathLike)):
|
|
61
|
+
with np.load(source, allow_pickle=False) as z:
|
|
62
|
+
yield z, Path(source).stem
|
|
63
|
+
else:
|
|
64
|
+
yield source, source.key
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class _Reader:
|
|
68
|
+
"""Member access that transparently undoes the v4 byte-shuffle and FFT quantization."""
|
|
69
|
+
|
|
70
|
+
def __init__(self, view, spec, hybrid_format):
|
|
71
|
+
self._view = view
|
|
72
|
+
self._spec = spec
|
|
73
|
+
self._hybrid = hybrid_format
|
|
74
|
+
|
|
75
|
+
def __contains__(self, name):
|
|
76
|
+
return name in self._view
|
|
77
|
+
|
|
78
|
+
def raw(self, name):
|
|
79
|
+
return self._view[name]
|
|
80
|
+
|
|
81
|
+
def __getitem__(self, name):
|
|
82
|
+
a = self._view[name]
|
|
83
|
+
meta = self._spec.get(name)
|
|
84
|
+
if meta is not None:
|
|
85
|
+
# v4 stores float/complex arrays as (itemsize, n) uint8 columns.
|
|
86
|
+
packed = np.ascontiguousarray(np.asarray(a))
|
|
87
|
+
if packed.dtype != np.uint8 or packed.ndim != 2:
|
|
88
|
+
raise ValueError(f"Invalid byte-shuffled member {name!r}")
|
|
89
|
+
b = np.ascontiguousarray(packed.T)
|
|
90
|
+
a = b.view(np.dtype(meta["dtype"])).reshape(tuple(meta["shape"]))
|
|
91
|
+
return a
|
|
92
|
+
|
|
93
|
+
def fft(self):
|
|
94
|
+
"""(indices, complex64 coefficients) for either hybrid format."""
|
|
95
|
+
indices = np.asarray(self["fft_indices"])
|
|
96
|
+
if self._hybrid == HYBRID_FORMAT_V2:
|
|
97
|
+
packed = np.asarray(self["fft_packed"], dtype=np.float32)
|
|
98
|
+
scale = np.asarray(self["fft_scale"], dtype=np.float32)
|
|
99
|
+
if packed.ndim != 3 or packed.shape[-1] != 2 or packed.shape[:2] != indices.shape:
|
|
100
|
+
raise ValueError("Invalid packed FFT payload")
|
|
101
|
+
coefficients = (packed[..., 0] + 1j * packed[..., 1]) * scale.reshape(-1, 1)
|
|
102
|
+
return indices, np.ascontiguousarray(coefficients, dtype=np.complex64)
|
|
103
|
+
return indices, np.asarray(self["fft_coefficients"], dtype=np.complex64)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def read_shard(source) -> Shard:
|
|
107
|
+
"""``source``: a shard path, or a ShardView from :class:`zipoop.Dataset`."""
|
|
108
|
+
with _opened(source) as (z, key):
|
|
109
|
+
if "format" not in z or str(z["format"]) not in SHARD_FORMATS:
|
|
110
|
+
raise ValueError(f"Unsupported shard format: {z['format'] if 'format' in z else None}")
|
|
111
|
+
if "hybrid_format" not in z or str(z["hybrid_format"]) not in HYBRID_FORMATS:
|
|
112
|
+
raise ValueError("Unsupported hybrid archive")
|
|
113
|
+
if "valid_encoding" not in z or str(z["valid_encoding"]) != MASK_FORMAT:
|
|
114
|
+
raise ValueError("Unsupported mask encoding")
|
|
115
|
+
spec = {}
|
|
116
|
+
if "byte_shuffle" in z:
|
|
117
|
+
spec = json.loads(z["byte_shuffle"].tobytes())
|
|
118
|
+
if not isinstance(spec, dict):
|
|
119
|
+
raise ValueError("Invalid byte-shuffle index")
|
|
120
|
+
if "storage_encoding" in z and str(z["storage_encoding"]) != STORAGE_ENCODING_V4:
|
|
121
|
+
raise ValueError(f"Unsupported storage encoding: {str(z['storage_encoding'])}")
|
|
122
|
+
view = _Reader(z, spec, str(z["hybrid_format"]))
|
|
123
|
+
shape = tuple(int(n) for n in z["shape"])
|
|
124
|
+
if len(shape) != 2:
|
|
125
|
+
raise ValueError("Invalid shape")
|
|
126
|
+
length, tracks = shape
|
|
127
|
+
dna = view["dna"].copy()
|
|
128
|
+
if dna.shape != (length,) or np.any(dna > 4):
|
|
129
|
+
raise ValueError("Invalid DNA data in archive")
|
|
130
|
+
names = json.loads(view["parameter_names"].tobytes())
|
|
131
|
+
if len(names) != len(set(names)):
|
|
132
|
+
raise ValueError("Duplicate parameter names")
|
|
133
|
+
fft_indices, fft_coefficients = view.fft()
|
|
134
|
+
shard = Shard(
|
|
135
|
+
key=key, shape=shape, metadata=json.loads(view["metadata"].tobytes()),
|
|
136
|
+
config=ModelConfig.from_json(json.loads(view["model_config"].tobytes())),
|
|
137
|
+
dna=dna, valid=decode_mask(view["valid_v2"].tobytes(), shape),
|
|
138
|
+
parameters={name: view[f"parameter_{i}"].copy() for i, name in enumerate(names)},
|
|
139
|
+
fft_indices=fft_indices.copy(), fft_coefficients=fft_coefficients.copy(),
|
|
140
|
+
**{name: view[name].copy() for name in ("mean", "power", "gain", "offset",
|
|
141
|
+
"final_gain", "final_offset")})
|
|
142
|
+
_validate(shard)
|
|
143
|
+
return shard
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _validate(s: Shard) -> None:
|
|
147
|
+
length, tracks = s.shape
|
|
148
|
+
if any(a.shape != (tracks,) for a in (s.mean, s.power, s.gain, s.offset)):
|
|
149
|
+
raise ValueError("Invalid track scales")
|
|
150
|
+
i, c = s.fft_indices, s.fft_coefficients
|
|
151
|
+
if (i.ndim != 2 or i.shape[0] != tracks or c.shape != i.shape or not np.issubdtype(i.dtype, np.integer)
|
|
152
|
+
or (i < 0).any() or (i > length // 2).any() or not np.isfinite(c).all()):
|
|
153
|
+
raise ValueError("Invalid FFT payload")
|
|
154
|
+
for a in (s.final_gain, s.final_offset):
|
|
155
|
+
if a.shape != (tracks,) or not np.isfinite(a).all():
|
|
156
|
+
raise ValueError("Invalid final-signal calibration")
|
|
157
|
+
if (s.final_gain < 0).any():
|
|
158
|
+
raise ValueError("Invalid final-signal calibration")
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: zipoop
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Decoder and reader for Zipoop teacher shards (per-window neural compression of ENCODE signal tracks)
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Decoder and reader for Zipoop teacher shards (per-window neural compression of ENCODE signal tracks), v3 and v4 archives
|
|
5
5
|
Author: zatumout
|
|
6
6
|
License-Expression: MIT
|
|
7
|
-
Project-URL: Dataset, https://huggingface.co/datasets/zatumout/
|
|
7
|
+
Project-URL: Dataset, https://huggingface.co/datasets/zatumout/zipoop_32k1885_010_v4
|
|
8
|
+
Project-URL: DatasetV3, https://huggingface.co/datasets/zatumout/zipoop_32k1885_010
|
|
8
9
|
Keywords: genomics,ENCODE,neural compression,distillation
|
|
9
10
|
Classifier: Programming Language :: Python :: 3
|
|
10
11
|
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
@@ -19,11 +20,18 @@ Dynamic: license-file
|
|
|
19
20
|
|
|
20
21
|
# zipoop
|
|
21
22
|
|
|
22
|
-
Decoder and reader for the Zipoop teacher shards
|
|
23
|
+
Decoder and reader for the Zipoop teacher shards: 89,438 windows of 32 kb, 1,885 ENCODE tracks (GRCh38), stored as one `<chrom>.npz` per chromosome plus `MANIFEST.tsv`.
|
|
24
|
+
|
|
25
|
+
Two equivalent releases are published:
|
|
26
|
+
|
|
27
|
+
- [zatumout/zipoop_32k1885_010_v4](https://huggingface.co/datasets/zatumout/zipoop_32k1885_010_v4) — `pooper-dna-v4`, 89.3 GiB, **requires `zipoop>=0.2`**;
|
|
28
|
+
- [zatumout/zipoop_32k1885_010](https://huggingface.co/datasets/zatumout/zipoop_32k1885_010) — the older `pooper-dna-v3` encoding, 128.4 GiB, read by 0.1.x as well.
|
|
29
|
+
|
|
30
|
+
Both decode to the same signals; v4 only changes how the arrays are stored (byte-shuffled members, LZMA, and a float16 sparse FFT with one float32 scale per track).
|
|
23
31
|
|
|
24
32
|
## Data layout
|
|
25
33
|
|
|
26
|
-
`<root>/<chrom>.npz` (25 files) plus `MANIFEST.tsv` (chrom, windows, members, bytes, sha256). Each file is a
|
|
34
|
+
`<root>/<chrom>.npz` (25 files) plus `MANIFEST.tsv` (chrom, windows, members, bytes, sha256). Each file is a zip with members `<window>/<key>.npy` (161 arrays per window in v3, 164 in v4), windows in genome order. Window names are `<chrom>_<start>_<end>`.
|
|
27
35
|
|
|
28
36
|
## Usage
|
|
29
37
|
|
|
@@ -42,9 +50,16 @@ Lower level: `Dataset.read(window)` gives the parsed `Shard`, `Decoder(shard).de
|
|
|
42
50
|
|
|
43
51
|
Rules baked into `load_window` (see `zipoop/teacher.py`): RNA-seq tracks are 0 and valid where unrecorded; other tracks stay missing there. Opening a chromosome file parses its whole zip directory (chr1 about 3.6 s and 0.9 GB), so keep the `Dataset` open; forked DataLoader workers reopen their own handles.
|
|
44
52
|
|
|
45
|
-
|
|
53
|
+
## Supported formats
|
|
54
|
+
|
|
55
|
+
| archive | `format` | `hybrid_format` | storage |
|
|
56
|
+
|---|---|---|---|
|
|
57
|
+
| v3 | `pooper-dna-v3` | `fft-plus-dna-residual-v1` | deflate, `fft_coefficients` complex64 |
|
|
58
|
+
| v4 | `pooper-dna-v4` | `fft-plus-dna-residual-v2` | LZMA, byte-shuffled floats, `fft_packed` float16 + `fft_scale` float32 |
|
|
59
|
+
|
|
60
|
+
`read_shard` accepts both and rejects anything else with a clear error. The v4 decode path is verified against v3: parameters, masks and calibrations are bit-identical, the FFT payload differs only by float16 rounding (max relative error about 4.7e-4), and decoded windows reach a per-track Pearson of at least 0.9999997 against the v3 decode.
|
|
46
61
|
|
|
47
|
-
Tests: `ZIPOOP_DATA=<weights_npz> pytest tests` (the pooper-equivalence test also needs the original shards and the pooper package).
|
|
62
|
+
Tests: `ZIPOOP_DATA=<weights_npz> pytest tests` (the pooper-equivalence test also needs the original shards and the pooper package). The v4 storage encoding has dataset-free unit tests in `tests/test_v4_format.py`.
|
|
48
63
|
|
|
49
64
|
## License and data terms
|
|
50
65
|
|
zipoop-0.1.0/README.md
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# zipoop
|
|
2
|
-
|
|
3
|
-
Decoder and reader for the Zipoop teacher shards (dataset: https://huggingface.co/datasets/zatumout/zipoop-teacher-shards): 89,438 windows of 32 kb, 1,885 ENCODE tracks, stored as one `<chrom>.npz` per chromosome (`weights_npz`, with `MANIFEST.tsv`).
|
|
4
|
-
|
|
5
|
-
## Data layout
|
|
6
|
-
|
|
7
|
-
`<root>/<chrom>.npz` (25 files) plus `MANIFEST.tsv` (chrom, windows, members, bytes, sha256). Each file is a deflate zip with members `<window>/<key>.npy` (161 arrays per window), windows in genome order. Window names are `<chrom>_<start>_<end>`.
|
|
8
|
-
|
|
9
|
-
## Usage
|
|
10
|
-
|
|
11
|
-
```python
|
|
12
|
-
from zipoop import Dataset
|
|
13
|
-
with Dataset("/path/to/weights_npz") as ds:
|
|
14
|
-
w = ds.load_window(ds.windows("chr21")[0]) # TeacherWindow: key, dna (L,), signal (L, T), valid (L, T)
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
```
|
|
18
|
-
zipoop verify /path/to/weights_npz # size + sha256 against MANIFEST.tsv
|
|
19
|
-
zipoop show /path/to/weights_npz chr21_10027008_10059776
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
Lower level: `Dataset.read(window)` gives the parsed `Shard`, `Decoder(shard).decode(start, end, tracks)` decodes any slice or track subset. Evaluation helpers (`window_pearson`, `summarize`, `calibrate`, `restore_real`) are in `zipoop.evaluation`.
|
|
23
|
-
|
|
24
|
-
Rules baked into `load_window` (see `zipoop/teacher.py`): RNA-seq tracks are 0 and valid where unrecorded; other tracks stay missing there. Opening a chromosome file parses its whole zip directory (chr1 about 3.6 s and 0.9 GB), so keep the `Dataset` open; forked DataLoader workers reopen their own handles.
|
|
25
|
-
|
|
26
|
-
Only the shard variant in this dataset is supported (hybrid `pooper-dna-v3`, max-pool, sinusoidal positions, mask `track-rle-lzma-v2`); other variants raise a clear error.
|
|
27
|
-
|
|
28
|
-
Tests: `ZIPOOP_DATA=<weights_npz> pytest tests` (the pooper-equivalence test also needs the original shards and the pooper package).
|
|
29
|
-
|
|
30
|
-
## License and data terms
|
|
31
|
-
|
|
32
|
-
The code is MIT-licensed (see `LICENSE`; keep the copyright notice "zatumout"). The shard dataset is released separately under CC BY 4.0 (credit: zatumout), commercial use and model distillation allowed. The shards and `zipoop/data/encode-panel_1885.json` are derived from public ENCODE tracks: when you use them, also cite and acknowledge the ENCODE Consortium as described at https://www.encodeproject.org/help/citing-encode.
|
zipoop-0.1.0/zipoop/shard.py
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
"""Parse and validate one teacher shard (a ``pooper-dna-v3`` hybrid archive) into a typed record."""
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
|
|
4
|
-
import json
|
|
5
|
-
import os
|
|
6
|
-
from contextlib import contextmanager
|
|
7
|
-
from dataclasses import dataclass
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
|
|
10
|
-
import numpy as np
|
|
11
|
-
|
|
12
|
-
from .masks import MASK_FORMAT, decode_mask
|
|
13
|
-
from .network import ModelConfig
|
|
14
|
-
|
|
15
|
-
SHARD_FORMAT = "pooper-dna-v3"
|
|
16
|
-
HYBRID_FORMAT = "fft-plus-dna-residual-v1"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class ShardView:
|
|
20
|
-
"""Read-only mapping over one window inside a merged per-chromosome npz (members "<window>/<key>")."""
|
|
21
|
-
|
|
22
|
-
def __init__(self, npz, key: str, members):
|
|
23
|
-
self._npz, self.key, self._members = npz, key, frozenset(members)
|
|
24
|
-
|
|
25
|
-
def __contains__(self, name):
|
|
26
|
-
return name in self._members
|
|
27
|
-
|
|
28
|
-
def __getitem__(self, name):
|
|
29
|
-
if name not in self._members:
|
|
30
|
-
raise KeyError(name)
|
|
31
|
-
return self._npz[f"{self.key}/{name}"]
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
@dataclass
|
|
35
|
-
class Shard:
|
|
36
|
-
key: str
|
|
37
|
-
shape: tuple[int, int] # (length, tracks)
|
|
38
|
-
metadata: dict
|
|
39
|
-
config: ModelConfig
|
|
40
|
-
dna: np.ndarray # (L,) uint8
|
|
41
|
-
valid: np.ndarray # (L, T) bool
|
|
42
|
-
parameters: dict[str, np.ndarray]
|
|
43
|
-
mean: np.ndarray # per-track target scaling and calibration, (T,)
|
|
44
|
-
power: np.ndarray
|
|
45
|
-
gain: np.ndarray
|
|
46
|
-
offset: np.ndarray
|
|
47
|
-
fft_indices: np.ndarray # (T, K) retained rfft bins
|
|
48
|
-
fft_coefficients: np.ndarray # (T, K) complex64
|
|
49
|
-
final_gain: np.ndarray
|
|
50
|
-
final_offset: np.ndarray
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
@contextmanager
|
|
54
|
-
def _opened(source):
|
|
55
|
-
if isinstance(source, (str, os.PathLike)):
|
|
56
|
-
with np.load(source, allow_pickle=False) as z:
|
|
57
|
-
yield z, Path(source).stem
|
|
58
|
-
else:
|
|
59
|
-
yield source, source.key
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def read_shard(source) -> Shard:
|
|
63
|
-
"""``source``: a shard path, or a ShardView from :class:`zipoop.Dataset`."""
|
|
64
|
-
with _opened(source) as (z, key):
|
|
65
|
-
if "format" not in z or str(z["format"]) != SHARD_FORMAT:
|
|
66
|
-
raise ValueError(f"Unsupported shard format: {z['format'] if 'format' in z else None}")
|
|
67
|
-
if "hybrid_format" not in z or str(z["hybrid_format"]) != HYBRID_FORMAT:
|
|
68
|
-
raise ValueError("Unsupported hybrid archive")
|
|
69
|
-
if "valid_encoding" not in z or str(z["valid_encoding"]) != MASK_FORMAT:
|
|
70
|
-
raise ValueError("Unsupported mask encoding")
|
|
71
|
-
shape = tuple(int(n) for n in z["shape"])
|
|
72
|
-
if len(shape) != 2:
|
|
73
|
-
raise ValueError("Invalid shape")
|
|
74
|
-
length, tracks = shape
|
|
75
|
-
dna = z["dna"].copy()
|
|
76
|
-
if dna.shape != (length,) or np.any(dna > 4):
|
|
77
|
-
raise ValueError("Invalid DNA data in archive")
|
|
78
|
-
names = json.loads(z["parameter_names"].tobytes())
|
|
79
|
-
if len(names) != len(set(names)):
|
|
80
|
-
raise ValueError("Duplicate parameter names")
|
|
81
|
-
shard = Shard(
|
|
82
|
-
key=key, shape=shape, metadata=json.loads(z["metadata"].tobytes()),
|
|
83
|
-
config=ModelConfig.from_json(json.loads(z["model_config"].tobytes())),
|
|
84
|
-
dna=dna, valid=decode_mask(z["valid_v2"].tobytes(), shape),
|
|
85
|
-
parameters={name: z[f"parameter_{i}"].copy() for i, name in enumerate(names)},
|
|
86
|
-
**{name: z[name].copy() for name in ("mean", "power", "gain", "offset")},
|
|
87
|
-
fft_indices=z["fft_indices"].copy(), fft_coefficients=z["fft_coefficients"].copy(),
|
|
88
|
-
final_gain=z["final_gain"].copy(), final_offset=z["final_offset"].copy())
|
|
89
|
-
_validate(shard)
|
|
90
|
-
return shard
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def _validate(s: Shard) -> None:
|
|
94
|
-
length, tracks = s.shape
|
|
95
|
-
if any(a.shape != (tracks,) for a in (s.mean, s.power, s.gain, s.offset)):
|
|
96
|
-
raise ValueError("Invalid track scales")
|
|
97
|
-
i, c = s.fft_indices, s.fft_coefficients
|
|
98
|
-
if (i.ndim != 2 or i.shape[0] != tracks or c.shape != i.shape or not np.issubdtype(i.dtype, np.integer)
|
|
99
|
-
or (i < 0).any() or (i > length // 2).any() or not np.isfinite(c).all()):
|
|
100
|
-
raise ValueError("Invalid FFT payload")
|
|
101
|
-
for a in (s.final_gain, s.final_offset):
|
|
102
|
-
if a.shape != (tracks,) or not np.isfinite(a).all():
|
|
103
|
-
raise ValueError("Invalid final-signal calibration")
|
|
104
|
-
if (s.final_gain < 0).any():
|
|
105
|
-
raise ValueError("Invalid final-signal calibration")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|