cellarr 0.2.2__tar.gz → 0.2.3__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.
Files changed (54) hide show
  1. {cellarr-0.2.2 → cellarr-0.2.3}/PKG-INFO +27 -1
  2. {cellarr-0.2.2 → cellarr-0.2.3}/README.md +26 -0
  3. {cellarr-0.2.2 → cellarr-0.2.3}/docs/tutorial.md +27 -0
  4. cellarr-0.2.3/src/cellarr/autoencoder.py +444 -0
  5. {cellarr-0.2.2 → cellarr-0.2.3}/src/cellarr/build_cellarrdataset.py +15 -8
  6. {cellarr-0.2.2 → cellarr-0.2.3}/src/cellarr.egg-info/PKG-INFO +27 -1
  7. {cellarr-0.2.2 → cellarr-0.2.3}/src/cellarr.egg-info/SOURCES.txt +1 -0
  8. {cellarr-0.2.2 → cellarr-0.2.3}/tests/test_dataloader.py +54 -0
  9. {cellarr-0.2.2 → cellarr-0.2.3}/.coveragerc +0 -0
  10. {cellarr-0.2.2 → cellarr-0.2.3}/.github/workflows/pypi-publish.yml +0 -0
  11. {cellarr-0.2.2 → cellarr-0.2.3}/.github/workflows/pypi-test.yml +0 -0
  12. {cellarr-0.2.2 → cellarr-0.2.3}/.gitignore +0 -0
  13. {cellarr-0.2.2 → cellarr-0.2.3}/.pre-commit-config.yaml +0 -0
  14. {cellarr-0.2.2 → cellarr-0.2.3}/.readthedocs.yml +0 -0
  15. {cellarr-0.2.2 → cellarr-0.2.3}/AUTHORS.md +0 -0
  16. {cellarr-0.2.2 → cellarr-0.2.3}/CHANGELOG.md +0 -0
  17. {cellarr-0.2.2 → cellarr-0.2.3}/CONTRIBUTING.md +0 -0
  18. {cellarr-0.2.2 → cellarr-0.2.3}/LICENSE.txt +0 -0
  19. {cellarr-0.2.2 → cellarr-0.2.3}/assets/cellarr.jpg +0 -0
  20. {cellarr-0.2.2 → cellarr-0.2.3}/assets/cellarr.png +0 -0
  21. {cellarr-0.2.2 → cellarr-0.2.3}/assets/cellarr.svg +0 -0
  22. {cellarr-0.2.2 → cellarr-0.2.3}/docs/Makefile +0 -0
  23. {cellarr-0.2.2 → cellarr-0.2.3}/docs/_static/.gitignore +0 -0
  24. {cellarr-0.2.2 → cellarr-0.2.3}/docs/authors.md +0 -0
  25. {cellarr-0.2.2 → cellarr-0.2.3}/docs/changelog.md +0 -0
  26. {cellarr-0.2.2 → cellarr-0.2.3}/docs/conf.py +0 -0
  27. {cellarr-0.2.2 → cellarr-0.2.3}/docs/contributing.md +0 -0
  28. {cellarr-0.2.2 → cellarr-0.2.3}/docs/index.md +0 -0
  29. {cellarr-0.2.2 → cellarr-0.2.3}/docs/license.md +0 -0
  30. {cellarr-0.2.2 → cellarr-0.2.3}/docs/notes.md +0 -0
  31. {cellarr-0.2.2 → cellarr-0.2.3}/docs/readme.md +0 -0
  32. {cellarr-0.2.2 → cellarr-0.2.3}/docs/requirements.txt +0 -0
  33. {cellarr-0.2.2 → cellarr-0.2.3}/pyproject.toml +0 -0
  34. {cellarr-0.2.2 → cellarr-0.2.3}/setup.cfg +0 -0
  35. {cellarr-0.2.2 → cellarr-0.2.3}/setup.py +0 -0
  36. {cellarr-0.2.2 → cellarr-0.2.3}/src/cellarr/CellArrDataset.py +0 -0
  37. {cellarr-0.2.2 → cellarr-0.2.3}/src/cellarr/CellArrDatasetSlice.py +0 -0
  38. {cellarr-0.2.2 → cellarr-0.2.3}/src/cellarr/__init__.py +0 -0
  39. {cellarr-0.2.2 → cellarr-0.2.3}/src/cellarr/build_options.py +0 -0
  40. {cellarr-0.2.2 → cellarr-0.2.3}/src/cellarr/buildutils_tiledb_array.py +0 -0
  41. {cellarr-0.2.2 → cellarr-0.2.3}/src/cellarr/buildutils_tiledb_frame.py +0 -0
  42. {cellarr-0.2.2 → cellarr-0.2.3}/src/cellarr/dataloader.py +0 -0
  43. {cellarr-0.2.2 → cellarr-0.2.3}/src/cellarr/queryutils_tiledb_frame.py +0 -0
  44. {cellarr-0.2.2 → cellarr-0.2.3}/src/cellarr/utils_anndata.py +0 -0
  45. {cellarr-0.2.2 → cellarr-0.2.3}/src/cellarr.egg-info/dependency_links.txt +0 -0
  46. {cellarr-0.2.2 → cellarr-0.2.3}/src/cellarr.egg-info/not-zip-safe +0 -0
  47. {cellarr-0.2.2 → cellarr-0.2.3}/src/cellarr.egg-info/requires.txt +0 -0
  48. {cellarr-0.2.2 → cellarr-0.2.3}/src/cellarr.egg-info/top_level.txt +0 -0
  49. {cellarr-0.2.2 → cellarr-0.2.3}/tests/conftest.py +0 -0
  50. {cellarr-0.2.2 → cellarr-0.2.3}/tests/data/adata.h5ad +0 -0
  51. {cellarr-0.2.2 → cellarr-0.2.3}/tests/test_anndata_utils.py +0 -0
  52. {cellarr-0.2.2 → cellarr-0.2.3}/tests/test_build.py +0 -0
  53. {cellarr-0.2.2 → cellarr-0.2.3}/tests/test_query.py +0 -0
  54. {cellarr-0.2.2 → cellarr-0.2.3}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cellarr
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: TileDB-based array storage for genomics data collections.
5
5
  Home-page: https://github.com/BiocPy/cellarr
