Append a new dataset .md .md

We have one dataset in storage and are about to receive a new dataset.

In this notebook, we’ll see how to manage the situation.

import lamindb as ln
import bionty as bt
import readfcs

bt.settings.organism = "human"

ln.track("SmQmhrhigFPL0000")
Hide code cell output
→ connected lamindb: testuser1/test-facs
→ created Transform('SmQmhrhigFPL0000', key='facs2.ipynb'), started new Run('IWBejVriqKXnAw8e') at 2026-10-28 10:01:32 UTC

Ingest a new artifact

Access

Let us validate and register another .fcs file from Oetjen18:

filepath = readfcs.datasets.Oetjen18_t1()

adata = readfcs.read(filepath)
# since anndata>=0.12.0, `/` is not allowed in keys
adata.var.index = adata.var.index.str.replace("/", "|")
adata.var["marker"] = adata.var["marker"].str.replace("/", "|")
adata.uns["meta"]["spill"].index = adata.uns["meta"]["spill"].index.str.replace(
    "/", "|"
)
adata.uns["meta"]["spill"].columns = adata.uns["meta"]["spill"].columns.str.replace(
    "/", "|"
)
adata
Hide code cell output
/opt/hostedtoolcache/Python/3.12.15/x64/lib/python3.12/functools.py:912: ImplicitModificationWarning: Transforming to str index.
  return dispatch(args[0].__class__)(*args, **kw)
AnnData object with n_obs × n_vars = 241552 × 20
    var: 'n', 'channel', 'marker', 'PnR', 'PnB', 'PnE', 'PnV', 'PnG'
    uns: 'meta'

Transform: normalize

import pytometry as pm
pm.pp.split_signal(adata, var_key="channel")
Hide code cell output
/opt/hostedtoolcache/Python/3.12.15/x64/lib/python3.12/site-packages/pytometry/pp/_process_data.py:230: FutureWarning: The method var_keys is deprecated and will be removed in the future. Use var instead of var_keys. (e.g. `k in adata.var` or `str(adata.var.columns.tolist())`)
  if key_in not in adata.var_keys():
/opt/hostedtoolcache/Python/3.12.15/x64/lib/python3.12/site-packages/pytometry/pp/_process_data.py:66: FutureWarning: The method var_keys is deprecated and will be removed in the future. Use var instead of var_keys. (e.g. `k in adata.var` or `str(adata.var.columns.tolist())`)
  elif var_key in adata.var_keys():
pm.pp.compensate(adata)
pm.tl.normalize_biExp(adata)
adata = adata[  # subset to rows that do not have nan values
    adata.to_df().isna().sum(axis=1) == 0
]
adata.to_df().describe()
Hide code cell output
CD95 CD8 CD27 CXCR4 CCR7 LIVE|DEAD CD4 CD45RA CD3 CD49B CD14|19 CD69 CD103
count 241552.000000 241552.000000 241552.000000 241552.000000 241552.000000 241552.000000 241552.000000 241552.000000 241552.000000 241552.000000 241552.000000 241552.000000 241552.000000
mean 1049.015217 1293.326611 1240.562589 1163.779802 795.800526 1897.421272 1671.292539 692.529545 820.430567 870.801405 1429.846115 608.127554 963.554032
std 634.579243 831.914759 675.886939 559.254219 579.415556 922.604717 832.483038 853.018613 680.059269 587.595901 565.002417 653.581155 649.819303
min 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 20.200048 0.000000 0.000000
25% 528.414959 485.497959 613.954166 693.165730 398.802203 1100.959173 900.084668 0.000000 404.329481 418.861659 1014.659374 0.000000 451.780983
50% 989.333555 1188.070828 1154.625279 1081.005669 592.531495 1960.735775 1702.230733 383.136112 558.805440 784.715150 1355.348975 434.837372 688.743994
75% 1541.398578 2033.498971 1743.629192 1540.015342 1149.777722 2628.235834 2328.352403 721.017068 1003.313507 1370.356836 1793.131327 1096.707626 1386.160643
max 4054.890968 4064.772534 4096.000000 4042.557502 3991.302815 4096.000000 4096.000000 3958.503398 3933.310314 4075.465630 4096.000000 4003.981981 4021.460156

Validate cell markers

Let’s see how many markers validate:

validated = bt.CellMarker.validate(adata.var.index)
Hide code cell output
! 9 unique terms (69.20%) are not validated for name: 'CD95', 'CXCR4', 'CCR7', 'LIVE|DEAD', 'CD4', 'CD49B', 'CD14|19', 'CD69', 'CD103'

Let’s standardize and re-validate:

adata.var.index = bt.CellMarker.standardize(adata.var.index)
validated = bt.CellMarker.validate(adata.var.index)
Hide code cell output
! 7 unique terms (53.80%) are not validated for name: 'CD95', 'CXCR4', 'LIVE|DEAD', 'CD49B', 'CD14|19', 'CD69', 'CD103'
/tmp/ipykernel_3831/92294437.py:1: ImplicitModificationWarning: Trying to modify index of attribute `.var` of view, initializing view as actual.
  adata.var.index = bt.CellMarker.standardize(adata.var.index)

