Skip to content

Commit 9783f74

Browse files
committed
fix reading anndatas with .raw attributes (closes #142)
1 parent 07ca8b0 commit 9783f74

4 files changed

Lines changed: 34 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ and this project adheres to [Semantic Versioning][].
88
[keep a changelog]: https://keepachangelog.com/en/1.1.0/
99
[semantic versioning]: https://semver.org/spec/v2.0.0.html
1010

11+
## [0.3.7]
12+
13+
### Fixed
14+
15+
- MuData objects with modalities containing `.raw` attributes can once again be read from disk.
16+
1117
## [0.3.6]
1218

1319
### Fixed
@@ -156,6 +162,7 @@ To copy the annotations explicitly, you will need to use `pull_obs()` and/or `pu
156162

157163
Initial `mudata` release with `MuData`, previously a part of the `muon` framework.
158164

165+
[0.3.7]: https://github.com/scverse/mudata/releases/tag/v0.3.7
159166
[0.3.6]: https://github.com/scverse/mudata/releases/tag/v0.3.6
160167
[0.3.5]: https://github.com/scverse/mudata/releases/tag/v0.3.5
161168
[0.3.4]: https://github.com/scverse/mudata/releases/tag/v0.3.4

src/mudata/_core/io.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,12 @@ def _write_h5mu(file: h5py.File, mdata: MuData, write_data=True, **kwargs):
8282
if write_data or not adata.isbacked:
8383
write_elem(group, "X", adata.X, dataset_kwargs=kwargs)
8484
if adata.raw is not None:
85-
write_elem(group, "raw", adata.raw)
85+
if not adata.isbacked:
86+
write_elem(group, "raw", adata.raw, dataset_kwargs=kwargs)
87+
else:
88+
rawgrp = group.require_group("raw")
89+
write_elem(rawgrp, "var", adata.raw.var, dataset_kwargs=kwargs)
90+
write_elem(rawgrp, "varm", dict(adata.raw.varm), dataset_kwargs=kwargs)
8691

8792
write_elem(group, "obs", adata.obs, dataset_kwargs=kwargs)
8893
write_elem(group, "var", adata.var, dataset_kwargs=kwargs)
@@ -201,7 +206,12 @@ def write_zarr(
201206
else:
202207
write_elem(group, "X", adata.X, dataset_kwargs=kwargs)
203208
if adata.raw is not None:
204-
write_elem(group, "raw", adata.raw)
209+
if write_data or not adata.isbacked:
210+
write_elem(group, "raw", adata.raw, dataset_kwargs=kwargs)
211+
else:
212+
rawgrp = group.require_group("raw")
213+
write_elem(rawgrp, "var", adata.raw.var, dataset_kwargs=kwargs)
214+
write_elem(rawgrp, "varm", dict(adata.raw.varm), dataset_kwargs=kwargs)
205215

206216
write_elem(group, "obs", adata.obs, dataset_kwargs=kwargs)
207217
write_elem(group, "var", adata.var, dataset_kwargs=kwargs)
@@ -527,16 +537,16 @@ def _read_zarr_mod(g: zarr.Group, manager: MuDataFileManager = None, backed: boo
527537
d["X"] = read_elem(X)
528538
elif k != "raw":
529539
d[k] = read_elem(g[k])
530-
ad = AnnData(**d)
540+
adata = AnnData(**d)
531541
if manager is not None:
532-
ad.file = AnnDataFileManager(ad, Path(g.name).name, manager)
542+
adata.file = AnnDataFileManager(adata, Path(g.name).name, manager)
533543

534544
raw = _read_legacy_raw(
535545
g, d.get("raw"), read_zarr_dataframe, read_elem, attrs=("var", "varm") if backed else ("var", "varm", "X")
536546
)
537547
if raw:
538-
ad._raw = ad.Raw(ad, **raw)
539-
return ad
548+
adata._raw = ad.Raw(adata, **raw)
549+
return adata
540550

541551

542552
def _read_h5mu_mod(g: h5py.Group, manager: MuDataFileManager = None, backed: bool = False) -> dict:
@@ -551,14 +561,14 @@ def _read_h5mu_mod(g: h5py.Group, manager: MuDataFileManager = None, backed: boo
551561
d["X"] = read_elem(X)
552562
elif k != "raw":
553563
d[k] = read_elem(g[k])
554-
ad = AnnData(**d)
564+
adata = AnnData(**d)
555565
if manager is not None:
556-
ad.file = AnnDataFileManager(ad, Path(g.name).name, manager)
566+
adata.file = AnnDataFileManager(adata, Path(g.name).name, manager)
557567

558568
raw = _read_raw(g, attrs=("var", "varm") if backed else ("var", "varm", "X"))
559569
if raw:
560-
ad._raw = ad.Raw(ad, **raw)
561-
return ad
570+
adata._raw = ad.Raw(adata, **raw)
571+
return adata
562572

563573

564574
def read_h5ad(

tests/conftest.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def rng() -> np.random.Generator:
3434

3535

3636
@pytest.fixture
37-
def mdata(rng: np.random.Generator, request: pytest.FixtureRequest):
37+
def mdata(rng: np.random.Generator, request: pytest.FixtureRequest) -> MuData:
3838
axis = getattr(request, "param", 0)
3939
mod1 = AnnData(
4040
np.arange(0, 200, 0.1).reshape(-1, 20), obs=pd.DataFrame(index=rng.choice(150, size=100, replace=False))
@@ -46,6 +46,8 @@ def mdata(rng: np.random.Generator, request: pytest.FixtureRequest):
4646
mod2.var["assert-bool"] = False
4747
mod1.var["assert-boolean-1"] = True
4848
mod2.var["assert-boolean-2"] = False
49+
50+
mod1.raw = mod1[:, :10].copy()
4951
mods = {"mod2": mod2, "mod1": mod1}
5052

5153
attr = "obs" if axis == 0 else "var"

tests/test_view_copy.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,12 +191,14 @@ def _test_view_after_setattr(mdata: md.MuData, mdata_ref: md.MuData, skip: Seque
191191

192192
for k, v in mdata.obsm.items():
193193
if k in mdata_ref.obsm:
194-
assert v.shape[1] == mdata_ref.obsm[k].shape[1]
194+
if v.ndim > 1:
195+
assert v.shape[1] == mdata_ref.obsm[k].shape[1]
195196
if isinstance(v, pd.DataFrame):
196197
assert (v.columns == mdata_ref.obsm[k].columns).all()
197198
for k, v in mdata.varm.items():
198199
if k in mdata_ref.varm:
199-
assert v.shape[1] == mdata_ref.obsm[k].shape[1]
200+
if v.ndim > 1:
201+
assert v.shape[1] == mdata_ref.obsm[k].shape[1]
200202
if isinstance(v, pd.DataFrame):
201203
assert (v.columns == mdata_ref.varm[k].columns).all()
202204

0 commit comments

Comments
 (0)