@@ -50,6 +50,74 @@ class MuAxisArrays(AxisArrays):
50
50
_view_class = MuAxisArraysView
51
51
52
52
53
+ class ModDict (dict ):
54
+ def __init__ (self , * args , ** kwargs ):
55
+ super ().__init__ (* args , ** kwargs )
56
+
57
+ def _repr_hierarchy (
58
+ self , nest_level : int = 0 , is_last : bool = False , active_levels : Optional [List [int ]] = None
59
+ ) -> str :
60
+ descr = ""
61
+ active_levels = active_levels or []
62
+ for i , kv in enumerate (self .items ()):
63
+ k , v = kv
64
+ indent = (" " * nest_level ) + ("└─ " if i == len (self ) - 1 else "├─ " )
65
+
66
+ if len (active_levels ) > 0 :
67
+ indent_list = list (indent )
68
+ for level in active_levels :
69
+ indent_list [level * 3 ] = "│"
70
+ indent = "" .join (indent_list )
71
+
72
+ is_view = " view" if v .is_view else ""
73
+ backed_at = f" backed at { str (v .filename )!r} " if v .isbacked else ""
74
+
75
+ if isinstance (v , MuData ):
76
+ maybe_axis = (
77
+ (
78
+ f" [shared obs] "
79
+ if v .axis == 0
80
+ else f" [shared var] "
81
+ if v .axis == 1
82
+ else f" [shared obs and var] "
83
+ )
84
+ if hasattr (v , "axis" )
85
+ else ""
86
+ )
87
+ descr += (
88
+ f"\n { indent } { k } MuData{ maybe_axis } ({ v .n_obs } × { v .n_vars } ){ backed_at } { is_view } "
89
+ )
90
+
91
+ if i != len (self ) - 1 :
92
+ levels = [nest_level ] + [level for level in active_levels ]
93
+ else :
94
+ levels = [level for level in active_levels if level != nest_level ]
95
+ descr += v .mod ._repr_hierarchy (nest_level = nest_level + 1 , active_levels = levels )
96
+ elif isinstance (v , AnnData ):
97
+ descr += f"\n { indent } { k } AnnData ({ v .n_obs } x { v .n_vars } ){ backed_at } { is_view } "
98
+ else :
99
+ continue
100
+
101
+ return descr
102
+
103
+ def __repr__ (self ) -> str :
104
+ """
105
+ Represent the hierarchy of the modalities in the object.
106
+
107
+ A MuData object with two modalities, protein and RNA,
108
+ with the latter being a MuData containing raw, QC'ed and hvg-filtered AnnData objects,
109
+ will be represented as:
110
+
111
+ root MuData (axis=0) (5000 x 20100)
112
+ ├── protein AnnData (5000 x 100)
113
+ └── rna MuData (axis=-1) (5000 x 20000)
114
+ ├── raw AnnData (5000 x 20000)
115
+ ├── quality-filtered AnnData (3000 x 20000)
116
+ └── hvg-filtered AnnData (3000 x 4000)
117
+ """
118
+ return "MuData" + self ._repr_hierarchy ()
119
+
120
+
53
121
class MuData :
54
122
"""
55
123
Multimodal data object
@@ -81,7 +149,7 @@ def __init__(
81
149
return
82
150
83
151
# Add all modalities to a MuData object
84
- self .mod = dict ()
152
+ self .mod = ModDict ()
85
153
if isinstance (data , abc .Mapping ):
86
154
for k , v in data .items ():
87
155
self .mod [k ] = v
@@ -185,7 +253,7 @@ def _init_as_view(self, mudata_ref: "MuData", index):
185
253
if isinstance (varidx , Integral ):
186
254
varidx = slice (varidx , varidx + 1 )
187
255
188
- self .mod = dict ()
256
+ self .mod = ModDict ()
189
257
for m , a in mudata_ref .mod .items ():
190
258
cobsidx , cvaridx = mudata_ref .obsmap [m ][obsidx ], mudata_ref .varmap [m ][varidx ]
191
259
cobsidx , cvaridx = cobsidx [cobsidx > 0 ] - 1 , cvaridx [cvaridx > 0 ] - 1
@@ -1239,7 +1307,17 @@ def _gen_repr(self, n_obs, n_vars, extensive: bool = False, nest_level: int = 0)
1239
1307
indent = " " * nest_level
1240
1308
backed_at = f" backed at { str (self .filename )!r} " if self .isbacked else ""
1241
1309
view_of = "View of " if self .is_view else ""
1242
- maybe_axis = f" (axis={ self .axis } ) " if hasattr (self , "axis" ) and self .axis != 0 else ""
1310
+ maybe_axis = (
1311
+ (
1312
+ f" (shared obs) "
1313
+ if self .axis == 0
1314
+ else f" (shared var) "
1315
+ if self .axis == 1
1316
+ else f" (shared obs and var) "
1317
+ )
1318
+ if hasattr (self , "axis" )
1319
+ else ""
1320
+ )
1243
1321
descr = f"{ view_of } MuData object with n_obs × n_vars = { n_obs } × { n_vars } { maybe_axis } { backed_at } "
1244
1322
for attr in ["obs" , "var" , "uns" , "obsm" , "varm" , "obsp" , "varp" ]:
1245
1323
if hasattr (self , attr ) and getattr (self , attr ) is not None :
0 commit comments