Next, register non-validated markers from Bionty:

records = bt.CellMarker.from_values(adata.var.index[~validated])
ln.save(records)

Manually create 1 marker:

bt.CellMarker(name="CD14|19").save()
Hide code cell output
CellMarker(uid='67ZpJGSKNFyEAq', abbr=None, synonyms=None, description=None, name='CD14|19', gene_symbol=None, ncbi_gene_id=None, uniprotkb_id=None, branch_id=1, created_on_id=1, space_id=1, created_by_id=1, run_id=2, source_id=None, organism_id=1, created_at=2026-10-28 10:01:36 UTC, is_locked=False)

Move metadata to obs:

validated = bt.CellMarker.validate(adata.var.index)
adata.obs = adata[:, ~validated].to_df()
adata = adata[:, validated].copy()
Hide code cell output
! 1 unique term (7.70%) is not validated for name: 'LIVE|DEAD'

Now all markers pass validation:

validated = bt.CellMarker.validate(adata.var.index)
assert all(validated)

Register

schema = ln.Schema.get(name="FACS-AnnData-schema")
curator = ln.curators.AnnDataCurator(adata, schema=schema)
artifact = curator.save_artifact(description="Oetjen18_t1")
Hide code cell output
! no values were validated for columns!
→ returning schema with same hash: Schema(uid='0000000000000000', is_type=False, name='FACS-sample-metadata', description=None, n_members=None, coerce=None, flexible=True, itype='Feature', otype=None, suffix=None, hash='kMi7B_N88uu-YnbTLDU-DA', minimal_set=True, ordered_set=False, maximal_set=False, branch_id=1, created_on_id=1, space_id=1, created_by_id=1, run_id=1, type_id=None, created_at=2026-10-28 10:01:26 UTC, is_locked=False)

Annotate with more labels:

efs = bt.ExperimentalFactor.lookup()
organism = bt.Organism.lookup()

artifact.experimental_factors.add(efs.fluorescence_activated_cell_sorting)
artifact.organisms.add(organism.human)
artifact.describe()
Hide code cell output
Artifact:  (0000)
|   description: Oetjen18_t1
├── uid: DnHcIgG0FYtkOxUc0000            run: IWBejVr (facs2.ipynb) 
│   kind: dataset                        otype: AnnData             
│   hash: d6l9MySX8FVezzyFggkKvQ         size: 44.4 MB              
│   branch: main                         space: all                 
│   created_at: 2026-10-28 10:01:36 UTC  created_by: testuser1      
│   n_observations: 241552               schema: FACS-AnnData-schema
├── storage/path: /home/runner/work/lamin-usecases/lamin-usecases/docs/test-facs/.lamindb/DnHcIgG0FYtkOxUc0000.h5ad
├── Dataset features
│   ├── obs (None)                                                                                                 
│   └── var.T (12 bionty.CellMarker)                                                                               
│       CD103                          num                                                                         
│       CD14|19                        num                                                                         
│       CD27                           num                                                                         
│       CD3                            num                                                                         
│       CD45RA                         num                                                                         
│       CD49B                          num                                                                         
│       CD69                           num                                                                         
│       CD8                            num                                                                         
│       CD95                           num                                                                         
│       CXCR4                          num                                                                         
│       Ccr7                           num                                                                         
│       Cd4                            num                                                                         
└── Entities
    └── .organisms                     bionty.Organism                      human                                  
        .experimental_factors          bionty.ExperimentalFactor            fluorescence-activated cell sorting    

Inspect a PCA fo QC - this collection looks much like noise:

import scanpy as sc

markers = bt.CellMarker.lookup()

sc.pp.pca(adata)
sc.pl.pca(adata, color=markers.cd8.name)
Hide code cell output
_images/e695a5e313ef61e364886988d4c4b73d00aafa2da1838abe486344535851e83b.png

Create a new version of the collection by appending a artifact

Query the old version:

collection_v1 = ln.Collection.get(key="My versioned cytometry collection")
collection_v2 = ln.Collection(
    [artifact, collection_v1.ordered_artifacts[0]],
    revises=collection_v1,
    version="2",
)
collection_v2.describe()
Hide code cell output
Collection: My versioned cytometry collection (2)
└── uid: Me2xfi0bHcN93XE50001                                                            run: IWBejVr (facs2.ipynb)
    branch: main                                                                         space: all                
    created_at: <django.db.models.expressions.DatabaseDefault object at 0x7f718d63ad20>  created_by: testuser1     
collection_v2.save()
Hide code cell output
Collection(uid='Me2xfi0bHcN93XE50001', key='My versioned cytometry collection', description=None, hash='SaHaV6QKcWEz6PLFYLjK2Q', reference=None, reference_type=None, meta_artifact=None, branch_id=1, created_on_id=1, space_id=1, created_by_id=1, run_id=2, schema_id=None, created_at=2026-10-28 10:01:38 UTC, is_locked=False, version_tag='2', is_latest=True)