6
6
  Author: Jayaram Kancherla
@@ -165,6 +165,32 @@ datamodule = DataModule(
165
165
  )
166
166
  ```
167
167
 
168
+ The package also includes a simple autoencoder in the `pytorch-lightning` which makes use of the dataloader. This can be used as a template to create custom architectures and models.
169
+
170
+ ```python
171
+ import pytorch_lightning as pl
172
+ from cellarr.autoencoder import AutoEncoder
173
+
174
+ autoencoder = AutoEncoder(
175
+ n_genes=len(datamodule.gene_indices),
176
+ latent_dim=128,
177
+ hidden_dim=[1024, 1024, 1024],
178
+ dropout=0.5,
179
+ input_dropout=0.4,
180
+ residual=False,
181
+ )
182
+
183
+ model_path = "/path/to/model/mymodel/"
184
+ params = {
185
+ "max_epochs": 500,
186
+ "logger": True,
187
+ "log_every_n_steps": 1,
188
+ "limit_train_batches": 100, # to specify number of batches per epoch
189
+ }
190
+ trainer = pl.Trainer(**params)
191
+ trainer.fit(autoencoder, datamodule=datamodule)
192
+ autoencoder.save_all(model_path=model_path)
193
+ ```
168
194
  <!-- pyscaffold-notes -->
169
195
 
170
196
  ## Note
@@ -135,6 +135,32 @@ datamodule = DataModule(
135
135
  )
136
136
  ```
137
137
 
