cellucid 0.0.1a0__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.
cellucid/__init__.py ADDED
@@ -0,0 +1,12 @@
1
+ """Utilities for exporting AnnData/array data for the 3D UMAP web viewer."""
2
+
3
+ from importlib import metadata as _metadata
4
+
5
+ try:
6
+ __version__ = _metadata.version("cellucid")
7
+ except _metadata.PackageNotFoundError: # pragma: no cover - handled in dev installs
8
+ __version__ = "0.0.0"
9
+
10
+ from .prepare_data import export_data_for_web
11
+
12
+ __all__ = ["export_data_for_web", "__version__"]
@@ -0,0 +1,149 @@
1
+ import gc
2
+ import pandas as pd
3
+ import itertools
4
+ from pathlib import Path
5
+ from tqdm import tqdm
6
+
7
+ try:
8
+ import scanpy as sc
9
+ except ImportError as exc: # pragma: no cover - optional dependency
10
+ raise ImportError(
11
+ "scanpy is required for cellucid.anndata_variations; install with "
12
+ "`pip install cellucid[analysis]` or add scanpy to your environment."
13
+ ) from exc
14
+
15
+ def _find_repo_root() -> Path:
16
+ """Locate the python project root (contains pyproject.toml and data/)."""
17
+ for candidate in Path(__file__).resolve().parents:
18
+ if (candidate / "pyproject.toml").exists() and (candidate / "data").exists():
19
+ return candidate
20
+ return Path.cwd()
21
+
22
+
23
+ REPO_ROOT = _find_repo_root()
24
+ RAW_DATA_DIR = REPO_ROOT / "data" / "raw"
25
+ EXPERIMENT_DIR = REPO_ROOT / "data" / "experiments"
26
+
27
+ INPUT_H5AD = RAW_DATA_DIR / "adata_latent_scvi-adata_unified_20250925_001_integration-supergpu02.scidom.de-bb886d36409e78a3.h5ad"
28
+ METADATA_PICKLE = RAW_DATA_DIR / "adata_obs_extracted.pickle"
29
+ OUTPUT_DIR = EXPERIMENT_DIR / "umap_parameter_sweep"
30
+
31
+ MIN_DISTS = [0.1, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45, 0.50]
32
+ N_NEIGHBORS_LIST = [5, 10, 15, 30, 50, 90, 120]
33
+ RANDOM_SEED = 42
34
+
35
+
36
+ def _print_setup():
37
+ """Log resolved paths so it's obvious what is being used."""
38
+ print("==== AnnData UMAP sweep setup ====")
39
+ print(f"Current working dir : {Path.cwd()}")
40
+ print(f"Repo root : {REPO_ROOT}")
41
+ print(f"Input h5ad : {INPUT_H5AD} (exists: {INPUT_H5AD.exists()})")
42
+ print(f"Metadata pickle : {METADATA_PICKLE} (exists: {METADATA_PICKLE.exists()})")
43
+ print(f"Output directory : {OUTPUT_DIR}")
44
+ print("==================================")
45
+
46
+
47
+ def validate_inputs() -> None:
48
+ """Ensure required files and folders exist before heavy work."""
49
+ missing = [path for path in (INPUT_H5AD, METADATA_PICKLE) if not path.exists()]
50
+ if missing:
51
+ missing_list = "\n - ".join(str(path) for path in missing)
52
+ raise FileNotFoundError(f"Missing required input files:\n - {missing_list}")
53
+
54
+ EXPERIMENT_DIR.mkdir(parents=True, exist_ok=True)
55
+ OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
56
+
57
+
58
+
59
+ def load_and_preprocess_data():
60
+ validate_inputs()
61
+ print("Loading and preparing data (this happens only once)...")
62
+ adata = sc.read_h5ad(INPUT_H5AD)
63
+ adata_complete = pd.read_pickle(METADATA_PICKLE)
64
+
65
+ if "qc_fail" not in adata_complete.columns:
66
+ raise KeyError("Metadata pickle missing required column 'qc_fail'.")
67
+
68
+ missing_cells = set(adata.obs_names) - set(adata_complete.index)
69
+ if missing_cells:
70
+ raise ValueError(f"Metadata missing {len(missing_cells)} cells (e.g., {next(iter(missing_cells))}).")
71
+
72
+ adata_complete = adata_complete.loc[adata.obs_names]
73
+ qc_mask = ~adata_complete["qc_fail"].fillna(False)
74
+
75
+ # Apply QC filtering to both the matrix and obs
76
+ adata = adata[qc_mask].copy()
77
+ adata_complete = adata_complete.loc[qc_mask]
78
+ adata.obs = adata_complete
79
+
80
+ # Drop heavy derived fields to reduce per-run memory
81
+ for container in (adata.obsm, adata.obsp):
82
+ for key in list(container.keys()):
83
+ del container[key]
84
+ adata.uns.clear()
85
+
86
+ print(f"Data prepared. Final: {adata}")
87
+ print(f"Cells kept after QC : {adata.n_obs}")
88
+ print(f"Features : {adata.n_vars}")
89
+ return adata
90
+
91
+
92
+ def run_parameter_sweep(_adata):
93
+ """
94
+ Iterates through parameters, calculating UMAPs and saving results.
95
+ """
96
+ OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
97
+ param_combinations = list(itertools.product(N_NEIGHBORS_LIST, MIN_DISTS))
98
+ print(f"Starting sweep over {len(param_combinations)} combinations...")
99
+ print(f"Writing outputs to: {OUTPUT_DIR}")
100
+
101
+ existing_before = 0
102
+ for k, min_dist in param_combinations:
103
+ dist_str = f"{min_dist:.2f}".replace('.', '_')
104
+ filename = OUTPUT_DIR / f"adata|k__{k}|min_dist_{dist_str}.h5ad"
105
+ if filename.exists():
106
+ existing_before += 1
107
+ to_compute = len(param_combinations) - existing_before
108
+ print(f"Found {existing_before} outputs already present; {to_compute} will be computed.")
109
+
110
+ if to_compute == 0:
111
+ print("All combinations already exist. Nothing to do.")
112
+ return
113
+
114
+ skipped = 0
115
+ created = 0
116
+
117
+ for k, min_dist in tqdm(param_combinations, desc="Processing"):
118
+ dist_str = f"{min_dist:.2f}".replace('.', '_')
119
+ filename = OUTPUT_DIR / f"adata|k__{k}|min_dist_{dist_str}.h5ad"
120
+ if filename.exists():
121
+ skipped += 1
122
+ tqdm.write(f"Exists, skipping: {filename.name}")
123
+ continue
124
+
125
+ adata = _adata.copy()
126
+ try:
127
+ sc.pp.neighbors(adata, n_neighbors=k, random_state=RANDOM_SEED)
128
+ sc.tl.umap(
129
+ adata,
130
+ n_components=3,
131
+ min_dist=min_dist,
132
+ random_state=RANDOM_SEED,
133
+ )
134
+ adata.write_h5ad(filename)
135
+ created += 1
136
+ except Exception as e:
137
+ tqdm.write(f"Calculation error for {filename.name}: {e}")
138
+ gc.collect()
139
+
140
+ print(f"Finished sweep. Created {created}, skipped {skipped}, total {len(param_combinations)}.")
141
+
142
+ if __name__ == "__main__":
143
+ try:
144
+ _print_setup()
145
+ clean_adata = load_and_preprocess_data()
146
+ run_parameter_sweep(clean_adata)
147
+ print("Processing complete.")
148
+ except Exception as e:
149
+ print(f"An error occurred: {e}")