Multi-modal .md .md

Here, we’ll showcase how to curate and register ECCITE-seq data from Papalexi21 in the form of MuData objects.

ECCITE-seq is designed to enable interrogation of single-cell transcriptomes together with surface protein markers in the context of CRISPR screens.

MuData objects build on top of AnnData objects to store multimodal data.

# pip install lamindb
lamin init --storage ./test-multimodal --modules bionty
Hide code cell output
 dev-dir is: /home/runner/work/lamin-usecases/lamin-usecases/docs
 initialized lamindb: testuser1/test-multimodal
import lamindb as ln
import bionty as bt

bt.settings.organism = "human"
ln.track()
Hide code cell output
 connected lamindb: testuser1/test-multimodal
 created Transform('HLN9sRSoooRB0000', key='multimodal.ipynb'), started new Run('RZGxuKIZ08EDAFo9') at 2026-09-15 10:23:33 UTC
 notebook imports: bionty==2.5.0 lamindb-core==2.10.0
 tip: to identify the notebook across renames, pass the uid: ln.track("HLN9sRSoooRB")

Creating MuData Artifacts

lamindb provides a from_mudata() method to create Artifact from MuData objects.

mdata = ln.core.datasets.mudata_papalexi21_subset()
mdata
Hide code cell output
/opt/hostedtoolcache/Python/3.12.14/x64/lib/python3.12/site-packages/mudata/_core/mudata.py:1354: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  mod_df.rename(
MuData object with n_obs × n_vars = 200 × 300
  obs:	'perturbation', 'replicate'
  var:	'name'
  4 modalities
    rna:	200 × 173
      obs:	'nCount_RNA', 'nFeature_RNA', 'percent.mito'
      var:	'name'
    adt:	200 × 4
      obs:	'nCount_ADT', 'nFeature_ADT'
      var:	'name'
    hto:	200 × 12
      obs:	'nCount_HTO', 'nFeature_HTO', 'technique'
      var:	'name'
    gdo:	200 × 111
      obs:	'nCount_GDO'
      var:	'name'
mdata_artifact = ln.Artifact.from_mudata(mdata, key="papalexi.h5mu")
mdata_artifact
Hide code cell output
Artifact(uid='4Xd8aKWNZezfPIr50000', key='papalexi.h5mu', description=None, suffix='.h5mu', kind='dataset', otype='MuData', size=549976, hash='yOTamE22ANM-Ph1iqxB7Gw', n_files=None, n_observations=200, extra_data=None, branch_id=1, created_on_id=1, space_id=1, storage_id=1, run_id=1, schema_id=None, created_by_id=1, created_at=<django.db.models.expressions.DatabaseDefault object at 0x7fb1147d1a30>, is_locked=False, version_tag=None, is_latest=True)
# MuData Artifacts have the corresponding otype
mdata_artifact.otype
Hide code cell output
'MuData'
# MuData Artifacts can easily be loaded back into memory
papalexi_in_memory = mdata_artifact.load()
papalexi_in_memory
Hide code cell output
MuData object with n_obs × n_vars = 200 × 300
  obs:	'perturbation', 'replicate'
  var:	'name'
  4 modalities
    rna:	200 × 173
      obs:	'nCount_RNA', 'nFeature_RNA', 'percent.mito'
      var:	'name'
    adt:	200 × 4
      obs:	'nCount_ADT', 'nFeature_ADT'
      var:	'name'
    hto:	200 × 12
      obs:	'nCount_HTO', 'nFeature_HTO', 'technique'
      var:	'name'
    gdo:	200 × 111
      obs:	'nCount_GDO'
      var:	'name'

Schema

# define labels
perturbation = ln.ULabel(name="Perturbation", is_type=True).save()
ln.ULabel(name="Perturbed", type=perturbation).save()
ln.ULabel(name="NT", type=perturbation).save()

replicate = ln.ULabel(name="Replicate", is_type=True).save()
ln.ULabel(name="rep1", type=replicate).save()
ln.ULabel(name="rep2", type=replicate).save()
ln.ULabel(name="rep3", type=replicate).save()

# define obs schema
obs_schema = ln.Schema(
    name="mudata_papalexi21_subset_obs_schema",
    features=[
        ln.Feature(name="perturbation", dtype=perturbation).save(),
        ln.Feature(name="replicate", dtype=replicate).save(),
    ],
).save()

obs_schema_rna = ln.Schema(
    name="mudata_papalexi21_subset_rna_obs_schema",
    features=[
        ln.Feature(name="nCount_RNA", dtype=int).save(),
        ln.Feature(name="nFeature_RNA", dtype=int).save(),
        ln.Feature(name="percent.mito", dtype=float).save(),
    ],
    coerce_dtype=True,
).save()

obs_schema_hto = ln.Schema(
    name="mudata_papalexi21_subset_hto_obs_schema",
    features=[
        ln.Feature(name="nCount_HTO", dtype=int).save(),
        ln.Feature(name="nFeature_HTO", dtype=int).save(),
        ln.Feature(name="technique", dtype=bt.ExperimentalFactor).save(),
    ],
    coerce_dtype=True,
).save()

var_schema_rna = ln.Schema(
    name="mudata_papalexi21_subset_rna_var_schema",
    itype=bt.Gene.symbol,
    dtype=float,
).save()

# define composite schema
mudata_schema = ln.Schema(
    name="mudata_papalexi21_subset_mudata_schema",
    otype="MuData",
    slots={
        "obs": obs_schema,
        "rna:obs": obs_schema_rna,
        "hto:obs": obs_schema_hto,
        "rna:var": var_schema_rna,
    },
).save()
Hide code cell output
! tip: pass `type` to map ulabel into a type hierarchy
! tip: pass `type` to map ulabel into a type hierarchy
! tip: pass `type` to map feature into a type hierarchy
! tip: pass `type` to map feature into a type hierarchy
! tip: pass `type` to map schema into a type hierarchy
! tip: pass `type` to map feature into a type hierarchy
! tip: pass `type` to map feature into a type hierarchy
! tip: pass `type` to map feature into a type hierarchy
! tip: pass `type` to map schema into a type hierarchy
! tip: pass `type` to map feature into a type hierarchy
! tip: pass `type` to map feature into a type hierarchy
! you are trying to create a record with name='nFeature_HTO' but a record with similar name exists: 'nFeature_RNA'. Did you mean to load it?
! tip: pass `type` to map feature into a type hierarchy
! tip: pass `type` to map schema into a type hierarchy
! tip: pass `type` to map schema into a type hierarchy
! tip: pass `type` to map schema into a type hierarchy
/tmp/ipykernel_3595/897646117.py:20: DeprecationWarning: `coerce_dtype` argument was renamed to `coerce` and will be removed in a future release.
  obs_schema_rna = ln.Schema(
/tmp/ipykernel_3595/897646117.py:30: DeprecationWarning: `coerce_dtype` argument was renamed to `coerce` and will be removed in a future release.
  obs_schema_hto = ln.Schema(
mudata_schema.describe()
Hide code cell output
Schema: mudata_papalexi21_subset_mudata_schema
├── uid: rl3WCqtvZRNt6ADD  run: RZGxuKI (multimodal.ipynb)    
otype: MuData          created_at: 2026-09-15 10:23:35 UTC
created_by: testuser1                                     
├── obs: mudata_papalexi21_subset_obs_schema
│   ├── uid: X4DYFH0x8PLseScy                run: RZGxuKI (multimodal.ipynb)
│   │   created_at: 2026-09-15 10:23:35 UTC  created_by: testuser1          
│   └── Features (2)
│       └── name          dtype                 optional  nullable  coerce  default_value
perturbation  ULabel[Perturbation]  ✗         ✓         ✗       unset        
replicate     ULabel[Replicate]     ✗         ✓         ✗       unset        
├── rna:obs: mudata_papalexi21_subset_rna_obs_schema
│   ├── uid: KAGVSScP0xeFrVMC                run: RZGxuKI (multimodal.ipynb)
│   │   created_at: 2026-09-15 10:23:35 UTC  created_by: testuser1          
│   └── Features (3)
│       └── name          dtype  optional  nullable  coerce  default_value
nCount_RNA    int    ✗         ✓         ✓       unset        
nFeature_RNA  int    ✗         ✓         ✓       unset        
percent.mito  float  ✗         ✓         ✓       unset        
├── hto:obs: mudata_papalexi21_subset_hto_obs_schema
│   ├── uid: At02Om5hGISQsYNa                run: RZGxuKI (multimodal.ipynb)
│   │   created_at: 2026-09-15 10:23:35 UTC  created_by: testuser1          
│   └── Features (3)
│       └── name          dtype                      optional  nullable  coerce  default_value
nCount_HTO    int                        ✗         ✓         ✓       unset        
nFeature_HTO  int                        ✗         ✓         ✓       unset        
technique     bionty.ExperimentalFactor  ✗         ✓         ✓       unset        
└── rna:var: mudata_papalexi21_subset_rna_var_schema
    ├── uid: 75aV6K3lliaNl175      run: RZGxuKI (multimodal.ipynb)    
itype: bionty.Gene.symbol  created_at: 2026-09-15 10:23:35 UTC
created_by: testuser1                                         
    └── bionty.Gene.symbol
        └── dtype: float

Validate MuData annotations

curator = ln.curators.MuDataCurator(mdata, mudata_schema)
Hide code cell output
! auto-transposed `var` for backward compat, please indicate transposition in the schema definition by calling out `.T`: slots={'var.T': itype=bt.Gene.ensembl_gene_id}
try:
    curator.validate()
except ln.errors.ValidationError:
    pass
curator.slots["rna:var"].cat.standardize("columns")
curator.slots["rna:var"].cat.add_new_from("columns")
curator.validate()

Register curated Artifact

artifact = curator.save_artifact(key="mudata_papalexi21_subset.h5mu")
Hide code cell output
 returning schema with same hash: Schema(uid='X4DYFH0x8PLseScy', is_type=False, name='mudata_papalexi21_subset_obs_schema', description=None, n_members=2, coerce=None, flexible=False, itype='Feature', otype=None, suffix=None, hash='24LXIb5KRVL3JidQwQRfCQ', 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-09-15 10:23:35 UTC, is_locked=False)
 returning schema with same hash: Schema(uid='KAGVSScP0xeFrVMC', is_type=False, name='mudata_papalexi21_subset_rna_obs_schema', description=None, n_members=3, coerce=True, flexible=False, itype='Feature', otype=None, suffix=None, hash='r1C1mgOkkxcJnOSUCHdlBQ', 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-09-15 10:23:35 UTC, is_locked=False)
 returning schema with same hash: Schema(uid='At02Om5hGISQsYNa', is_type=False, name='mudata_papalexi21_subset_hto_obs_schema', description=None, n_members=3, coerce=True, flexible=False, itype='Feature', otype=None, suffix=None, hash='Vde-hKOpsa7XLUkh4F-lqA', 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-09-15 10:23:35 UTC, is_locked=False)
artifact.describe()
Hide code cell output
Artifact: mudata_papalexi21_subset.h5mu (0000)
├── uid: vud73dUkoKMEax6Y0000            run: RZGxuKI (multimodal.ipynb)               
kind: dataset                        otype: MuData                                 
hash: yOTamE22ANM-Ph1iqxB7Gw         size: 537.1 KB                                
branch: main                         space: all                                    
created_at: 2026-09-15 10:23:37 UTC  created_by: testuser1                         
n_observations: 200                  schema: mudata_papalexi21_subset_mudata_schema
├── storage/path: 
/home/runner/work/lamin-usecases/lamin-usecases/docs/test-multimodal/.lamindb/vud73dUkoKMEax6Y0000.h5mu
├── Dataset features
├── obs (2)                                                                                                    
│   perturbation                   ULabel[Perturbation]                 NT, Perturbed                          
│   replicate                      ULabel[Replicate]                    rep1, rep2, rep3                       
├── rna:obs (3)                                                                                                
│   nCount_RNA                     int                                                                         
│   nFeature_RNA                   int                                                                         
│   percent.mito                   float                                                                       
├── hto:obs (3)                                                                                                
│   nCount_HTO                     int                                                                         
│   nFeature_HTO                   int                                                                         
│   technique                      bionty.ExperimentalFactor            cell hashing                           
└── rna:var (184 bionty.Gene.sym…                                                                              
    AK8                            num                                                                         
    AP4B1-AS1                      num                                                                         
    ARAP1-AS2                      num                                                                         
    ARHGAP26-AS1                   num                                                                         
    CA8                            num                                                                         
    CAV2-DT                        num                                                                         
    CDH8                           num                                                                         
    CERS3                          num                                                                         
    CLEC18C                        num                                                                         
    COL5A3                         num                                                                         
    CRYAB                          num                                                                         
    CRYBA1                         num                                                                         
    CSMD3                          num                                                                         
    CTAGE15                        num                                                                         
    CTAGE15                        num                                                                         
    CTRB2                          num                                                                         
    CTRB2                          num                                                                         
    DNAI7                          num                                                                         
    FSD2                           num                                                                         
    GABRA1                         num                                                                         
    H4C12                          num                                                                         
    HLA-DQB1-AS1                   num                                                                         
    HLA-DQB1-AS1                   num                                                                         
    HLA-DQB1-AS1                   num                                                                         
    HLA-DQB1-AS1                   num                                                                         
    HLA-DQB1-AS1                   num                                                                         
    HLA-DQB1-AS1                   num                                                                         
    HLA-DQB1-AS1                   num                                                                         
    HOXC-AS2                       num                                                                         
    HPN                            num                                                                         
    LARGE1                         num                                                                         
    LGALS9C                        num                                                                         
    LGALS9C                        num                                                                         
    LRRIQ1                         num                                                                         
    MEF2C-AS2                      num                                                                         
    MIA                            num                                                                         
    MYO16                          num                                                                         
    NPHS1                          num                                                                         
    NUCB1-AS1                      num                                                                         
    PFKFB1                         num                                                                         
    RBPMS-AS1                      num                                                                         
    RPRML                          num                                                                         
    SH2D6                          num                                                                         
    SPACA1                         num                                                                         
    TMEM72-AS1                     num                                                                         
    TRPC5                          num                                                                         
    TUBA3C                         num                                                                         
    TULP2                          num                                                                         
    VNN1                           num                                                                         
    ZNF483                         num                                                                         
└── Labels
    └── .ulabels                       ULabel                               Perturbed, NT, rep1, rep2, rep3        
        .experimental_factors          bionty.ExperimentalFactor            cell hashing                           
ln.finish()
Hide code cell output
 finished Run('RZGxuKIZ08EDAFo9') after 4s at 2026-09-15 10:23:38 UTC