138
+ The package also includes a simple autoencoder in the `pytorch-lightning` which makes use of the dataloader. This can be used as a template to create custom architectures and models.
139
+
140
+ ```python
141
+ import pytorch_lightning as pl
142
+ from cellarr.autoencoder import AutoEncoder
143
+
144
+ autoencoder = AutoEncoder(
145
+ n_genes=len(datamodule.gene_indices),
146
+ latent_dim=128,
147
+ hidden_dim=[1024, 1024, 1024],
148
+ dropout=0.5,
149
+ input_dropout=0.4,
150
+ residual=False,
151
+ )
152
+
153
+ model_path = "/path/to/model/mymodel/"
154
+ params = {
155
+ "max_epochs": 500,
156
+ "logger": True,
157
+ "log_every_n_steps": 1,
158
+ "limit_train_batches": 100, # to specify number of batches per epoch
159
+ }
160
+ trainer = pl.Trainer(**params)
161
+ trainer.fit(autoencoder, datamodule=datamodule)
162
+ autoencoder.save_all(model_path=model_path)
163
+ ```
138
164
  <!-- pyscaffold-notes -->
139
165
 
140
166
  ## Note
@@ -237,6 +237,33 @@ batch = next(iter(dataloader))
237
237
  expression, labels, studies = batch
