cytome 0.2.2__py3-none-any.whl

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.
Files changed (45) hide show
  1. cytome/__init__.py +456 -0
  2. cytome/cli/__init__.py +1 -0
  3. cytome/cli/main.py +330 -0
  4. cytome/core/__init__.py +24 -0
  5. cytome/core/dataset.py +1756 -0
  6. cytome/core/embedding.py +60 -0
  7. cytome/core/entity.py +157 -0
  8. cytome/core/fragments.py +220 -0
  9. cytome/core/graph.py +104 -0
  10. cytome/core/lazy_layer.py +23 -0
  11. cytome/core/measurement.py +205 -0
  12. cytome/core/metadata.py +181 -0
  13. cytome/core/provenance.py +125 -0
  14. cytome/dataloader/__init__.py +5 -0
  15. cytome/dataloader/pytorch.py +53 -0
  16. cytome/index/__init__.py +23 -0
  17. cytome/index/builder.py +61 -0
  18. cytome/index/rtree.py +217 -0
  19. cytome/io/__init__.py +32 -0
  20. cytome/io/chunk_tuning.py +36 -0
  21. cytome/io/chunked_io.py +658 -0
  22. cytome/io/compression.py +143 -0
  23. cytome/io/convert_anndata.py +1174 -0
  24. cytome/io/convert_cellranger.py +500 -0
  25. cytome/io/convert_fragments.py +1000 -0
  26. cytome/io/convert_mudata.py +86 -0
  27. cytome/io/convert_soma.py +15 -0
  28. cytome/io/export_bigwig.py +330 -0
  29. cytome/io/gtf_import.py +373 -0
  30. cytome/io/merge.py +540 -0
  31. cytome/io/sqlite_engine.py +560 -0
  32. cytome/io/subset.py +560 -0
  33. cytome/py.typed +0 -0
  34. cytome/utils/__init__.py +39 -0
  35. cytome/utils/genome.py +99 -0
  36. cytome/utils/modality.py +275 -0
  37. cytome/utils/regions.py +73 -0
  38. cytome/utils/validation.py +311 -0
  39. cytome/utils/versioning.py +61 -0
  40. cytome-0.2.2.dist-info/METADATA +270 -0
  41. cytome-0.2.2.dist-info/RECORD +45 -0
  42. cytome-0.2.2.dist-info/WHEEL +5 -0
  43. cytome-0.2.2.dist-info/entry_points.txt +2 -0
  44. cytome-0.2.2.dist-info/licenses/LICENSE +29 -0
  45. cytome-0.2.2.dist-info/top_level.txt +1 -0
