pyadata-cli 0.5.0__py3-none-any.whl
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.
- adata/__init__.py +10 -0
- adata/cli.py +1139 -0
- adata/commands/__init__.py +8 -0
- adata/commands/concat.py +21 -0
- adata/commands/create.py +93 -0
- adata/commands/export.py +105 -0
- adata/commands/import_data.py +144 -0
- adata/commands/info.py +199 -0
- adata/commands/ls.py +114 -0
- adata/commands/split.py +152 -0
- adata/commands/subset.py +17 -0
- adata/core/__init__.py +1 -0
- adata/core/concat.py +1084 -0
- adata/core/info.py +276 -0
- adata/core/query.py +268 -0
- adata/core/read.py +28 -0
- adata/core/select.py +122 -0
- adata/core/subset.py +767 -0
- adata/elements/__init__.py +10 -0
- adata/elements/read.py +310 -0
- adata/elements/spec.py +125 -0
- adata/elements/strings.py +90 -0
- adata/elements/write.py +255 -0
- adata/formats/__init__.py +1 -0
- adata/formats/array.py +109 -0
- adata/formats/common.py +51 -0
- adata/formats/dataframe.py +234 -0
- adata/formats/image.py +85 -0
- adata/formats/json_data.py +196 -0
- adata/formats/sparse.py +253 -0
- adata/formats/validate.py +180 -0
- adata/info.py +3 -0
- adata/py.typed +0 -0
- adata/read.py +19 -0
- adata/storage/__init__.py +529 -0
- adata/util/__init__.py +1 -0
- adata/util/path.py +9 -0
- pyadata_cli-0.5.0.dist-info/METADATA +135 -0
- pyadata_cli-0.5.0.dist-info/RECORD +42 -0
- pyadata_cli-0.5.0.dist-info/WHEEL +4 -0
- pyadata_cli-0.5.0.dist-info/entry_points.txt +4 -0
- pyadata_cli-0.5.0.dist-info/licenses/LICENSE +21 -0
adata/cli.py
ADDED
|
@@ -0,0 +1,1139 @@
|
|
|
1
|
+
"""CLI for AnnData stores (.h5ad / .zarr) with export and import subcommands."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional, Sequence, List
|
|
5
|
+
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from adata.commands import (
|
|
10
|
+
MERGE_STRATEGIES,
|
|
11
|
+
concat_stores,
|
|
12
|
+
create_store,
|
|
13
|
+
split_store,
|
|
14
|
+
list_store,
|
|
15
|
+
show_info,
|
|
16
|
+
subset_h5ad,
|
|
17
|
+
export_mtx,
|
|
18
|
+
export_npy,
|
|
19
|
+
export_json,
|
|
20
|
+
export_table,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
from adata.commands import export_image as export_image_cmd
|
|
24
|
+
|
|
25
|
+
app = typer.Typer(
|
|
26
|
+
help="Streaming CLI for huge AnnData .h5ad and .zarr stores "
|
|
27
|
+
"(view, ls, subset, split, concat, export, import)."
|
|
28
|
+
)
|
|
29
|
+
# Use stderr for status/progress to keep stdout clean for data output
|
|
30
|
+
# force_terminal=True ensures Rich output is visible even in non-TTY environments
|
|
31
|
+
console = Console(stderr=True, force_terminal=True)
|
|
32
|
+
# Results go to stdout so they can be piped; status and errors stay on stderr.
|
|
33
|
+
out_console = Console()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _version_callback(value: bool) -> None:
|
|
37
|
+
if value:
|
|
38
|
+
from adata import __version__
|
|
39
|
+
|
|
40
|
+
out_console.print(__version__, highlight=False)
|
|
41
|
+
raise typer.Exit()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# Create sub-apps for export and import
|
|
45
|
+
export_app = typer.Typer(help="Export objects from an AnnData store.")
|
|
46
|
+
import_app = typer.Typer(help="Import objects into an AnnData store.")
|
|
47
|
+
app.add_typer(export_app, name="export")
|
|
48
|
+
app.add_typer(import_app, name="import")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@app.callback()
|
|
52
|
+
def _root(
|
|
53
|
+
version: bool = typer.Option(
|
|
54
|
+
False,
|
|
55
|
+
"--version",
|
|
56
|
+
"-V",
|
|
57
|
+
help="Show the installed version and exit.",
|
|
58
|
+
callback=_version_callback,
|
|
59
|
+
is_eager=True,
|
|
60
|
+
),
|
|
61
|
+
) -> None:
|
|
62
|
+
"""Streaming CLI for huge AnnData .h5ad and .zarr stores."""
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# ============================================================================
|
|
66
|
+
# VIEW command
|
|
67
|
+
# ============================================================================
|
|
68
|
+
@app.command("view")
|
|
69
|
+
def view(
|
|
70
|
+
file: Path = typer.Argument(
|
|
71
|
+
...,
|
|
72
|
+
help="Path to the .h5ad/.zarr store",
|
|
73
|
+
exists=True,
|
|
74
|
+
readable=True,
|
|
75
|
+
dir_okay=True,
|
|
76
|
+
file_okay=True,
|
|
77
|
+
),
|
|
78
|
+
entry: Optional[str] = typer.Argument(
|
|
79
|
+
None,
|
|
80
|
+
help="Entry path to inspect (e.g., 'obsm/X_pca', 'X', 'uns')",
|
|
81
|
+
),
|
|
82
|
+
tree: bool = typer.Option(
|
|
83
|
+
False,
|
|
84
|
+
"--tree",
|
|
85
|
+
"-t",
|
|
86
|
+
help="Show a tree of all entries",
|
|
87
|
+
),
|
|
88
|
+
depth: int = typer.Option(
|
|
89
|
+
None,
|
|
90
|
+
"--depth",
|
|
91
|
+
"-d",
|
|
92
|
+
help="Maximum recursion depth for tree display (only with --tree)",
|
|
93
|
+
),
|
|
94
|
+
) -> None:
|
|
95
|
+
"""
|
|
96
|
+
Show high-level information about an AnnData store.
|
|
97
|
+
|
|
98
|
+
Use --tree to see a tree of all entries.
|
|
99
|
+
Pass an entry path to inspect a specific entry in detail.
|
|
100
|
+
|
|
101
|
+
Examples:
|
|
102
|
+
adata view data.h5ad
|
|
103
|
+
adata view --tree data.h5ad
|
|
104
|
+
adata view data.h5ad obsm/X_pca
|
|
105
|
+
"""
|
|
106
|
+
try:
|
|
107
|
+
show_info(
|
|
108
|
+
file,
|
|
109
|
+
console,
|
|
110
|
+
show_types=tree,
|
|
111
|
+
depth=depth,
|
|
112
|
+
entry_path=entry,
|
|
113
|
+
out_console=out_console,
|
|
114
|
+
)
|
|
115
|
+
except Exception as e:
|
|
116
|
+
console.print(f"[bold red]Error:[/] {e}")
|
|
117
|
+
raise typer.Exit(code=1)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@app.command("info", hidden=True)
|
|
121
|
+
def info(
|
|
122
|
+
file: Path = typer.Argument(
|
|
123
|
+
...,
|
|
124
|
+
help="Path to the .h5ad/.zarr store",
|
|
125
|
+
exists=True,
|
|
126
|
+
readable=True,
|
|
127
|
+
dir_okay=True,
|
|
128
|
+
file_okay=True,
|
|
129
|
+
),
|
|
130
|
+
entry: Optional[str] = typer.Argument(
|
|
131
|
+
None,
|
|
132
|
+
help="Entry path to inspect (e.g., 'obsm/X_pca', 'X', 'uns')",
|
|
133
|
+
),
|
|
134
|
+
tree: bool = typer.Option(False, "--tree", "-t", help="Show a tree of all entries"),
|
|
135
|
+
depth: int = typer.Option(
|
|
136
|
+
None, "--depth", "-d", help="Maximum recursion depth for tree display"
|
|
137
|
+
),
|
|
138
|
+
) -> None:
|
|
139
|
+
"""Deprecated alias for 'view'. Removed in 1.0.0."""
|
|
140
|
+
console.print(
|
|
141
|
+
"[yellow]Warning:[/] 'info' is deprecated and will be removed in 1.0.0; "
|
|
142
|
+
"use 'view' instead.",
|
|
143
|
+
)
|
|
144
|
+
view(file=file, entry=entry, tree=tree, depth=depth)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
# ============================================================================
|
|
148
|
+
# CREATE command
|
|
149
|
+
# ============================================================================
|
|
150
|
+
@app.command("create")
|
|
151
|
+
def create(
|
|
152
|
+
output: Path = typer.Argument(
|
|
153
|
+
...,
|
|
154
|
+
help="Path of the store to create (.h5ad or .zarr)",
|
|
155
|
+
dir_okay=True,
|
|
156
|
+
file_okay=True,
|
|
157
|
+
),
|
|
158
|
+
n_obs: Optional[int] = typer.Option(
|
|
159
|
+
None, "--n-obs", help="Number of observations (cells)"
|
|
160
|
+
),
|
|
161
|
+
n_var: Optional[int] = typer.Option(
|
|
162
|
+
None, "--n-var", help="Number of variables (genes)"
|
|
163
|
+
),
|
|
164
|
+
obs_names: Optional[Path] = typer.Option(
|
|
165
|
+
None,
|
|
166
|
+
"--obs-names",
|
|
167
|
+
help="File of obs names, one per line (sets --n-obs)",
|
|
168
|
+
exists=True,
|
|
169
|
+
readable=True,
|
|
170
|
+
),
|
|
171
|
+
var_names: Optional[Path] = typer.Option(
|
|
172
|
+
None,
|
|
173
|
+
"--var-names",
|
|
174
|
+
help="File of var names, one per line (sets --n-var)",
|
|
175
|
+
exists=True,
|
|
176
|
+
readable=True,
|
|
177
|
+
),
|
|
178
|
+
zarr_format: Optional[int] = typer.Option(
|
|
179
|
+
None, "--zarr-format", help="Zarr spec version to write (2 or 3)"
|
|
180
|
+
),
|
|
181
|
+
force: bool = typer.Option(
|
|
182
|
+
False, "--force", "-f", help="Overwrite an existing store"
|
|
183
|
+
),
|
|
184
|
+
) -> None:
|
|
185
|
+
"""
|
|
186
|
+
Create a new, empty AnnData store.
|
|
187
|
+
|
|
188
|
+
The result is a valid AnnData object straight away; fill it in with
|
|
189
|
+
`adata import`. Give each axis either a size or a file of names.
|
|
190
|
+
|
|
191
|
+
Examples:
|
|
192
|
+
adata create out.h5ad --n-obs 5000 --n-var 2000
|
|
193
|
+
adata create out.zarr --obs-names cells.txt --var-names genes.txt
|
|
194
|
+
adata import sparse out.h5ad X counts.mtx --inplace
|
|
195
|
+
"""
|
|
196
|
+
if zarr_format is not None and zarr_format not in (2, 3):
|
|
197
|
+
console.print("[bold red]Error:[/] --zarr-format must be 2 or 3.")
|
|
198
|
+
raise typer.Exit(code=1)
|
|
199
|
+
|
|
200
|
+
try:
|
|
201
|
+
create_store(
|
|
202
|
+
output,
|
|
203
|
+
console,
|
|
204
|
+
n_obs=n_obs,
|
|
205
|
+
n_var=n_var,
|
|
206
|
+
obs_names=obs_names,
|
|
207
|
+
var_names=var_names,
|
|
208
|
+
zarr_format=zarr_format,
|
|
209
|
+
force=force,
|
|
210
|
+
)
|
|
211
|
+
except Exception as e:
|
|
212
|
+
console.print(f"[bold red]Error:[/] {e}")
|
|
213
|
+
raise typer.Exit(code=1)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
# ============================================================================
|
|
217
|
+
# LS command
|
|
218
|
+
# ============================================================================
|
|
219
|
+
@app.command("ls")
|
|
220
|
+
def ls(
|
|
221
|
+
file: Path = typer.Argument(
|
|
222
|
+
...,
|
|
223
|
+
help="Path to any .h5ad/.zarr/.h5 store",
|
|
224
|
+
exists=True,
|
|
225
|
+
readable=True,
|
|
226
|
+
dir_okay=True,
|
|
227
|
+
file_okay=True,
|
|
228
|
+
),
|
|
229
|
+
entry: Optional[str] = typer.Argument(
|
|
230
|
+
None, help="Only list below this path (e.g. 'obsm', 'uns/spatial')"
|
|
231
|
+
),
|
|
232
|
+
depth: Optional[int] = typer.Option(
|
|
233
|
+
None, "--depth", "-d", help="Maximum depth to descend"
|
|
234
|
+
),
|
|
235
|
+
long: bool = typer.Option(
|
|
236
|
+
False, "--long", "-l", help="Show type, shape, dtype and encoding"
|
|
237
|
+
),
|
|
238
|
+
plain: bool = typer.Option(
|
|
239
|
+
False, "-1", "--plain", help="One bare path per line, for piping"
|
|
240
|
+
),
|
|
241
|
+
) -> None:
|
|
242
|
+
"""
|
|
243
|
+
List the contents of any HDF5 or Zarr store.
|
|
244
|
+
|
|
245
|
+
Makes no AnnData assumptions, so it also works on .loom and plain .h5
|
|
246
|
+
files. Use -1 to pipe paths into other tools.
|
|
247
|
+
|
|
248
|
+
Examples:
|
|
249
|
+
adata ls data.h5ad
|
|
250
|
+
adata ls data.h5ad --long
|
|
251
|
+
adata ls data.h5ad obsm --depth 1
|
|
252
|
+
adata ls data.h5ad -1 | grep spatial
|
|
253
|
+
"""
|
|
254
|
+
try:
|
|
255
|
+
list_store(
|
|
256
|
+
file,
|
|
257
|
+
out_console,
|
|
258
|
+
entry_path=entry,
|
|
259
|
+
depth=depth,
|
|
260
|
+
long=long,
|
|
261
|
+
plain=plain,
|
|
262
|
+
)
|
|
263
|
+
except Exception as e:
|
|
264
|
+
console.print(f"[bold red]Error:[/] {e}")
|
|
265
|
+
raise typer.Exit(code=1)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
# ============================================================================
|
|
269
|
+
# SUBSET command
|
|
270
|
+
# ============================================================================
|
|
271
|
+
@app.command()
|
|
272
|
+
def subset(
|
|
273
|
+
file: Path = typer.Argument(
|
|
274
|
+
...,
|
|
275
|
+
help="Input .h5ad/.zarr",
|
|
276
|
+
exists=True,
|
|
277
|
+
readable=True,
|
|
278
|
+
dir_okay=True,
|
|
279
|
+
file_okay=True,
|
|
280
|
+
),
|
|
281
|
+
output: Optional[Path] = typer.Option(
|
|
282
|
+
None,
|
|
283
|
+
"--output",
|
|
284
|
+
"-o",
|
|
285
|
+
help="Output .h5ad/.zarr path. Required unless --inplace.",
|
|
286
|
+
dir_okay=True,
|
|
287
|
+
file_okay=True,
|
|
288
|
+
),
|
|
289
|
+
inplace: bool = typer.Option(
|
|
290
|
+
False,
|
|
291
|
+
"--inplace",
|
|
292
|
+
help="Modify source file directly.",
|
|
293
|
+
),
|
|
294
|
+
obs: Optional[Path] = typer.Option(
|
|
295
|
+
None,
|
|
296
|
+
"--obs",
|
|
297
|
+
help="File with obs names (one per line)",
|
|
298
|
+
exists=True,
|
|
299
|
+
readable=True,
|
|
300
|
+
),
|
|
301
|
+
var: Optional[Path] = typer.Option(
|
|
302
|
+
None,
|
|
303
|
+
"--var",
|
|
304
|
+
help="File with var names (one per line)",
|
|
305
|
+
exists=True,
|
|
306
|
+
readable=True,
|
|
307
|
+
),
|
|
308
|
+
obs_query: Optional[str] = typer.Option(
|
|
309
|
+
None,
|
|
310
|
+
"--obs-query",
|
|
311
|
+
"-q",
|
|
312
|
+
help="Keep obs matching an expression, e.g. \"cluster == Cortex_2\"",
|
|
313
|
+
),
|
|
314
|
+
var_query: Optional[str] = typer.Option(
|
|
315
|
+
None,
|
|
316
|
+
"--var-query",
|
|
317
|
+
help="Keep var matching an expression, e.g. \"highly_variable == True\"",
|
|
318
|
+
),
|
|
319
|
+
chunk_rows: int = typer.Option(
|
|
320
|
+
1024,
|
|
321
|
+
"--chunk",
|
|
322
|
+
"-C",
|
|
323
|
+
"--chunk-rows",
|
|
324
|
+
"-r",
|
|
325
|
+
help="Row chunk size for dense matrices",
|
|
326
|
+
),
|
|
327
|
+
zarr_format: Optional[int] = typer.Option(
|
|
328
|
+
None,
|
|
329
|
+
"--zarr-format",
|
|
330
|
+
help="Zarr spec version to write (defaults to the source store's)",
|
|
331
|
+
),
|
|
332
|
+
) -> None:
|
|
333
|
+
"""
|
|
334
|
+
Subset an AnnData store by obs and/or var.
|
|
335
|
+
|
|
336
|
+
Select either by name list (--obs/--var) or by expression
|
|
337
|
+
(--obs-query/--var-query). Expressions support ==, !=, <, <=, >, >=, in,
|
|
338
|
+
not in, and, or, not, and parentheses.
|
|
339
|
+
|
|
340
|
+
Examples:
|
|
341
|
+
adata subset data.h5ad -o out.h5ad --obs barcodes.txt
|
|
342
|
+
adata subset data.h5ad -o out.h5ad --obs-query "cluster == Cortex_2"
|
|
343
|
+
adata subset data.h5ad -o out.h5ad -q "n_counts > 1000 and cluster in A,B"
|
|
344
|
+
adata subset data.h5ad -o out.h5ad --var-query "highly_variable == True"
|
|
345
|
+
"""
|
|
346
|
+
if obs is None and var is None and obs_query is None and var_query is None:
|
|
347
|
+
console.print(
|
|
348
|
+
"[bold red]Error:[/] Provide at least one of --obs, --var, "
|
|
349
|
+
"--obs-query or --var-query.",
|
|
350
|
+
)
|
|
351
|
+
raise typer.Exit(code=1)
|
|
352
|
+
|
|
353
|
+
if not inplace and output is None:
|
|
354
|
+
console.print(
|
|
355
|
+
"[bold red]Error:[/] Output file is required. "
|
|
356
|
+
"Use --output/-o or --inplace.",
|
|
357
|
+
)
|
|
358
|
+
raise typer.Exit(code=1)
|
|
359
|
+
|
|
360
|
+
try:
|
|
361
|
+
subset_h5ad(
|
|
362
|
+
file=file,
|
|
363
|
+
output=output,
|
|
364
|
+
obs_file=obs,
|
|
365
|
+
var_file=var,
|
|
366
|
+
chunk_rows=chunk_rows,
|
|
367
|
+
console=console,
|
|
368
|
+
inplace=inplace,
|
|
369
|
+
obs_query=obs_query,
|
|
370
|
+
var_query=var_query,
|
|
371
|
+
zarr_format=zarr_format,
|
|
372
|
+
)
|
|
373
|
+
except Exception as e:
|
|
374
|
+
console.print(f"[bold red]Error:[/] {e}")
|
|
375
|
+
raise typer.Exit(code=1)
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
# ============================================================================
|
|
379
|
+
# CONCAT command
|
|
380
|
+
# ============================================================================
|
|
381
|
+
@app.command("concat")
|
|
382
|
+
def concat(
|
|
383
|
+
files: List[Path] = typer.Argument(
|
|
384
|
+
...,
|
|
385
|
+
help="Two or more .h5ad/.zarr stores to concatenate",
|
|
386
|
+
exists=True,
|
|
387
|
+
readable=True,
|
|
388
|
+
dir_okay=True,
|
|
389
|
+
file_okay=True,
|
|
390
|
+
),
|
|
391
|
+
output: Path = typer.Option(
|
|
392
|
+
..., "--output", "-o", help="Output .h5ad/.zarr path", dir_okay=True
|
|
393
|
+
),
|
|
394
|
+
join: str = typer.Option(
|
|
395
|
+
"inner",
|
|
396
|
+
"--join",
|
|
397
|
+
"-j",
|
|
398
|
+
help="Align var by intersection ('inner') or union ('outer')",
|
|
399
|
+
),
|
|
400
|
+
label: Optional[str] = typer.Option(
|
|
401
|
+
None, "--label", help="Add an obs column recording which input each cell came from"
|
|
402
|
+
),
|
|
403
|
+
keys: Optional[str] = typer.Option(
|
|
404
|
+
None,
|
|
405
|
+
"--keys",
|
|
406
|
+
help="Comma separated names for the inputs (defaults to their filenames)",
|
|
407
|
+
),
|
|
408
|
+
index_unique: Optional[str] = typer.Option(
|
|
409
|
+
None,
|
|
410
|
+
"--index-unique",
|
|
411
|
+
help="Delimiter used to suffix obs names with their key, e.g. '-'",
|
|
412
|
+
),
|
|
413
|
+
merge: Optional[str] = typer.Option(
|
|
414
|
+
None,
|
|
415
|
+
"--merge",
|
|
416
|
+
help="How to reconcile var columns: same, unique, first, only (default: drop)",
|
|
417
|
+
),
|
|
418
|
+
uns_merge: Optional[str] = typer.Option(
|
|
419
|
+
None,
|
|
420
|
+
"--uns-merge",
|
|
421
|
+
help="How to reconcile uns: same, unique, first, only (default: drop)",
|
|
422
|
+
),
|
|
423
|
+
fill_value: float = typer.Option(
|
|
424
|
+
0.0, "--fill-value", help="Value for dense cells introduced by an outer join"
|
|
425
|
+
),
|
|
426
|
+
chunk_rows: int = typer.Option(
|
|
427
|
+
1024, "--chunk", "-C", help="Row chunk size while streaming"
|
|
428
|
+
),
|
|
429
|
+
zarr_format: Optional[int] = typer.Option(
|
|
430
|
+
None, "--zarr-format", help="Zarr spec version to write (2 or 3)"
|
|
431
|
+
),
|
|
432
|
+
) -> None:
|
|
433
|
+
"""
|
|
434
|
+
Concatenate stores along the obs axis.
|
|
435
|
+
|
|
436
|
+
Streams each input in turn, so memory use is set by --chunk rather than by
|
|
437
|
+
the size of the inputs. obsp, varp and raw are not carried over.
|
|
438
|
+
|
|
439
|
+
Examples:
|
|
440
|
+
adata concat a.h5ad b.h5ad -o merged.h5ad
|
|
441
|
+
adata concat *.h5ad -o merged.h5ad --join outer --label sample
|
|
442
|
+
adata concat a.h5ad b.h5ad -o m.h5ad --keys a,b --index-unique - --uns-merge same
|
|
443
|
+
"""
|
|
444
|
+
for name, value in (("--merge", merge), ("--uns-merge", uns_merge)):
|
|
445
|
+
if value is not None and value not in MERGE_STRATEGIES:
|
|
446
|
+
console.print(
|
|
447
|
+
f"[bold red]Error:[/] {name} must be one of: "
|
|
448
|
+
f"{', '.join(MERGE_STRATEGIES)}"
|
|
449
|
+
)
|
|
450
|
+
raise typer.Exit(code=1)
|
|
451
|
+
|
|
452
|
+
try:
|
|
453
|
+
concat_stores(
|
|
454
|
+
files,
|
|
455
|
+
output,
|
|
456
|
+
console,
|
|
457
|
+
join=join,
|
|
458
|
+
label=label,
|
|
459
|
+
keys=[k.strip() for k in keys.split(",")] if keys else None,
|
|
460
|
+
index_unique=index_unique,
|
|
461
|
+
merge=merge,
|
|
462
|
+
uns_merge=uns_merge,
|
|
463
|
+
fill_value=fill_value,
|
|
464
|
+
chunk_rows=chunk_rows,
|
|
465
|
+
zarr_format=zarr_format,
|
|
466
|
+
)
|
|
467
|
+
except Exception as e:
|
|
468
|
+
console.print(f"[bold red]Error:[/] {e}")
|
|
469
|
+
raise typer.Exit(code=1)
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
# ============================================================================
|
|
473
|
+
# SPLIT command
|
|
474
|
+
# ============================================================================
|
|
475
|
+
@app.command("split")
|
|
476
|
+
def split(
|
|
477
|
+
file: Path = typer.Argument(
|
|
478
|
+
...,
|
|
479
|
+
help="Input .h5ad/.zarr",
|
|
480
|
+
exists=True,
|
|
481
|
+
readable=True,
|
|
482
|
+
dir_okay=True,
|
|
483
|
+
file_okay=True,
|
|
484
|
+
),
|
|
485
|
+
by: str = typer.Option(
|
|
486
|
+
..., "--by", "-b", help="Column to split on (e.g. 'sample', 'cell_type')"
|
|
487
|
+
),
|
|
488
|
+
output_dir: Path = typer.Option(
|
|
489
|
+
..., "--output-dir", "-o", help="Directory to write the split stores into"
|
|
490
|
+
),
|
|
491
|
+
axis: str = typer.Option(
|
|
492
|
+
"obs", "--axis", help="Axis the column belongs to ('obs' or 'var')"
|
|
493
|
+
),
|
|
494
|
+
suffix: Optional[str] = typer.Option(
|
|
495
|
+
None,
|
|
496
|
+
"--suffix",
|
|
497
|
+
help="Output extension (defaults to the source store's format)",
|
|
498
|
+
),
|
|
499
|
+
min_size: int = typer.Option(
|
|
500
|
+
1, "--min-size", help="Skip groups with fewer rows than this"
|
|
501
|
+
),
|
|
502
|
+
manifest: bool = typer.Option(
|
|
503
|
+
True, "--manifest/--no-manifest", help="Write a CSV manifest of the outputs"
|
|
504
|
+
),
|
|
505
|
+
dry_run: bool = typer.Option(
|
|
506
|
+
False, "--dry-run", help="Show what would be written without writing it"
|
|
507
|
+
),
|
|
508
|
+
chunk_rows: int = typer.Option(
|
|
509
|
+
1024, "--chunk", "-C", help="Row chunk size for dense matrices"
|
|
510
|
+
),
|
|
511
|
+
zarr_format: Optional[int] = typer.Option(
|
|
512
|
+
None,
|
|
513
|
+
"--zarr-format",
|
|
514
|
+
help="Zarr spec version to write (defaults to the source store's)",
|
|
515
|
+
),
|
|
516
|
+
) -> None:
|
|
517
|
+
"""
|
|
518
|
+
Split a store into one file per distinct value of a column.
|
|
519
|
+
|
|
520
|
+
Streams the source, so it works on stores far larger than memory.
|
|
521
|
+
|
|
522
|
+
Examples:
|
|
523
|
+
adata split data.h5ad --by sample -o per_sample/
|
|
524
|
+
adata split data.h5ad --by cell_type -o clusters/ --dry-run
|
|
525
|
+
adata split data.h5ad --by cluster -o out/ --min-size 50
|
|
526
|
+
"""
|
|
527
|
+
if axis not in ("obs", "var"):
|
|
528
|
+
console.print("[bold red]Error:[/] --axis must be 'obs' or 'var'.")
|
|
529
|
+
raise typer.Exit(code=1)
|
|
530
|
+
|
|
531
|
+
if zarr_format is not None and zarr_format not in (2, 3):
|
|
532
|
+
console.print("[bold red]Error:[/] --zarr-format must be 2 or 3.")
|
|
533
|
+
raise typer.Exit(code=1)
|
|
534
|
+
|
|
535
|
+
try:
|
|
536
|
+
split_store(
|
|
537
|
+
file=file,
|
|
538
|
+
column=by,
|
|
539
|
+
output_dir=output_dir,
|
|
540
|
+
console=console,
|
|
541
|
+
axis=axis,
|
|
542
|
+
suffix=suffix,
|
|
543
|
+
dry_run=dry_run,
|
|
544
|
+
manifest=manifest,
|
|
545
|
+
min_size=min_size,
|
|
546
|
+
chunk_rows=chunk_rows,
|
|
547
|
+
zarr_format=zarr_format,
|
|
548
|
+
)
|
|
549
|
+
except Exception as e:
|
|
550
|
+
console.print(f"[bold red]Error:[/] {e}")
|
|
551
|
+
raise typer.Exit(code=1)
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
# ============================================================================
|
|
555
|
+
# EXPORT subcommands
|
|
556
|
+
# ============================================================================
|
|
557
|
+
@export_app.command("dataframe")
|
|
558
|
+
def export_dataframe(
|
|
559
|
+
file: Path = typer.Argument(
|
|
560
|
+
...,
|
|
561
|
+
help="Path to the .h5ad/.zarr store",
|
|
562
|
+
exists=True,
|
|
563
|
+
readable=True,
|
|
564
|
+
dir_okay=True,
|
|
565
|
+
file_okay=True,
|
|
566
|
+
),
|
|
567
|
+
entry: str = typer.Argument(
|
|
568
|
+
..., help="Path of the dataframe to export (e.g. 'obs', 'var', 'raw/var')"
|
|
569
|
+
),
|
|
570
|
+
output: Path = typer.Option(
|
|
571
|
+
None, "--output", "-o", writable=True, help="Output CSV file path"
|
|
572
|
+
),
|
|
573
|
+
columns: Optional[str] = typer.Option(
|
|
574
|
+
None,
|
|
575
|
+
"--columns",
|
|
576
|
+
"-c",
|
|
577
|
+
help="Comma separated column names to include",
|
|
578
|
+
),
|
|
579
|
+
chunk_rows: int = typer.Option(
|
|
580
|
+
10_000,
|
|
581
|
+
"--chunk",
|
|
582
|
+
"-C",
|
|
583
|
+
"--chunk-rows",
|
|
584
|
+
"-r",
|
|
585
|
+
help="Number of rows to read per chunk",
|
|
586
|
+
),
|
|
587
|
+
head: Optional[int] = typer.Option(
|
|
588
|
+
None, "--head", "-n", help="Output only the first n entries"
|
|
589
|
+
),
|
|
590
|
+
) -> None:
|
|
591
|
+
"""
|
|
592
|
+
Export any dataframe-encoded group to CSV.
|
|
593
|
+
|
|
594
|
+
Not limited to obs/var -- any path holding a dataframe works, including
|
|
595
|
+
ones under raw/, obsm/ or uns/.
|
|
596
|
+
|
|
597
|
+
Examples:
|
|
598
|
+
adata export dataframe data.h5ad obs --output obs.csv
|
|
599
|
+
adata export dataframe data.h5ad var --columns gene_id,mean
|
|
600
|
+
adata export dataframe data.h5ad raw/var --head 100
|
|
601
|
+
"""
|
|
602
|
+
|
|
603
|
+
col_list: Optional[List[str]] = None
|
|
604
|
+
if columns:
|
|
605
|
+
col_list = [col.strip() for col in columns.split(",") if col.strip()]
|
|
606
|
+
|
|
607
|
+
try:
|
|
608
|
+
export_table(
|
|
609
|
+
file=file,
|
|
610
|
+
axis=entry,
|
|
611
|
+
columns=col_list,
|
|
612
|
+
out=output,
|
|
613
|
+
chunk_rows=chunk_rows,
|
|
614
|
+
head=head,
|
|
615
|
+
console=console,
|
|
616
|
+
)
|
|
617
|
+
except Exception as e:
|
|
618
|
+
console.print(f"[bold red]Error:[/] {e}")
|
|
619
|
+
raise typer.Exit(code=1)
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
@export_app.command("array")
|
|
623
|
+
def export_array(
|
|
624
|
+
file: Path = typer.Argument(
|
|
625
|
+
...,
|
|
626
|
+
help="Path to the .h5ad/.zarr store",
|
|
627
|
+
exists=True,
|
|
628
|
+
readable=True,
|
|
629
|
+
dir_okay=True,
|
|
630
|
+
file_okay=True,
|
|
631
|
+
),
|
|
632
|
+
entry: str = typer.Argument(
|
|
633
|
+
..., help="Entry path to export (e.g., 'obsm/X_pca', 'varm/PCs', 'X')"
|
|
634
|
+
),
|
|
635
|
+
output: Optional[Path] = typer.Option(
|
|
636
|
+
None,
|
|
637
|
+
"--output",
|
|
638
|
+
"-o",
|
|
639
|
+
help="Output .npy file path (defaults to stdout)",
|
|
640
|
+
writable=True,
|
|
641
|
+
),
|
|
642
|
+
chunk_elements: int = typer.Option(
|
|
643
|
+
100_000,
|
|
644
|
+
"--chunk",
|
|
645
|
+
"-C",
|
|
646
|
+
help="Number of elements to read per chunk",
|
|
647
|
+
),
|
|
648
|
+
) -> None:
|
|
649
|
+
"""
|
|
650
|
+
Export a dense array or matrix to NumPy .npy format.
|
|
651
|
+
|
|
652
|
+
Writes to stdout when no --output is given. Writing to a file streams in
|
|
653
|
+
chunks; stdout is not seekable, so that path holds the array in memory.
|
|
654
|
+
|
|
655
|
+
Examples:
|
|
656
|
+
adata export array data.h5ad obsm/X_pca -o pca.npy
|
|
657
|
+
adata export array data.h5ad X -o matrix.npy
|
|
658
|
+
adata export array data.h5ad obsm/X_umap > umap.npy
|
|
659
|
+
"""
|
|
660
|
+
|
|
661
|
+
try:
|
|
662
|
+
export_npy(
|
|
663
|
+
file=file,
|
|
664
|
+
obj=entry,
|
|
665
|
+
out=output,
|
|
666
|
+
chunk_elements=chunk_elements,
|
|
667
|
+
console=console,
|
|
668
|
+
)
|
|
669
|
+
except Exception as e:
|
|
670
|
+
console.print(f"[bold red]Error:[/] {e}")
|
|
671
|
+
raise typer.Exit(code=1)
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
@export_app.command("sparse")
|
|
675
|
+
def export_sparse(
|
|
676
|
+
file: Path = typer.Argument(
|
|
677
|
+
...,
|
|
678
|
+
help="Path to the .h5ad/.zarr store",
|
|
679
|
+
exists=True,
|
|
680
|
+
readable=True,
|
|
681
|
+
dir_okay=True,
|
|
682
|
+
file_okay=True,
|
|
683
|
+
),
|
|
684
|
+
entry: str = typer.Argument(
|
|
685
|
+
..., help="Entry path to export (e.g., 'X', 'layers/counts')"
|
|
686
|
+
),
|
|
687
|
+
output: Optional[Path] = typer.Option(
|
|
688
|
+
None,
|
|
689
|
+
"--output",
|
|
690
|
+
"-o",
|
|
691
|
+
writable=True,
|
|
692
|
+
help="Output .mtx file path (defaults to stdout)",
|
|
693
|
+
),
|
|
694
|
+
head: Optional[int] = typer.Option(
|
|
695
|
+
None, "--head", "-n", help="Output only the first n entries of mtx file"
|
|
696
|
+
),
|
|
697
|
+
chunk_elements: int = typer.Option(
|
|
698
|
+
1_000,
|
|
699
|
+
"--chunk",
|
|
700
|
+
"-C",
|
|
701
|
+
help="Number of rows/columns (depends on compression format) to process per chunk",
|
|
702
|
+
),
|
|
703
|
+
in_memory: bool = typer.Option(
|
|
704
|
+
False,
|
|
705
|
+
"--in-memory",
|
|
706
|
+
"-m",
|
|
707
|
+
help="Load the entire sparse matrix into memory before exporting (may be faster for small matrices)",
|
|
708
|
+
),
|
|
709
|
+
) -> None:
|
|
710
|
+
"""
|
|
711
|
+
Export a sparse matrix (CSR/CSC) to Matrix Market (.mtx) format.
|
|
712
|
+
|
|
713
|
+
Examples:
|
|
714
|
+
h5ad export sparse data.h5ad X matrix.mtx
|
|
715
|
+
h5ad export sparse data.h5ad layers/counts counts.mtx
|
|
716
|
+
h5ad export sparse data.h5ad X --head 100
|
|
717
|
+
"""
|
|
718
|
+
|
|
719
|
+
try:
|
|
720
|
+
export_mtx(
|
|
721
|
+
file=file,
|
|
722
|
+
obj=entry,
|
|
723
|
+
out=output,
|
|
724
|
+
head=head,
|
|
725
|
+
chunk_elements=chunk_elements,
|
|
726
|
+
in_memory=in_memory,
|
|
727
|
+
console=console,
|
|
728
|
+
)
|
|
729
|
+
except Exception as e:
|
|
730
|
+
console.print(f"[bold red]Error:[/] {e}")
|
|
731
|
+
raise typer.Exit(code=1)
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
@export_app.command("dict")
|
|
735
|
+
def export_dict(
|
|
736
|
+
file: Path = typer.Argument(
|
|
737
|
+
...,
|
|
738
|
+
help="Path to the .h5ad/.zarr store",
|
|
739
|
+
exists=True,
|
|
740
|
+
readable=True,
|
|
741
|
+
dir_okay=True,
|
|
742
|
+
file_okay=True,
|
|
743
|
+
),
|
|
744
|
+
entry: str = typer.Argument(
|
|
745
|
+
..., help="Entry path to export (e.g., 'uns', 'uns/colors')"
|
|
746
|
+
),
|
|
747
|
+
output_arg: Optional[Path] = typer.Argument(None, help="Output .json file path"),
|
|
748
|
+
output: Optional[Path] = typer.Option(
|
|
749
|
+
None, "--output", "-o", help="Output .json file path"
|
|
750
|
+
),
|
|
751
|
+
max_elements: int = typer.Option(
|
|
752
|
+
100_000,
|
|
753
|
+
"--max-elements",
|
|
754
|
+
help="Maximum array elements for JSON export",
|
|
755
|
+
),
|
|
756
|
+
include_attrs: bool = typer.Option(
|
|
757
|
+
False, "--include-attrs", help="Include HDF5 attributes in JSON export"
|
|
758
|
+
),
|
|
759
|
+
) -> None:
|
|
760
|
+
"""
|
|
761
|
+
Export a dict/group or scalar to JSON format.
|
|
762
|
+
|
|
763
|
+
Examples:
|
|
764
|
+
h5ad export dict data.h5ad uns metadata.json
|
|
765
|
+
h5ad export dict data.h5ad uns/colors colors.json
|
|
766
|
+
"""
|
|
767
|
+
|
|
768
|
+
try:
|
|
769
|
+
out_path = output if output is not None else output_arg
|
|
770
|
+
export_json(
|
|
771
|
+
file=file,
|
|
772
|
+
obj=entry,
|
|
773
|
+
out=out_path,
|
|
774
|
+
max_elements=max_elements,
|
|
775
|
+
include_attrs=include_attrs,
|
|
776
|
+
console=console,
|
|
777
|
+
)
|
|
778
|
+
except Exception as e:
|
|
779
|
+
console.print(f"[bold red]Error:[/] {e}")
|
|
780
|
+
raise typer.Exit(code=1)
|
|
781
|
+
|
|
782
|
+
|
|
783
|
+
@export_app.command("image")
|
|
784
|
+
def export_image(
|
|
785
|
+
file: Path = typer.Argument(
|
|
786
|
+
...,
|
|
787
|
+
help="Path to the .h5ad/.zarr store",
|
|
788
|
+
exists=True,
|
|
789
|
+
readable=True,
|
|
790
|
+
dir_okay=True,
|
|
791
|
+
file_okay=True,
|
|
792
|
+
),
|
|
793
|
+
entry: str = typer.Argument(..., help="Entry path to export (2D or 3D array)"),
|
|
794
|
+
output: Optional[Path] = typer.Option(
|
|
795
|
+
None, "--output", "-o", help="Output image file (.png, .jpg, .tiff)"
|
|
796
|
+
),
|
|
797
|
+
) -> None:
|
|
798
|
+
"""
|
|
799
|
+
Export an image-like array to PNG/JPG/TIFF format.
|
|
800
|
+
|
|
801
|
+
The array should be 2D (H,W) or 3D (H,W,C) with C in {1,3,4}.
|
|
802
|
+
|
|
803
|
+
Examples:
|
|
804
|
+
h5ad export image data.h5ad uns/spatial/image tissue.png
|
|
805
|
+
"""
|
|
806
|
+
|
|
807
|
+
try:
|
|
808
|
+
export_image_cmd(file=file, obj=entry, out=output, console=console)
|
|
809
|
+
except Exception as e:
|
|
810
|
+
console.print(f"[bold red]Error:[/] {e}")
|
|
811
|
+
raise typer.Exit(code=1)
|
|
812
|
+
|
|
813
|
+
|
|
814
|
+
# ============================================================================
|
|
815
|
+
# IMPORT subcommands
|
|
816
|
+
# ============================================================================
|
|
817
|
+
def _get_target_file(file: Path, output: Optional[Path], inplace: bool) -> Path:
|
|
818
|
+
"""Determine target path and copy/convert if needed."""
|
|
819
|
+
from adata.commands.import_data import _prepare_target_path
|
|
820
|
+
|
|
821
|
+
return _prepare_target_path(file, output, inplace, console)
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
@import_app.command("dataframe")
|
|
825
|
+
def import_dataframe(
|
|
826
|
+
file: Path = typer.Argument(
|
|
827
|
+
...,
|
|
828
|
+
help="Path to the source .h5ad/.zarr store",
|
|
829
|
+
exists=True,
|
|
830
|
+
readable=True,
|
|
831
|
+
dir_okay=True,
|
|
832
|
+
file_okay=True,
|
|
833
|
+
),
|
|
834
|
+
entry: str = typer.Argument(
|
|
835
|
+
..., help="Dataframe path to create/replace (e.g. 'obs', 'var', 'raw/var')"
|
|
836
|
+
),
|
|
837
|
+
input_file: Path = typer.Argument(
|
|
838
|
+
..., help="Input CSV file", exists=True, readable=True
|
|
839
|
+
),
|
|
840
|
+
output: Optional[Path] = typer.Option(
|
|
841
|
+
None,
|
|
842
|
+
"--output",
|
|
843
|
+
"-o",
|
|
844
|
+
help="Output .h5ad/.zarr path. Required unless --inplace.",
|
|
845
|
+
dir_okay=True,
|
|
846
|
+
file_okay=True,
|
|
847
|
+
),
|
|
848
|
+
inplace: bool = typer.Option(
|
|
849
|
+
False,
|
|
850
|
+
"--inplace",
|
|
851
|
+
help="Modify source file directly.",
|
|
852
|
+
),
|
|
853
|
+
index_column: Optional[str] = typer.Option(
|
|
854
|
+
None,
|
|
855
|
+
"--index-column",
|
|
856
|
+
"-i",
|
|
857
|
+
help="Column to use as index. Defaults to first column.",
|
|
858
|
+
),
|
|
859
|
+
categorical: Optional[str] = typer.Option(
|
|
860
|
+
None,
|
|
861
|
+
"--categorical",
|
|
862
|
+
help="Comma separated columns to force to categorical",
|
|
863
|
+
),
|
|
864
|
+
auto_categorical: bool = typer.Option(
|
|
865
|
+
True,
|
|
866
|
+
"--auto-categorical/--no-auto-categorical",
|
|
867
|
+
help="Infer categoricals from low-cardinality string columns",
|
|
868
|
+
),
|
|
869
|
+
) -> None:
|
|
870
|
+
"""
|
|
871
|
+
Import a CSV file as a dataframe.
|
|
872
|
+
|
|
873
|
+
String columns with few distinct values become categoricals; pass
|
|
874
|
+
--no-auto-categorical to keep them as plain strings, or --categorical to
|
|
875
|
+
force specific ones.
|
|
876
|
+
|
|
877
|
+
Examples:
|
|
878
|
+
adata import dataframe data.h5ad obs cells.csv -o out.h5ad -i cell_id
|
|
879
|
+
adata import dataframe data.h5ad var genes.csv --inplace -i gene_id
|
|
880
|
+
adata import dataframe data.h5ad obs cells.csv --inplace --categorical batch
|
|
881
|
+
"""
|
|
882
|
+
from adata.commands.import_data import _import_csv
|
|
883
|
+
|
|
884
|
+
if not inplace and output is None:
|
|
885
|
+
console.print(
|
|
886
|
+
"[bold red]Error:[/] Output file is required. "
|
|
887
|
+
"Use --output/-o or --inplace.",
|
|
888
|
+
)
|
|
889
|
+
raise typer.Exit(code=1)
|
|
890
|
+
|
|
891
|
+
try:
|
|
892
|
+
target = _get_target_file(file, output, inplace)
|
|
893
|
+
cat_list = (
|
|
894
|
+
[c.strip() for c in categorical.split(",") if c.strip()]
|
|
895
|
+
if categorical
|
|
896
|
+
else None
|
|
897
|
+
)
|
|
898
|
+
_import_csv(
|
|
899
|
+
target,
|
|
900
|
+
entry,
|
|
901
|
+
input_file,
|
|
902
|
+
index_column,
|
|
903
|
+
console,
|
|
904
|
+
categorical=cat_list,
|
|
905
|
+
auto_categorical=auto_categorical,
|
|
906
|
+
)
|
|
907
|
+
except Exception as e:
|
|
908
|
+
console.print(f"[bold red]Error:[/] {e}")
|
|
909
|
+
raise typer.Exit(code=1)
|
|
910
|
+
|
|
911
|
+
|
|
912
|
+
@import_app.command("array")
|
|
913
|
+
def import_array(
|
|
914
|
+
file: Path = typer.Argument(
|
|
915
|
+
...,
|
|
916
|
+
help="Path to the source .h5ad/.zarr store",
|
|
917
|
+
exists=True,
|
|
918
|
+
readable=True,
|
|
919
|
+
dir_okay=True,
|
|
920
|
+
file_okay=True,
|
|
921
|
+
),
|
|
922
|
+
entry: str = typer.Argument(
|
|
923
|
+
..., help="Entry path to create/replace (e.g., 'X', 'obsm/X_pca')"
|
|
924
|
+
),
|
|
925
|
+
input_file: Path = typer.Argument(
|
|
926
|
+
..., help="Input .npy file", exists=True, readable=True
|
|
927
|
+
),
|
|
928
|
+
output: Optional[Path] = typer.Option(
|
|
929
|
+
None,
|
|
930
|
+
"--output",
|
|
931
|
+
"-o",
|
|
932
|
+
help="Output .h5ad/.zarr path. Required unless --inplace.",
|
|
933
|
+
dir_okay=True,
|
|
934
|
+
file_okay=True,
|
|
935
|
+
),
|
|
936
|
+
inplace: bool = typer.Option(
|
|
937
|
+
False,
|
|
938
|
+
"--inplace",
|
|
939
|
+
help="Modify source file directly.",
|
|
940
|
+
),
|
|
941
|
+
) -> None:
|
|
942
|
+
"""
|
|
943
|
+
Import a NumPy .npy file as a dense array.
|
|
944
|
+
|
|
945
|
+
Dimensions are validated against existing obs/var.
|
|
946
|
+
|
|
947
|
+
Examples:
|
|
948
|
+
h5ad import array data.h5ad obsm/X_pca pca.npy -o output.h5ad
|
|
949
|
+
h5ad import array data.h5ad X matrix.npy --inplace
|
|
950
|
+
"""
|
|
951
|
+
from adata.commands.import_data import _import_npy
|
|
952
|
+
|
|
953
|
+
if not inplace and output is None:
|
|
954
|
+
console.print(
|
|
955
|
+
"[bold red]Error:[/] Output file is required. "
|
|
956
|
+
"Use --output/-o or --inplace.",
|
|
957
|
+
)
|
|
958
|
+
raise typer.Exit(code=1)
|
|
959
|
+
|
|
960
|
+
try:
|
|
961
|
+
target = _get_target_file(file, output, inplace)
|
|
962
|
+
_import_npy(target, entry, input_file, console)
|
|
963
|
+
except Exception as e:
|
|
964
|
+
console.print(f"[bold red]Error:[/] {e}")
|
|
965
|
+
raise typer.Exit(code=1)
|
|
966
|
+
|
|
967
|
+
|
|
968
|
+
@import_app.command("sparse")
|
|
969
|
+
def import_sparse(
|
|
970
|
+
file: Path = typer.Argument(
|
|
971
|
+
...,
|
|
972
|
+
help="Path to the source .h5ad/.zarr store",
|
|
973
|
+
exists=True,
|
|
974
|
+
readable=True,
|
|
975
|
+
dir_okay=True,
|
|
976
|
+
file_okay=True,
|
|
977
|
+
),
|
|
978
|
+
obj: str = typer.Argument(
|
|
979
|
+
..., help="Object path to create/replace (e.g., 'X', 'layers/counts')"
|
|
980
|
+
),
|
|
981
|
+
input_file: Path = typer.Argument(
|
|
982
|
+
..., help="Input .mtx file", exists=True, readable=True
|
|
983
|
+
),
|
|
984
|
+
output: Optional[Path] = typer.Option(
|
|
985
|
+
None,
|
|
986
|
+
"--output",
|
|
987
|
+
"-o",
|
|
988
|
+
help="Output .h5ad/.zarr path. Required unless --inplace.",
|
|
989
|
+
dir_okay=True,
|
|
990
|
+
file_okay=True,
|
|
991
|
+
),
|
|
992
|
+
inplace: bool = typer.Option(
|
|
993
|
+
False,
|
|
994
|
+
"--inplace",
|
|
995
|
+
help="Modify source file directly.",
|
|
996
|
+
),
|
|
997
|
+
) -> None:
|
|
998
|
+
"""
|
|
999
|
+
Import a Matrix Market (.mtx) file as a CSR sparse matrix.
|
|
1000
|
+
|
|
1001
|
+
Dimensions are validated against existing obs/var.
|
|
1002
|
+
|
|
1003
|
+
Examples:
|
|
1004
|
+
h5ad import sparse data.h5ad X matrix.mtx -o output.h5ad
|
|
1005
|
+
h5ad import sparse data.h5ad layers/counts counts.mtx --inplace
|
|
1006
|
+
"""
|
|
1007
|
+
from adata.commands.import_data import _import_mtx
|
|
1008
|
+
|
|
1009
|
+
if not inplace and output is None:
|
|
1010
|
+
console.print(
|
|
1011
|
+
"[bold red]Error:[/] Output file is required. "
|
|
1012
|
+
"Use --output/-o or --inplace.",
|
|
1013
|
+
)
|
|
1014
|
+
raise typer.Exit(code=1)
|
|
1015
|
+
|
|
1016
|
+
try:
|
|
1017
|
+
target = _get_target_file(file, output, inplace)
|
|
1018
|
+
_import_mtx(target, obj, input_file, console)
|
|
1019
|
+
except Exception as e:
|
|
1020
|
+
console.print(f"[bold red]Error:[/] {e}")
|
|
1021
|
+
raise typer.Exit(code=1)
|
|
1022
|
+
|
|
1023
|
+
|
|
1024
|
+
@import_app.command("dict")
|
|
1025
|
+
def import_dict(
|
|
1026
|
+
file: Path = typer.Argument(
|
|
1027
|
+
...,
|
|
1028
|
+
help="Path to the source .h5ad/.zarr store",
|
|
1029
|
+
exists=True,
|
|
1030
|
+
readable=True,
|
|
1031
|
+
dir_okay=True,
|
|
1032
|
+
file_okay=True,
|
|
1033
|
+
),
|
|
1034
|
+
obj: str = typer.Argument(
|
|
1035
|
+
..., help="Object path to create/replace (e.g., 'uns', 'uns/metadata')"
|
|
1036
|
+
),
|
|
1037
|
+
input_file: Path = typer.Argument(
|
|
1038
|
+
..., help="Input .json file", exists=True, readable=True
|
|
1039
|
+
),
|
|
1040
|
+
output: Optional[Path] = typer.Option(
|
|
1041
|
+
None,
|
|
1042
|
+
"--output",
|
|
1043
|
+
"-o",
|
|
1044
|
+
help="Output .h5ad/.zarr path. Required unless --inplace.",
|
|
1045
|
+
dir_okay=True,
|
|
1046
|
+
file_okay=True,
|
|
1047
|
+
),
|
|
1048
|
+
inplace: bool = typer.Option(
|
|
1049
|
+
False,
|
|
1050
|
+
"--inplace",
|
|
1051
|
+
help="Modify source file directly.",
|
|
1052
|
+
),
|
|
1053
|
+
) -> None:
|
|
1054
|
+
"""
|
|
1055
|
+
Import a JSON file into uns or other dict-like groups.
|
|
1056
|
+
|
|
1057
|
+
Examples:
|
|
1058
|
+
h5ad import dict data.h5ad uns/metadata config.json -o output.h5ad
|
|
1059
|
+
h5ad import dict data.h5ad uns settings.json --inplace
|
|
1060
|
+
"""
|
|
1061
|
+
from adata.commands.import_data import _import_json
|
|
1062
|
+
|
|
1063
|
+
if not inplace and output is None:
|
|
1064
|
+
console.print(
|
|
1065
|
+
"[bold red]Error:[/] Output file is required. "
|
|
1066
|
+
"Use --output/-o or --inplace.",
|
|
1067
|
+
)
|
|
1068
|
+
raise typer.Exit(code=1)
|
|
1069
|
+
|
|
1070
|
+
try:
|
|
1071
|
+
target = _get_target_file(file, output, inplace)
|
|
1072
|
+
_import_json(target, obj, input_file, console)
|
|
1073
|
+
except Exception as e:
|
|
1074
|
+
console.print(f"[bold red]Error:[/] {e}")
|
|
1075
|
+
raise typer.Exit(code=1)
|
|
1076
|
+
|
|
1077
|
+
|
|
1078
|
+
@import_app.command("image")
|
|
1079
|
+
def import_image_cmd(
|
|
1080
|
+
file: Path = typer.Argument(
|
|
1081
|
+
...,
|
|
1082
|
+
help="Path to the source .h5ad/.zarr store",
|
|
1083
|
+
exists=True,
|
|
1084
|
+
readable=True,
|
|
1085
|
+
dir_okay=True,
|
|
1086
|
+
file_okay=True,
|
|
1087
|
+
),
|
|
1088
|
+
entry: str = typer.Argument(
|
|
1089
|
+
..., help="Entry path to create/replace (e.g. 'uns/spatial/hires')"
|
|
1090
|
+
),
|
|
1091
|
+
input_file: Path = typer.Argument(
|
|
1092
|
+
..., help="Input image file (.png, .jpg, .tiff)", exists=True, readable=True
|
|
1093
|
+
),
|
|
1094
|
+
output: Optional[Path] = typer.Option(
|
|
1095
|
+
None,
|
|
1096
|
+
"--output",
|
|
1097
|
+
"-o",
|
|
1098
|
+
help="Output .h5ad/.zarr path. Required unless --inplace.",
|
|
1099
|
+
dir_okay=True,
|
|
1100
|
+
file_okay=True,
|
|
1101
|
+
),
|
|
1102
|
+
inplace: bool = typer.Option(
|
|
1103
|
+
False, "--inplace", help="Modify source file directly."
|
|
1104
|
+
),
|
|
1105
|
+
) -> None:
|
|
1106
|
+
"""
|
|
1107
|
+
Import an image file as a dense array.
|
|
1108
|
+
|
|
1109
|
+
Examples:
|
|
1110
|
+
adata import image data.h5ad uns/spatial/hires tissue.png --inplace
|
|
1111
|
+
"""
|
|
1112
|
+
from adata.commands.import_data import _import_image
|
|
1113
|
+
|
|
1114
|
+
if not inplace and output is None:
|
|
1115
|
+
console.print(
|
|
1116
|
+
"[bold red]Error:[/] Output file is required. "
|
|
1117
|
+
"Use --output/-o or --inplace.",
|
|
1118
|
+
)
|
|
1119
|
+
raise typer.Exit(code=1)
|
|
1120
|
+
|
|
1121
|
+
try:
|
|
1122
|
+
target = _get_target_file(file, output, inplace)
|
|
1123
|
+
_import_image(target, entry, input_file, console)
|
|
1124
|
+
except Exception as e:
|
|
1125
|
+
console.print(f"[bold red]Error:[/] {e}")
|
|
1126
|
+
raise typer.Exit(code=1)
|
|
1127
|
+
|
|
1128
|
+
|
|
1129
|
+
def main(argv: Optional[Sequence[str]] = None) -> None:
|
|
1130
|
+
app(standalone_mode=True)
|
|
1131
|
+
|
|
1132
|
+
|
|
1133
|
+
def main_deprecated(argv: Optional[Sequence[str]] = None) -> None:
|
|
1134
|
+
"""Entry point for the deprecated `h5ad` command name."""
|
|
1135
|
+
console.print(
|
|
1136
|
+
"[yellow]Warning:[/] the 'h5ad' command is deprecated and will be removed "
|
|
1137
|
+
"in 1.0.0; use 'adata' instead.",
|
|
1138
|
+
)
|
|
1139
|
+
main(argv)
|