238
238
  ```
239
239
 
240
+ The dataloader can then be used in training models. The package provides a simple autoencoder to serve as a template for the user's own models.
241
+
242
+ ```python
243
+ import pytorch_lightning as pl
244
+ from cellarr.autoencoder import AutoEncoder
245
+
246
+ autoencoder = AutoEncoder(
247
+ n_genes=len(datamodule.gene_indices),
248
+ latent_dim=128,
249
+ hidden_dim=[1024, 1024, 1024],
250
+ dropout=0.5,
251
+ input_dropout=0.4,
252
+ residual=False,
253
+ )
254
+
255
+ model_path = "/path/to/model/mymodel/"
256
+ params = {
257
+ "max_epochs": 500,
258
+ "logger": True,
259
+ "log_every_n_steps": 1,
260
+ "limit_train_batches": 100, # to specify number of batches per epoch
261
+ }
262
+ trainer = pl.Trainer(**params)
263
+ trainer.fit(autoencoder, datamodule=datamodule)
264
+ autoencoder.save_all(model_path=model_path)
265
+ ```
266
+
240
267
  ---
241
268
 
242
269
  Check out the [documentation](https://biocpy.github.io/cellarr/api/modules.html) for more details.
@@ -0,0 +1,444 @@
1
+ import os
2
+ import torch
3
+ from torch import nn
4
+ import torch.nn.functional as F
5
+ from typing import List
6
+ import pytorch_lightning as pl
7
+
8
+
9
+ class Encoder(nn.Module):
10
+ """A class that encapsulates the encoder."""
11
+
12
+ def __init__(
13
+ self,
14
+ n_genes: int,
15
+ latent_dim: int = 128,
16
+ hidden_dim: List[int] = [1024, 1024],
17
+ dropout: float = 0.5,
18
+ input_dropout: float = 0.4,
19
+ residual: bool = False,
20
+ ):
21
+ """Constructor.
22
+
23
+ Args:
24
+ n_genes:
25
+ The number of genes in the gene space, representing the input dimensions.
26
+
27
+ latent_dim:
28
+ The latent space dimensions
29
+
30
+ hidden_dim:
31
+ A list of hidden layer dimensions, describing the number of layers and their dimensions.
32
+ Hidden layers are constructed in the order of the list for the encoder and in reverse
33
+ for the decoder.
34
+
35
+ dropout:
36
+ The dropout rate for hidden layers
37
+
38
+ input_dropout:
39
+ The dropout rate for the input layer
40
+
41
+ residual:
42
+ Use residual connections.
43
+ """
44
+
45
+ super().__init__()
46
+ self.latent_dim = latent_dim
47
+ self.network = nn.ModuleList()
48
+ self.residual = residual
49
+ if self.residual:
50
+ assert len(set(hidden_dim)) == 1
51
+ for i in range(len(hidden_dim)):
52
+ if i == 0: # input layer
53
+ self.network.append(
54
+ nn.Sequential(
55
+ nn.Dropout(p=input_dropout),
56
+ nn.Linear(n_genes, hidden_dim[i]),
57
+ nn.BatchNorm1d(hidden_dim[i]),
58
+ nn.PReLU(),
59
+ )
60
+ )
61
+ else: # hidden layers
62
+ self.network.append(
63
+ nn.Sequential(
64
+ nn.Dropout(p=dropout),
65
+ nn.Linear(hidden_dim[i - 1], hidden_dim[i]),
66
+ nn.BatchNorm1d(hidden_dim[i]),
67
+ nn.PReLU(),
68
+ )
69
+ )
70
+ # output layer
71
+ self.network.append(nn.Linear(hidden_dim[-1], latent_dim))
72
+
73
+ def forward(self, x) -> torch.Tensor:
74
+ """Forward.
75
+
76
+ Args:
77
+ x: torch.Tensor
78
+ Input tensor corresponding to input layer.
79
+
80
+ Returns:
81
+ Output tensor corresponding to output layer.
82
+ """
83
+
84
+ for i, layer in enumerate(self.network):
85
+ if self.residual and (0 < i < len(self.network) - 1):
86
+ x = layer(x) + x
87
+ else:
88
+ x = layer(x)
89
+ return F.normalize(x, p=2, dim=1)
90
+
91
+ def save_state(self, filename: str):
92
+ """Save model state.
93
+
94
+ Args:
95
+ filename:
96
+ Filename to save the model state.
97
+ """
98
+
99
+ torch.save({"state_dict": self.state_dict()}, filename)
100
+
101
+ def load_state(self, filename: str, use_gpu: bool = False):
102
+ """Load model state.
103
+
104
+ Args:
105
+ filename:
106
+ Filename containing the model state.
107
+
108
+ use_gpu:
109
+ Boolean indicating whether or not to use GPUs.
110
+ """
111
+
112
+ if not use_gpu:
113
+ ckpt = torch.load(filename, map_location=torch.device("cpu"))
114
+ else:
115
+ ckpt = torch.load(filename)
116
+ self.load_state_dict(ckpt["state_dict"])
117
+
118
+
119
+ class Decoder(nn.Module):
120
+ """A class that encapsulates the decoder."""
121
+
122
+ def __init__(
123
+ self,
124
+ n_genes: int,
125
+ latent_dim: int = 128,
126
+ hidden_dim: List[int] = [1024, 1024],
127
+ dropout: float = 0.5,
128
+ residual: bool = False,
129
+ ):
130
+ """Constructor.
131
+
132
+ Args:
133
+ n_genes:
134
+ The number of genes in the gene space, representing the input dimensions.
135
+
136
+ latent_dim:
137
+ The latent space dimensions
138
+
139
+ hidden_dim:
140
+ A list of hidden layer dimensions, describing the number of layers and their dimensions.
141
+ Hidden layers are constructed in the order of the list for the encoder and in reverse
142
+ for the decoder.
143
+
144
+ dropout:
145
+ The dropout rate for hidden layers
146
+
147
+ residual:
148
+ Use residual connections.
149
+ """
150
+
151
+ super().__init__()
152
+ self.latent_dim = latent_dim
153
+ self.network = nn.ModuleList()
154
+ self.residual = residual
155
+ if self.residual:
156
+ assert len(set(hidden_dim)) == 1
157
+ for i in range(len(hidden_dim)):
158
+ if i == 0: # first hidden layer
159
+ self.network.append(
160
+ nn.Sequential(
161
+ nn.Linear(latent_dim, hidden_dim[i]),
162
+ nn.BatchNorm1d(hidden_dim[i]),
163
+ nn.PReLU(),
164
+ )
165
+ )
166
+ else: # other hidden layers
167
+ self.network.append(
168
+ nn.Sequential(
169
+ nn.Dropout(p=dropout),
170
+ nn.Linear(hidden_dim[i - 1], hidden_dim[i]),
171
+ nn.BatchNorm1d(hidden_dim[i]),
172
+ nn.PReLU(),
173
+ )
174
+ )
175
+ # reconstruction layer
176
+ self.network.append(nn.Linear(hidden_dim[-1], n_genes))
177
+
178
+ def forward(self, x) -> torch.Tensor:
179
+ """Forward.
180
+
181
+ Args:
182
+ x:
183
+ Input tensor corresponding to input layer.
184
+
185
+ Returns:
186
+ Output tensor corresponding to output layer.
187
+ """
188
+ for i, layer in enumerate(self.network):
189
+ if self.residual and (0 < i < len(self.network) - 1):
190
+ x = layer(x) + x
191
+ else:
192
+ x = layer(x)
193
+ return x
194
+
195
+ def save_state(self, filename: str):
196
+ """Save model state.
197
+
198
+ Args:
199
+ filename:
200
+ Filename to save the model state.
201
+ """
202
+
203
+ torch.save({"state_dict": self.state_dict()}, filename)
204
+
205
+ def load_state(self, filename: str, use_gpu: bool = False):
206
+ """Load model state.
207
+
208
+ Args:
209
+ filename:
210
+ Filename containing the model state.
211
+
212
+ use_gpu:
213
+ Boolean indicating whether or not to use GPUs.
214
+ """
215
+
216
+ if not use_gpu:
217
+ ckpt = torch.load(filename, map_location=torch.device("cpu"))
218
+ else:
219
+ ckpt = torch.load(filename)
220
+ self.load_state_dict(ckpt["state_dict"])
221
+
222
+
223
+ class AutoEncoder(pl.LightningModule):
224
+ """A class encapsulating training."""
225
+
226
+ def __init__(
227
+ self,
228
+ n_genes: int,
229
+ latent_dim: int = 128,
230
+ hidden_dim: List[int] = [1024, 1024],
231
+ dropout: float = 0.5,
232
+ input_dropout: float = 0.4,
233
+ lr: float = 5e-3,
234
+ residual: bool = False,
235
+ ):
236
+ """Constructor.
237
+
238
+ Args:
239
+ n_genes:
240
+ The number of genes in the gene space, representing the input dimensions.
241
+
242
+ latent_dim:
243
+ The latent space dimensions. Defaults to 128.
244
+
245
+ hidden_dim:
246
+ A list of hidden layer dimensions, describing the number of layers and their dimensions.
247
+ Hidden layers are constructed in the order of the list for the encoder and in reverse
248
+ for the decoder.
249
+
250
+ dropout:
251
+ The dropout rate for hidden layers
252
+
253
+ input_dropout:
254
+ The dropout rate for the input layer
255
+
256
+ lr:
257
+ The initial learning rate
258
+
259
+ residual:
260
+ Use residual connections.
261
+ """
262
+
263
+ super().__init__()
264
+
265
+ # network architecture
266
+ self.n_genes = n_genes
267
+ self.latent_dim = latent_dim
268
+ self.hidden_dim = hidden_dim
269
+ self.dropout = dropout
270
+ self.input_dropout = input_dropout
271
+ self.lr = lr
272
+ self.residual = residual
273
+
274
+ # networks
275
+ self.encoder = Encoder(
276
+ self.n_genes,
277
+ latent_dim=self.latent_dim,
278
+ hidden_dim=self.hidden_dim,
279
+ dropout=self.dropout,
280
+ input_dropout=self.input_dropout,
281
+ residual=self.residual,
282
+ )
283
+ self.decoder = Decoder(
284
+ self.n_genes,
285
+ latent_dim=self.latent_dim,
286
+ hidden_dim=list(reversed(self.hidden_dim)),
287
+ dropout=self.dropout,
288
+ residual=self.residual,
289
+ )
290
+
291
+ self.mse_loss_fn = nn.MSELoss()
292
+ self.scheduler = None
293
+ self.val_step_outputs = []
294
+
295
+ def configure_optimizers(self):
296
+ """Configure optimizers."""
297
+
298
+ optimizer = torch.optim.AdamW(self.parameters(), self.lr, weight_decay=0.01)
299
+ self.scheduler = {
300
+ "scheduler": torch.optim.lr_scheduler.StepLR(optimizer, step_size=0.001),
301
+ "interval": "epoch",
302
+ "frequency": 1,
303
+ }
304
+ return {
305
+ "optimizer": optimizer,
306
+ "lr_scheduler": self.scheduler,
307
+ } # pytorch-lightning required format
308
+
309
+ def forward(self, x):
310
+ """Forward.
311
+
312
+ Args:
313
+ x:
314
+ Input tensor corresponding to input layer.
315
+
316
+ Returns:
317
+ Output tensor corresponding to the last encoder layer.
318
+
319
+ Output tensor corresponding to the last decoder layer.
320
+ """
321
+
322
+ z = self.encoder(x)
323
+ x_hat = self.decoder(z)
324
+ return z, x_hat
325
+
326
+ def get_loss(self, batch):
327
+ """Calculate the loss.
328
+
329
+ Args:
330
+ batch:
331
+ A batch as defined by a pytorch DataLoader.
332
+
333
+ Returns:
334
+ The training loss
335
+ """
336
+
337
+ cells, labels, studies = batch
338
+ embedding, reconstruction = self(cells)
339
+ return self.mse_loss_fn(cells, reconstruction)
340
+
341
+ def training_step(self, batch, batch_idx):
342
+ """Pytorch-lightning training step."""
343
+
344
+ loss = self.get_loss(batch)
345
+ self.log("train loss", loss, prog_bar=True, logger=True)
346
+ return loss
347
+
348
+ def on_validation_epoch_start(self):
349
+ """Pytorch-lightning validation epoch start."""
350
+
351
+ super().on_validation_epoch_start()
352
+ self.val_step_outputs = []
353
+
354
+ def validation_step(self, batch, batch_idx):
355
+ """Pytorch-lightning validation step."""
356
+
357
+ if self.trainer.datamodule.val_dataset is None:
358
+ return {}
359
+ return self._eval_step(batch, prefix="val")
360
+
361
+ def on_validation_epoch_end(self):
362
+ """Pytorch-lightning validation epoch end evaluation."""
363
+
364
+ if self.trainer.datamodule.val_dataset is None:
365
+ return {}
366
+ return self._eval_epoch(prefix="val")
367
+
368
+ def _eval_step(self, batch, prefix: str):
369
+ """Evaluation of validation or test step.
370
+
371
+ Args:
372
+ batch:
373
+ A batch as defined by a pytorch DataLoader.
374
+ prefix:
375
+ A string prefix to label logs.
376
+
377
+ Returns:
378
+ A dictionary containing step evaluation metrics.
379
+ """
380
+
381
+ loss = self.get_loss(batch)
382
+
383
+ losses = {
384
+ f"{prefix}_loss": loss,
385
+ }
386
+
387
+ if prefix == "val":
388
+ self.val_step_outputs.append(losses)
389
+ return losses
390
+
391
+ def _eval_epoch(self, prefix: str):
392
+ """Evaluation of validation or test epoch.
393
+
394
+ Args:
395
+ prefix:
396
+ A string prefix to label logs.
397
+
398
+ Returns:
399
+ A dictionary containing epoch evaluation metrics.
400
+ """
401
+
402
+ if prefix == "val":
403
+ step_outputs = self.val_step_outputs
404
+
405
+ loss = torch.Tensor([step[f"{prefix}_loss"] for step in step_outputs]).mean()
406
+ self.log(f"{prefix} loss", loss, logger=True)
407
+
408
+ losses = {
409
+ f"{prefix}_loss": loss,
410
+ }
411
+ return losses
412
+
413
+ def save_all(
414
+ self,
415
+ model_path: str,
416
+ ):
417
+ if not os.path.isdir(model_path):
418
+ os.makedirs(model_path)
419
+
420
+ # save model
421
+ self.encoder.save_state(os.path.join(model_path, "encoder.ckpt"))
422
+ self.decoder.save_state(os.path.join(model_path, "decoder.ckpt"))
423
+
424
+ def load_state(
425
+ self,
426
+ encoder_filename: str,
427
+ decoder_filename: str,
428
+ use_gpu: bool = False,
429
+ ):
430
+ """Load model state.
431
+
432
+ Args:
433
+ encoder_filename:
434
+ Filename containing the encoder model state.
435
+
436
+ decoder_filename:
437
+ Filename containing the decoder model state.
438
+
439
+ use_gpu:
440
+ Boolean indicating whether or not to use GPUs.
441
+ """
442
+
443
+ self.encoder.load_state(encoder_filename, use_gpu)
444
+ self.decoder.load_state(decoder_filename, use_gpu)
@@ -291,9 +291,11 @@ def build_cellarrdataset(
291
291
  sample_metadata = pd.DataFrame({"cellarr_sample": _samples})
292
292
  elif isinstance(sample_metadata, str):
293
293
  sample_metadata = pd.read_csv(sample_metadata, header=0)
294
- sample_metadata["cellarr_sample"] = _samples
294
+ if "cellarr_sample" not in sample_metadata.columns:
295
+ sample_metadata["cellarr_sample"] = _samples
295
296
  elif isinstance(sample_metadata, pd.DataFrame):
296
- sample_metadata["cellarr_sample"] = _samples
297
+ if "cellarr_sample" not in sample_metadata.columns:
298
+ sample_metadata["cellarr_sample"] = _samples
297
299
  else:
298
300
  raise TypeError("'sample_metadata' is not an expected type.")
299
301
 
@@ -302,11 +304,13 @@ def build_cellarrdataset(
302
304
  "Scanning all files for feature ids (e.g. gene symbols), this may take long",
303
305
  UserWarning,
304
306
  )
305
- gene_scan_set = uad.scan_for_features(files_cache, unique=False)
306
- gene_set_str = [",".join(x) for x in gene_scan_set]
307
- sample_metadata["cellarr_original_gene_set"] = gene_set_str
307
+ if "cellarr_original_gene_set" not in sample_metadata.columns:
308
+ gene_scan_set = uad.scan_for_features(files_cache, unique=False)
309
+ gene_set_str = [",".join(x) for x in gene_scan_set]
310
+ sample_metadata["cellarr_original_gene_set"] = gene_set_str
308
311
 
309
- sample_metadata["cellarr_cell_counts"] = cell_counts
312
+ if "cellarr_cell_counts" not in sample_metadata.columns:
313
+ sample_metadata["cellarr_cell_counts"] = cell_counts
310
314
 
311
315
  _col_types = utf.infer_column_types(
312
316
  sample_metadata, sample_metadata_options.column_types
@@ -367,8 +371,11 @@ def build_cellarrdataset(
367
371
  "Number of rows in 'cell_metadata' does not match the number of cells across files."
368
372
  )
369
373
 
370
- cell_metadata["cellarr_sample"] = _sample_per_cell
371
- cell_metadata["cellarr_cell_index_in_sample"] = _cell_index_in_sample
374
+ if "cellarr_sample" not in cell_metadata.columns:
375
+ cell_metadata["cellarr_sample"] = _sample_per_cell
376
+
377
+ if "cellarr_cell_index_in_sample" not in cell_metadata.columns:
378
+ cell_metadata["cellarr_cell_index_in_sample"] = _cell_index_in_sample
372
379
 
373
380
  cell_metadata.reset_index(drop=True, inplace=True)
374
381
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cellarr
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: TileDB-based array storage for genomics data collections.
5
5
  Home-page: https://github.com/BiocPy/cellarr
6
6
  Author: Jayaram Kancherla
@@ -165,6 +165,32 @@ datamodule = DataModule(
165
165
  )
166
166
  ```
