citegeist 0.1.0__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.
citegeist/__init__.py ADDED
File without changes
@@ -0,0 +1,21 @@
1
+ # Expose key classes and functions for easy access
2
+
3
+ from .citegeist_model import CitegeistModel
4
+ from .gurobi_impl import optimize_cell_proportions, optimize_gene_expression, map_antibodies_to_profiles
5
+ from .utils import (
6
+ save_results_to_output,
7
+ validate_cell_profile_dict,
8
+ cleanup_memory,
9
+ setup_logging
10
+ )
11
+
12
+ __all__ = [
13
+ "CitegeistModel",
14
+ "optimize_cell_proportions",
15
+ "optimize_gene_expression",
16
+ "map_antibodies_to_profiles",
17
+ "save_results_to_output",
18
+ "validate_cell_profile_dict",
19
+ "cleanup_memory",
20
+ "setup_logging"
21
+ ]
@@ -0,0 +1,42 @@
1
+
2
+ import scanpy as sc
3
+ import anndata as ad
4
+
5
+
6
+ def filter_spots_by_celltype_proportion(adata, celltype, proportion_threshold):
7
+ """
8
+ Filter spots by celltype proportion based on if their .obs[celltype] is greater than the proportion_threshold.
9
+ """
10
+ return adata[adata.obs[celltype] > proportion_threshold].copy()
11
+
12
+
13
+ def expand_prop_gex_adata(prop_gex_adata, celltype_cutoff=0.2, celltype_profile_dict=None):
14
+ """
15
+ Expands celltype level spots to have multiple spots for each celltype, but retain spatial information
16
+ for COMMMOT analysis.
17
+ """
18
+
19
+ if celltype_profile_dict is None:
20
+ raise ValueError("celltype_profile_dict is required for this function.")
21
+ celltypes = celltype_profile_dict.keys()
22
+
23
+ all_adatas = []
24
+
25
+ for celltype in celltypes:
26
+ filtered_adata = filter_spots_by_celltype_proportion(prop_gex_adata, celltype, celltype_cutoff)
27
+ celltype_label = celltype.replace(" ", "_")
28
+ layer_name = f"{celltype_label}_genes_pass1"
29
+ filtered_adata.X = filtered_adata.layers[layer_name]
30
+ filtered_adata.obs['celltype'] = celltype
31
+ all_adatas.append(filtered_adata)
32
+ # Extract the corresponding layer for the filtered_adata, including the spatial information
33
+ combined_adata = ad.concat(all_adatas, join="outer", keys=celltypes, index_unique = "_", merge="same", uns_merge = "first")
34
+ return combined_adata
35
+
36
+ def get_celltype_expression_data(adata, celltype):
37
+ """
38
+ Get expression data for a specific celltype.
39
+ """
40
+ return adata[adata.obs['celltype'] == celltype].copy()
41
+
42
+
@@ -0,0 +1,174 @@
1
+ import os
2
+ import logging
3
+ import numpy as np
4
+ from pathlib import Path
5
+
6
+ class CheckpointManager:
7
+ """Manages loading and saving of optimization checkpoints."""
8
+
9
+ def __init__(self, output_dir, sample_name):
10
+ """
11
+ Initialize checkpoint manager.
12
+
13
+ Args:
14
+ output_dir (str): Directory for checkpoint files
15
+ sample_name (str): Unique identifier for this sample
16
+ """
17
+ self.output_dir = Path(output_dir)
18
+ self.sample_name = sample_name
19
+ self.output_dir.mkdir(parents=True, exist_ok=True)
20
+
21
+ def check_complete_run(self, N, T, M):
22
+ """
23
+ Check if a complete run exists.
24
+
25
+ Args:
26
+ N (int): Number of spots
27
+ T (int): Number of cell types
28
+ M (int): Number of genes/features
29
+
30
+ Returns:
31
+ dict or None: Dictionary of spot profiles if complete run exists, else None
32
+ """
33
+ complete_file = self.output_dir / f"{self.sample_name}_gene_expression_complete.npz"
34
+
35
+ if complete_file.exists():
36
+ try:
37
+ complete_data = np.load(complete_file)
38
+ if 'profiles' in complete_data and 'completed_spots' in complete_data:
39
+ profiles = complete_data['profiles']
40
+ if profiles.shape == (N, T, M):
41
+ return {i: profiles[i] for i in range(N)}
42
+ except Exception as e:
43
+ logging.error(f"Error loading complete file: {e}")
44
+ self._cleanup_corrupted_files()
45
+ return None
46
+
47
+ def load_latest_checkpoint(self, N, T, M):
48
+ """
49
+ Load the latest valid checkpoint.
50
+
51
+ Args:
52
+ N (int): Number of spots
53
+ T (int): Number of cell types
54
+ M (int): Number of genes/features
55
+
56
+ Returns:
57
+ tuple: (completed_spots set, spotwise_profiles dict)
58
+ """
59
+ checkpoints = list(self.output_dir.glob(
60
+ f"{self.sample_name}_gene_expression_checkpoint_*.npz"
61
+ ))
62
+
63
+ if not checkpoints:
64
+ return set(), {}
65
+
66
+ checkpoint_numbers = [
67
+ int(f.stem.split('_')[-1])
68
+ for f in checkpoints
69
+ ]
70
+ latest_number = max(checkpoint_numbers)
71
+ latest_checkpoint = self.output_dir / f"{self.sample_name}_gene_expression_checkpoint_{latest_number}.npz"
72
+
73
+ try:
74
+ checkpoint_data = np.load(latest_checkpoint)
75
+ if 'profiles' in checkpoint_data and 'completed_spots' in checkpoint_data:
76
+ profiles = checkpoint_data['profiles']
77
+ completed_spots = set(checkpoint_data['completed_spots'].tolist())
78
+
79
+ if profiles.shape == (N, T, M):
80
+ spotwise_profiles = {
81
+ i: profiles[i]
82
+ for i in completed_spots
83
+ if not np.any(np.isnan(profiles[i]))
84
+ }
85
+ completed_spots = set(spotwise_profiles.keys())
86
+ logging.info(f"Loaded {len(completed_spots)} valid profiles from checkpoint")
87
+ return completed_spots, spotwise_profiles
88
+
89
+ except Exception as e:
90
+ logging.error(f"Error loading checkpoint: {e}")
91
+ self._cleanup_corrupted_files()
92
+
93
+ return set(), {}
94
+
95
+ def save_checkpoint(self, completed_spots, spotwise_profiles, N, T, M):
96
+ """
97
+ Save current progress as checkpoint.
98
+
99
+ Args:
100
+ completed_spots (set): Set of completed spot indices
101
+ spotwise_profiles (dict): Dictionary of spot profiles
102
+ N (int): Number of spots
103
+ T (int): Number of cell types
104
+ M (int): Number of genes/features
105
+ """
106
+ try:
107
+ n_completed = len(completed_spots)
108
+ checkpoint_path = self.output_dir / f"{self.sample_name}_gene_expression_checkpoint_{n_completed}.npz"
109
+
110
+ # Convert dictionary to numpy array
111
+ max_spot = max(spotwise_profiles.keys())
112
+ profiles_array = np.full((max_spot + 1, T, M), np.nan)
113
+
114
+ for spot_idx, profile in spotwise_profiles.items():
115
+ profiles_array[spot_idx] = profile
116
+
117
+ # Save checkpoint
118
+ np.savez_compressed(
119
+ checkpoint_path,
120
+ profiles=profiles_array,
121
+ completed_spots=np.array(list(completed_spots)),
122
+ n_completed=n_completed
123
+ )
124
+
125
+ # Cleanup old checkpoints
126
+ self._cleanup_old_checkpoints(checkpoint_path)
127
+
128
+ logging.info(f"Saved checkpoint after {n_completed} completed spots")
129
+
130
+ except Exception as e:
131
+ logging.error(f"Failed to save checkpoint: {e}")
132
+
133
+ def save_final_results(self, spotwise_profiles, completed_spots, N, T, M):
134
+ """
135
+ Save final results.
136
+
137
+ Args:
138
+ spotwise_profiles (dict): Dictionary of spot profiles
139
+ completed_spots (set): Set of completed spot indices
140
+ N (int): Number of spots
141
+ T (int): Number of cell types
142
+ M (int): Number of genes/features
143
+ """
144
+ final_path = self.output_dir / f"{self.sample_name}_gene_expression_complete.npz"
145
+ max_spot = max(spotwise_profiles.keys())
146
+ final_profiles = np.full((max_spot + 1, T, M), np.nan)
147
+
148
+ for spot_idx, profile in spotwise_profiles.items():
149
+ final_profiles[spot_idx] = profile
150
+
151
+ np.savez_compressed(
152
+ final_path,
153
+ profiles=final_profiles,
154
+ completed_spots=np.array(list(completed_spots))
155
+ )
156
+ logging.info(f"Saved final results with {len(completed_spots)} completed spots")
157
+
158
+ def _cleanup_corrupted_files(self):
159
+ """Remove all checkpoint files if corruption is detected."""
160
+ for file in self.output_dir.glob(f"{self.sample_name}_gene_expression*.npz"):
161
+ try:
162
+ file.unlink()
163
+ logging.info(f"Deleted corrupted checkpoint: {file}")
164
+ except Exception as e:
165
+ logging.warning(f"Failed to delete {file}: {e}")
166
+
167
+ def _cleanup_old_checkpoints(self, current_checkpoint):
168
+ """Remove old checkpoints, keeping only the latest."""
169
+ for checkpoint in self.output_dir.glob(f"{self.sample_name}_gene_expression_checkpoint_*.npz"):
170
+ if checkpoint != current_checkpoint:
171
+ try:
172
+ checkpoint.unlink()
173
+ except Exception as e:
174
+ logging.warning(f"Failed to delete old checkpoint {checkpoint}: {e}")