cytome/__init__.py ADDED
@@ -0,0 +1,456 @@
1
+ """Top-level Cytome API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from cytome.core.dataset import CytomeDataset
8
+
9
+
10
+ __all__ = [
11
+ "CytomeDataset",
12
+ "open",
13
+ "create",
14
+ "from_anndata",
15
+ "from_h5ad",
16
+ "to_anndata",
17
+ "from_mudata",
18
+ "to_mudata",
19
+ "from_cellranger",
20
+ "from_cellranger_arc",
21
+ "from_10x_h5",
22
+ "from_barcodes",
23
+ "merge",
24
+ "import_gtf",
25
+ ]
26
+ __version__ = "0.2.2"
27
+
28
+
29
+ def import_gtf(ds, gtf_path, **kwargs):
30
+ """Convenience: forward to :func:`cytome.io.gtf_import.import_gtf`."""
31
+ from cytome.io.gtf_import import import_gtf as _impl
32
+ return _impl(ds, gtf_path, **kwargs)
33
+
34
+
35
+ def open(path: str | Path) -> CytomeDataset:
36
+ """Open an existing Cytome dataset."""
37
+ return CytomeDataset(path, mode="r+")
38
+
39
+
40
+ def create(path: str | Path, force: bool = False) -> CytomeDataset:
41
+ """Create a new Cytome dataset.
42
+
43
+ ``force=False`` (default) raises :class:`FileExistsError` if ``path``
44
+ already exists; pass ``force=True`` to overwrite.
45
+ """
46
+ return CytomeDataset(path, mode="w", force=force)
47
+
48
+
49
+ def from_anndata(adata, modality: str = "RNA", output: str | Path | None = None,
50
+ force: bool = False) -> CytomeDataset:
51
+ """Create a Cytome dataset from an in-memory AnnData object.
52
+
53
+ Use this when you already have an ``AnnData`` instance (e.g. just
54
+ loaded with ``scanpy.read``, or built in-memory). For h5ad files
55
+ on disk, prefer :func:`cytome.from_h5ad` — it can stream large
56
+ files chunk-by-chunk via ``backed=True``, keeping peak RAM bounded.
57
+
58
+ Quick guide:
59
+
60
+ +-------------------------------------------+--------------------------------+
61
+ | Input | Recommended entry |
62
+ +===========================================+================================+
63
+ | ``AnnData`` object in memory | ``cytome.from_anndata(adata)`` |
64
+ +-------------------------------------------+--------------------------------+
65
+ | ``.h5ad`` path, file fits comfortably | ``cytome.from_h5ad(path)`` |
66
+ | in RAM | (default ``backed=False``) |
67
+ +-------------------------------------------+--------------------------------+
68
+ | ``.h5ad`` path, file is large | ``cytome.from_h5ad(path, |
69
+ | (>1 GB sparse, multi-million cells) | backed=True)`` — streams via |
70
+ | | h5py, bounded RAM |
71
+ +-------------------------------------------+--------------------------------+
72
+
73
+ Both functions write structurally identical cytomes, with the same
74
+ ``_anndata_*`` metadata for lossless round-trip via
75
+ :func:`cytome.to_anndata`.
76
+
77
+ Parameters
78
+ ----------
79
+ adata
80
+ In-memory AnnData. ``adata.X``, ``adata.layers``, ``adata.raw``,
81
+ ``adata.obsm`` / ``varm`` / ``obsp`` / ``varp`` / ``uns`` are all
82
+ preserved per the cytome modality conventions.
83
+ modality
84
+ ``'RNA'`` (default), ``'GA'`` (gene activity), ``'ATAC'``, or
85
+ ``'tiles'``. Routes the var-entity table via cytome's modality
86
+ registry (``cytome.utils.modality.MODALITY_REGISTRY``).
87
+ output
88
+ Output path. If ``None``, a tempfile is created.
89
+ force
90
+ If ``False`` (default) and ``output`` exists, raise
91
+ :class:`FileExistsError`; ``force=True`` overwrites.
92
+ """
93
+ from cytome.io.convert_anndata import from_anndata as _impl
94
+ return _impl(adata=adata, modality=modality, output=output, force=force)
95
+
96
+
97
+ def from_h5ad(
98
+ h5ad_path: str | Path,
99
+ output: str | Path,
100
+ modality: str = "RNA",
101
+ backed: bool = False,
102
+ chunk_size: int = 2048,
103
+ storage_chunk_size: int = 128,
104
+ compression: str = "zstd",
105
+ verbose: bool = True,
106
+ # Round 10 (2026-05-24) — per-category opt-outs + per-key skip lists.
107
+ write_raw: bool = True,
108
+ write_layers: bool = True,
109
+ write_obsm: bool = True,
110
+ write_obsp: bool = True,
111
+ write_varm: bool = True,
112
+ write_varp: bool = True,
113
+ write_uns: bool = True,
114
+ skip_layers: list[str] | None = None,
115
+ skip_obsm: list[str] | None = None,
116
+ skip_obsp: list[str] | None = None,
117
+ skip_varm: list[str] | None = None,
118
+ skip_varp: list[str] | None = None,
119
+ force: bool = False,
120
+ ) -> CytomeDataset:
121
+ """Create a Cytome dataset from a ``.h5ad`` file on disk.
122
+
123
+ Use this when the input lives on disk as an ``.h5ad`` file. For an
124
+ in-memory ``AnnData`` object, prefer :func:`cytome.from_anndata`
125
+ — it skips the disk read entirely.
126
+
127
+ Quick guide:
128
+
129
+ +-------------------------------------------+--------------------------------+
130
+ | Input | Recommended entry |
131
+ +===========================================+================================+
132
+ | ``AnnData`` object in memory | ``cytome.from_anndata(adata)`` |
133
+ +-------------------------------------------+--------------------------------+
134
+ | ``.h5ad`` path, file fits comfortably | ``cytome.from_h5ad(path)`` |
135
+ | in RAM | (default ``backed=False``) |
136
+ +-------------------------------------------+--------------------------------+
137
+ | ``.h5ad`` path, file is large | ``cytome.from_h5ad(path, |
138
+ | (>1 GB sparse, multi-million cells) | backed=True)`` — streams via |
139
+ | | h5py, bounded RAM |
140
+ +-------------------------------------------+--------------------------------+
141
+
142
+ Parameters
143
+ ----------
144
+ h5ad_path
145
+ Path to the ``.h5ad`` file.
146
+ output
147
+ Output ``.cytome`` path.
148
+ modality
149
+ ``'RNA'`` (default), ``'GA'``, ``'ATAC'``, or ``'tiles'``.
150
+ Routes the var-entity table via cytome's modality registry.
151
+ backed
152
+ If ``False`` (default), the file is loaded fully into memory
153
+ as an ``AnnData`` and then converted (delegates to
154
+ :func:`from_anndata`). If ``True``, the file is streamed
155
+ chunk-by-chunk via h5py: peak RAM is bounded by ``chunk_size``
156
+ rows (sparse) or the largest dense obsm entry (whichever is
157
+ bigger). Use ``backed=True`` for files that don't comfortably
158
+ fit in RAM.
159
+
160
+ Per-category opt-outs (apply to both branches; default = preserve):
161
+ ``write_raw``, ``write_layers``, ``write_obsm``, ``write_obsp``,
162
+ ``write_varm``, ``write_varp``, ``write_uns``.
163
+
164
+ Per-key skip lists (drop named entries within a category):
165
+ ``skip_layers=[...]``, ``skip_obsm=[...]``, etc.
166
+
167
+ Notes
168
+ -----
169
+ ``backed=True`` bypasses ``anndata.read_h5ad(backed='r')``
170
+ entirely because AnnData's backed mode lazy-loads only ``.X`` and
171
+ ``.raw.X``; layers / obsm / obsp / varm / varp / uns are eagerly
172
+ loaded, defeating the purpose of "backed". cytome's
173
+ ``backed=True`` path reads directly from h5py instead, keeping
174
+ peak RAM bounded chunk-by-chunk.
175
+ """
176
+ from cytome.io.convert_anndata import from_h5ad as _impl
177
+ _common_kwargs = dict(
178
+ h5ad_path=h5ad_path,
179
+ output=output,
180
+ modality=modality,
181
+ chunk_size=chunk_size,
182
+ storage_chunk_size=storage_chunk_size,
183
+ compression=compression,
184
+ verbose=verbose,
185
+ write_raw=write_raw,
186
+ write_layers=write_layers,
187
+ write_obsm=write_obsm,
188
+ write_obsp=write_obsp,
189
+ write_varm=write_varm,
190
+ write_varp=write_varp,
191
+ write_uns=write_uns,
192
+ skip_layers=skip_layers,
193
+ skip_obsm=skip_obsm,
194
+ skip_obsp=skip_obsp,
195
+ skip_varm=skip_varm,
196
+ skip_varp=skip_varp,
197
+ force=force,
198
+ )
199
+ if not backed:
200
+ return _impl(backed=False, **_common_kwargs)
201
+
202
+ # Round 12 (2026-05-27): backed mode auto-falls-back to backed=False
203
+ # when the file's encoding isn't supported by the streaming reader
204
+ # (e.g. dense /X, CSC matrix, CSR without shape attr). AnnData's own
205
+ # reader handles every encoding, so the in-memory path is a strict
206
+ # superset for correctness — at the cost of peak RAM.
207
+ import warnings as _warnings
208
+ try:
209
+ return _impl(backed=True, **_common_kwargs)
210
+ except (KeyError, NotImplementedError) as exc:
211
+ _warnings.warn(
212
+ f"cytome.from_h5ad(backed=True) failed ({type(exc).__name__}: "
213
+ f"{exc}) — falling back to backed=False (in-memory load via "
214
+ f"anndata.read_h5ad). This requires enough RAM to hold the "
215
+ f"file. Pass backed=False explicitly to silence this warning.",
216
+ RuntimeWarning,
217
+ stacklevel=2,
218
+ )
219
+ return _impl(backed=False, **_common_kwargs)
220
+
221
+
222
+ def to_anndata(
223
+ ds: CytomeDataset,
224
+ modality: str = "RNA",
225
+ layer: str | None = None,
226
+ include_embeddings: bool = True,
227
+ cell_mask=None,
228
+ ):
229
+ """Convert a Cytome dataset to AnnData (function form).
230
+
231
+ Equivalent to ``ds.to_anndata(...)`` (the method form). Modality
232
+ routes to its own var entity table: RNA → ``genes``,
233
+ GA → ``GA_genes``, ATAC → ``peaks``, tiles → ``tiles``.
234
+ """
235
+ from cytome.io.convert_anndata import to_anndata as _impl
236
+ return _impl(
237
+ ds=ds, modality=modality, layer=layer,
238
+ include_embeddings=include_embeddings, cell_mask=cell_mask,
239
+ )
240
+
241
+
242
+ def from_mudata(mdata, output: str | Path | None = None, force: bool = False):
243
+ """Create a Cytome dataset from MuData.
244
+
245
+ ``force=False`` (default) raises :class:`FileExistsError` if ``output``
246
+ exists; ``force=True`` overwrites.
247
+ """
248
+ from cytome.io.convert_mudata import from_mudata as _impl
249
+ return _impl(mdata=mdata, output=output, force=force)
250
+
251
+
252
+ def to_mudata(ds: CytomeDataset):
253
+ """Convert a Cytome dataset to MuData (function form)."""
254
+ from cytome.io.convert_mudata import to_mudata as _impl
255
+ return _impl(ds=ds)
256
+
257
+
258
+ def from_cellranger(path, output: str | Path, sample_name=None,
259
+ import_fragments: bool | None = None, build_index: bool = True,
260
+ keep_chroms: str = "standard", modalities: str = "both",
261
+ force: bool = False):
262
+ """Create a Cytome dataset from Cell Ranger / Cell Ranger ARC output folder(s).
263
+
264
+ Reads each folder's feature-barcode matrix from ``filtered_feature_bc_matrix.h5``
265
+ (preferred) or the ``filtered_feature_bc_matrix/`` MTX directory, splitting **Gene
266
+ Expression** features into the ``RNA`` modality and **Peaks** into ``ATAC``, and
267
+ imports ``atac_fragments.tsv.gz`` if present.
268
+
269
+ ``path`` may be a single folder or a **list of folders** — a list merges the
270
+ libraries into one dataset (union of genes/peaks, cells concatenated, fragments
271
+ combined). ``sample_name`` accepts a **list matching ``path``** (one per folder),
272
+ written to ``cells.sample_id``.
273
+
274
+ Examples
275
+ --------
276
+ >>> cytome.from_cellranger("run/outs", output="lib.cytome", sample_name="ctrl")
277
+ >>> cytome.from_cellranger(["A/outs", "B/outs", "C/outs"], output="merged.cytome",
278
+ ... sample_name=["ctrl", "het", "cko"])
279
+ """
280
+ from cytome.io.convert_cellranger import from_cellranger as _impl
281
+ return _impl(path=path, output=output, sample_name=sample_name,
282
+ import_fragments=import_fragments, build_index=build_index,
283
+ keep_chroms=keep_chroms, modalities=modalities, force=force)
284
+
285
+
286
+ def from_10x_h5(path: str | Path, output: str | Path, sample_name: str | None = None,
287
+ build_index: bool = True, force: bool = False):
288
+ """Create a Cytome dataset directly from a CellRanger ``.h5`` (no AnnData).
289
+
290
+ Gene Expression features → RNA ``genes`` (``gene_id`` = Ensembl id, ``symbol``
291
+ = name); Peak features (multiome) → ATAC ``peaks``. Handles CellRanger v2/v3.
292
+ ``force=False`` (default) raises if ``output`` exists; ``force=True`` overwrites.
293
+ """
294
+ from cytome.io.convert_cellranger import from_10x_h5 as _impl
295
+ return _impl(path=path, output=output, sample_name=sample_name,
296
+ build_index=build_index, force=force)
297
+
298
+
299
+ def from_cellranger_arc(
300
+ path: str | Path,
301
+ output: str | Path,
302
+ sample_name: str | None = None,
303
+ import_fragments: bool = True,
304
+ build_index: bool = True,
305
+ force: bool = False,
306
+ ):
307
+ """Create a Cytome dataset from Cell Ranger ARC output.
308
+
309
+ ``force=False`` (default) raises if ``output`` exists; ``force=True`` overwrites.
310
+ """
311
+ from cytome.io.convert_cellranger import from_cellranger_arc as _impl
312
+ return _impl(
313
+ path=path,
314
+ output=output,
315
+ sample_name=sample_name,
316
+ import_fragments=import_fragments,
317
+ build_index=build_index,
318
+ force=force,
319
+ )
320
+
321
+
322
+ def merge(
323
+ inputs,
324
+ output,
325
+ batch_key: str = "sample_id",
326
+ batch_labels=None,
327
+ gene_strategy: str = "union",
328
+ peak_strategy: str = "union",
329
+ obs_columns="all",
330
+ include_embeddings: bool = False,
331
+ include_fragments: bool = True,
332
+ include_graphs: bool = False,
333
+ chunk_memory_mb: int = 256,
334
+ force: bool = False,
335
+ **kwargs,
336
+ ):
337
+ """Merge multiple Cytome datasets into one on-disk dataset.
338
+
339
+ Cells from every input are concatenated (each input occupies a contiguous
340
+ block); genes and peaks are reconciled by ``gene_strategy`` / ``peak_strategy``;
341
+ and — if present and requested — compressed fragments are remapped onto the
342
+ merged cell axis and streamed into the output's ``fragment_chunks`` store. This
343
+ is the standard way to combine multiple libraries / samples into one atlas.
344
+
345
+ Parameters
346
+ ----------
347
+ inputs : sequence of (str | Path | CytomeDataset)
348
+ Datasets to merge, in order. Paths are opened read-only and closed
349
+ afterwards; already-open ``CytomeDataset`` objects are left open. The
350
+ concatenation order defines the cell ordering in the output.
351
+ output : str | Path
352
+ Output ``.cytome`` path. By default (``force=False``) merge raises
353
+ :class:`FileExistsError` if it already exists; pass ``force=True`` to
354
+ overwrite.
355
+ batch_key : str, default ``"sample_id"``
356
+ Name of the ``cells`` column written to record each cell's source input.
357
+ batch_labels : sequence of str, optional
358
+ One label per input, written into ``cells[batch_key]``. Defaults to each
359
+ input's file stem (``Path(ds.path).stem``).
360
+ gene_strategy : {"union", "intersection"}, default ``"union"``
361
+ How to reconcile gene sets. ``"union"`` keeps every gene seen in any input
362
+ (genes missing from an input become explicit zeros for its cells —
363
+ atlas-friendly); ``"intersection"`` keeps only genes present in *all* inputs.
364
+ peak_strategy : {"union", "intersection"}, default ``"union"``
365
+ Same as ``gene_strategy`` but for ATAC peaks.
366
+ obs_columns : {"all", "shared"} | list of str, default ``"all"``
367
+ Which ``cells`` columns to keep. ``"all"`` keeps every column seen in any
368
+ input (NaN where absent); ``"shared"`` keeps only columns common to all
369
+ inputs; a list keeps exactly those columns (silently ignoring missing ones).
370
+ include_embeddings : bool, default ``False``
371
+ Reserved — per-input embeddings are not transferred (recompute on the merge).
372
+ include_fragments : bool, default ``True``
373
+ Remap and stream each input's fragments into the merged ``fragment_chunks``.
374
+ Set ``False`` to produce a counts-only merge (faster, smaller).
375
+ include_graphs : bool, default ``False``
376
+ Reserved — neighbor graphs are not transferred (recompute on the merge).
377
+ chunk_memory_mb : int, default ``256``
378
+ Reserved tuning knob for the fragment-streaming chunk size.
379
+ **kwargs
380
+ Forwarded verbatim to :func:`cytome.io.merge.merge` for forward
381
+ compatibility with newer options.
382
+
383
+ Returns
384
+ -------
385
+ CytomeDataset
386
+ The merged dataset (open).
387
+
388
+ Examples
389
+ --------
390
+ >>> # union of three libraries, fragments combined, labelled by sample
391
+ >>> ds = cytome.merge(
392
+ ... ["ctrl.cytome", "het.cytome", "cko.cytome"],
393
+ ... output="atlas.cytome",
394
+ ... batch_labels=["ctrl", "het", "cko"],
395
+ ... )
396
+ >>> # counts-only intersection merge (no fragments)
397
+ >>> ds = cytome.merge(parts, "merged.cytome",
398
+ ... gene_strategy="intersection", include_fragments=False)
399
+ """
400
+ from cytome.io.merge import merge as _impl
401
+ return _impl(
402
+ inputs=inputs,
403
+ output=output,
404
+ batch_key=batch_key,
405
+ batch_labels=batch_labels,
406
+ gene_strategy=gene_strategy,
407
+ peak_strategy=peak_strategy,
408
+ obs_columns=obs_columns,
409
+ include_embeddings=include_embeddings,
410
+ include_fragments=include_fragments,
411
+ include_graphs=include_graphs,
412
+ chunk_memory_mb=chunk_memory_mb,
413
+ force=force,
414
+ **kwargs,
415
+ )
416
+
417
+
418
+ def from_barcodes(
419
+ barcodes,
420
+ output: str | Path,
421
+ sample_id: str | None = None,
422
+ force: bool = False,
423
+ ) -> CytomeDataset:
424
+ """Create a minimal Cytome dataset from barcodes.
425
+
426
+ Parameters
427
+ ----------
428
+ barcodes
429
+ List of barcode strings, file path (one per line/TSV), or DataFrame.
430
+ output
431
+ Output .cytome file path.
432
+ sample_id
433
+ Optional sample identifier for cells table.
434
+ force
435
+ If ``False`` (default) and ``output`` exists, raise
436
+ :class:`FileExistsError`; ``force=True`` overwrites.
437
+ """
438
+ import pandas as pd
439
+
440
+ if isinstance(barcodes, (str, Path)):
441
+ bc_df = pd.read_csv(
442
+ barcodes, sep="\t", header=None, comment="#", dtype=str,
443
+ )
444
+ bc_df = bc_df.iloc[:, 0].to_frame("barcode").drop_duplicates()
445
+ elif isinstance(barcodes, pd.DataFrame):
446
+ bc_df = barcodes
447
+ else:
448
+ bc_df = pd.DataFrame({"barcode": list(barcodes)})
449
+
450
+ if sample_id:
451
+ bc_df["sample_id"] = sample_id
452
+
453
+ ds = create(output, force=force)
454
+ ds.set_entity("cells", bc_df)
455
+ ds.flush()
456
+ return ds
cytome/cli/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """CLI package."""