167
167
 
168
+ The package also includes a simple autoencoder in the `pytorch-lightning` which makes use of the dataloader. This can be used as a template to create custom architectures and models.
169
+
170
+ ```python
171
+ import pytorch_lightning as pl
172
+ from cellarr.autoencoder import AutoEncoder
173
+
174
+ autoencoder = AutoEncoder(
175
+ n_genes=len(datamodule.gene_indices),
176
+ latent_dim=128,
177
+ hidden_dim=[1024, 1024, 1024],
178
+ dropout=0.5,
179
+ input_dropout=0.4,
180
+ residual=False,
181
+ )
182
+
183
+ model_path = "/path/to/model/mymodel/"
184
+ params = {
185
+ "max_epochs": 500,
186
+ "logger": True,
187
+ "log_every_n_steps": 1,
188
+ "limit_train_batches": 100, # to specify number of batches per epoch
189
+ }
190
+ trainer = pl.Trainer(**params)
191
+ trainer.fit(autoencoder, datamodule=datamodule)
192
+ autoencoder.save_all(model_path=model_path)
193
+ ```
168
194
  <!-- pyscaffold-notes -->
169
195
 
170
196
  ## Note
@@ -31,6 +31,7 @@ docs/_static/.gitignore
31
31
  src/cellarr/CellArrDataset.py
32
32
  src/cellarr/CellArrDatasetSlice.py
33
33
  src/cellarr/__init__.py
34
+ src/cellarr/autoencoder.py
34
35
  src/cellarr/build_cellarrdataset.py
35
36
  src/cellarr/build_options.py
36
37
  src/cellarr/buildutils_tiledb_array.py
@@ -2,8 +2,10 @@ import tempfile
2
2
 
3
3
  import anndata
4
4
  import numpy as np
5
+ import os
5
6
  import pandas as pd
6
7
  import pytest
8
+ import pytorch_lightning as pl
7
9
  import random
8
10
  from scipy.sparse import rand
9
11
  import tiledb
@@ -12,6 +14,7 @@ from cellarr import (
12
14
  MatrixOptions,
13
15
  )
14
16
  from cellarr.dataloader import DataModule
17
+ from cellarr.autoencoder import AutoEncoder
15
18
 
16
19
  __author__ = "Jayaram Kancherla"
17
20
  __copyright__ = "Jayaram Kancherla"
@@ -78,3 +81,54 @@ def test_dataloader():
78
81
  assert len(labels) == 100
79
82
  assert len(studies) == 100
80
83
  assert data.shape == (100, len(datamodule.gene_indices))
84
+
85
+
86
+ def test_autoencoder():
87
+ tempdir = tempfile.mkdtemp()
88
+
89
+ adata1 = generate_adata(1000, 100, study="test1")
90
+ adata2 = generate_adata(100, 1000, study="test2")
91
+ adata3 = generate_adata(100, 100, study="test3")
92
+ obs = pd.concat([adata1.obs, adata2.obs, adata3.obs])
93
+ obs = obs.reset_index(drop=True)
94
+
95
+ build_cellarrdataset(
96
+ output_path=tempdir,
97
+ files=[adata1, adata2, adata3],
98
+ cell_metadata=obs,
99
+ matrix_options=MatrixOptions(dtype=np.float32),
100
+ )
101
+
102
+ datamodule = DataModule(
103
+ dataset_path=tempdir,
104
+ cell_metadata_uri="cell_metadata",
105
+ gene_annotation_uri="gene_annotation",
106
+ matrix_uri="counts",
107
+ val_studies=["test3"],
108
+ label_column_name="label",
109
+ study_column_name="study",
110
+ batch_size=100,
111
+ lognorm=True,
112
+ target_sum=1e4,
113
+ )
114
+
115
+ autoencoder = AutoEncoder(
116
+ n_genes=len(datamodule.gene_indices),
117
+ latent_dim=128,
118
+ hidden_dim=[1024, 1024],
119
+ dropout=0.5,
120
+ input_dropout=0.4,
121
+ residual=False,
122
+ )
123
+
124
+ params = {
125
+ "max_epochs": 2,
126
+ "logger": True,
127
+ "log_every_n_steps": 1,
128
+ "limit_train_batches": 4,
129
+ }
130
+ trainer = pl.Trainer(**params)
131
+ trainer.fit(autoencoder, datamodule=datamodule)
132
+ autoencoder.save_all(model_path=os.path.join(tempdir, "test"))
133
+ assert os.path.isfile(os.path.join(tempdir, "test", "encoder.ckpt"))
134
+ assert os.path.isfile(os.path.join(tempdir, "test", "decoder.ckpt"))
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
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