pycorearray 0.2.0__cp315-cp315-macosx_11_0_arm64.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.
- pycorearray/__init__.py +1439 -0
- pycorearray/ccall.cpython-315-darwin.so +0 -0
- pycorearray/cloud.py +806 -0
- pycorearray/data/ceu_exon.gds +0 -0
- pycorearray/include/CoreDEF.h +951 -0
- pycorearray/include/PyGDS.h +392 -0
- pycorearray/include/PyGDS2.h +761 -0
- pycorearray/include/PyGDS_CPP.h +179 -0
- pycorearray/include/dTrait.h +351 -0
- pycorearray/include/dType.h +296 -0
- pycorearray-0.2.0.dist-info/METADATA +202 -0
- pycorearray-0.2.0.dist-info/RECORD +18 -0
- pycorearray-0.2.0.dist-info/WHEEL +6 -0
- pycorearray-0.2.0.dist-info/licenses/LICENSE +674 -0
- pycorearray-0.2.0.dist-info/licenses/src/CoreArray/LICENSE +165 -0
- pycorearray-0.2.0.dist-info/licenses/src/LZ4/LICENSE +25 -0
- pycorearray-0.2.0.dist-info/licenses/src/ZLIB/LICENSE +22 -0
- pycorearray-0.2.0.dist-info/top_level.txt +1 -0
pycorearray/__init__.py
ADDED
|
@@ -0,0 +1,1439 @@
|
|
|
1
|
+
"""Python interface to CoreArray Genomic Data Structure (GDS) files
|
|
2
|
+
|
|
3
|
+
GDS files store multiple array-oriented data sets in a hierarchical structure,
|
|
4
|
+
with compression and relatively efficient random access; they are portable
|
|
5
|
+
across platforms, and the same files are used by the R/Bioconductor package
|
|
6
|
+
gdsfmt. A GDS file is opened as a `gdsfile`, whose data sets and folders are
|
|
7
|
+
`gdsnode` objects. Files on cloud storage can be opened read-only, see
|
|
8
|
+
`pycorearray.cloud`.
|
|
9
|
+
|
|
10
|
+
The examples in the documentation assume ``import numpy as np`` and
|
|
11
|
+
``import pycorearray as gds``; the examples writing a file create 'test.gds'
|
|
12
|
+
in the current directory, and remove it at the end.
|
|
13
|
+
|
|
14
|
+
Examples
|
|
15
|
+
--------
|
|
16
|
+
>>> f = gds.gdsfile()
|
|
17
|
+
>>> f.create('test.gds')
|
|
18
|
+
>>> n = f.root().add('x', np.arange(5, dtype=np.int32), compress='ZIP')
|
|
19
|
+
>>> n.append(np.arange(5, 10, dtype=np.int32))
|
|
20
|
+
>>> n.readmode().read()
|
|
21
|
+
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=int32)
|
|
22
|
+
>>> f.close()
|
|
23
|
+
>>> import os
|
|
24
|
+
>>> os.remove('test.gds')
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
# import numpy
|
|
28
|
+
import numpy as np
|
|
29
|
+
# import os
|
|
30
|
+
import os
|
|
31
|
+
# import c library
|
|
32
|
+
import pycorearray.ccall as cc
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
## export version number
|
|
36
|
+
__version__ = '0.2.0'
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# ===========================================================================
|
|
41
|
+
|
|
42
|
+
def cleanup_gds(filename, verbose=True):
|
|
43
|
+
"""Clean up fragments
|
|
44
|
+
|
|
45
|
+
Clean up the fragments of a CoreArray Genomic Data Structure (GDS) file.
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
filename : str
|
|
50
|
+
the file name of an existing GDS file
|
|
51
|
+
verbose : bool
|
|
52
|
+
if True, show information
|
|
53
|
+
|
|
54
|
+
Returns
|
|
55
|
+
-------
|
|
56
|
+
None
|
|
57
|
+
"""
|
|
58
|
+
cc.tidy_up(filename, verbose)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_include():
|
|
62
|
+
"""
|
|
63
|
+
Return the directory that contains the pycorearray \\*.h header files.
|
|
64
|
+
Extension modules that need to compile against pycorearray should use this
|
|
65
|
+
function to locate the appropriate include directory.
|
|
66
|
+
Notes
|
|
67
|
+
-----
|
|
68
|
+
When using ``distutils``, for example in ``setup.py``.
|
|
69
|
+
::
|
|
70
|
+
import pycorearray as gds
|
|
71
|
+
...
|
|
72
|
+
Extension('pkg_name', ...
|
|
73
|
+
include_dirs=[ gds.get_include() ])
|
|
74
|
+
...
|
|
75
|
+
"""
|
|
76
|
+
import pycorearray
|
|
77
|
+
return os.path.join(os.path.dirname(pycorearray.__file__), 'include')
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def get_example_path(filename=None):
|
|
81
|
+
"""Example files
|
|
82
|
+
|
|
83
|
+
Return a file name in the folder of example data.
|
|
84
|
+
|
|
85
|
+
Parameters
|
|
86
|
+
----------
|
|
87
|
+
filename : str
|
|
88
|
+
a file name in the folder of example data, or None for returning the path of example folder
|
|
89
|
+
|
|
90
|
+
Returns
|
|
91
|
+
-------
|
|
92
|
+
string
|
|
93
|
+
|
|
94
|
+
Examples
|
|
95
|
+
--------
|
|
96
|
+
>>> f = gds.gdsfile()
|
|
97
|
+
>>> f.open(gds.get_example_path('ceu_exon.gds'))
|
|
98
|
+
>>> f.close()
|
|
99
|
+
"""
|
|
100
|
+
import pycorearray
|
|
101
|
+
s = os.path.dirname(pycorearray.__file__)
|
|
102
|
+
if filename is None:
|
|
103
|
+
return os.path.join(s, 'data')
|
|
104
|
+
else:
|
|
105
|
+
return os.path.join(s, 'data', filename)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# ---------------------------------------------------------------------------
|
|
109
|
+
# Internal helpers for writing data
|
|
110
|
+
# ---------------------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
# numpy dtype kind -> GDS storage name (used when storage is not specified)
|
|
113
|
+
def _infer_storage(val):
|
|
114
|
+
if isinstance(val, str):
|
|
115
|
+
return 'string'
|
|
116
|
+
arr = np.asarray(val)
|
|
117
|
+
k = arr.dtype.kind
|
|
118
|
+
if k in ('U', 'S', 'O'):
|
|
119
|
+
return 'string'
|
|
120
|
+
if k == 'b':
|
|
121
|
+
return 'int8'
|
|
122
|
+
return str(arr.dtype)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _prepare_value(val):
|
|
126
|
+
"""Normalise 'val' for the C layer.
|
|
127
|
+
|
|
128
|
+
Returns (obj, dim, is_string) where obj is either a C-contiguous numpy
|
|
129
|
+
array (numeric) or a flat list of str (character), dim is the C-order
|
|
130
|
+
shape as a list, and is_string indicates the kind.
|
|
131
|
+
"""
|
|
132
|
+
if isinstance(val, str):
|
|
133
|
+
return ([val], [1], True)
|
|
134
|
+
arr = np.asarray(val)
|
|
135
|
+
if arr.ndim == 0:
|
|
136
|
+
arr = arr.reshape(1)
|
|
137
|
+
if arr.dtype.kind in ('U', 'S', 'O'):
|
|
138
|
+
flat = ['' if x is None else str(x) for x in arr.ravel(order='C').tolist()]
|
|
139
|
+
return (flat, list(arr.shape), True)
|
|
140
|
+
arr = np.ascontiguousarray(arr)
|
|
141
|
+
return (arr, list(arr.shape), False)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def apply_gdsn(nodes, margins, fun, *args, as_is='none', **kwargs):
|
|
145
|
+
"""Apply a function over a margin of one or more array nodes
|
|
146
|
+
|
|
147
|
+
Iterate in lock-step over the given margin of each node; for each index,
|
|
148
|
+
call ``fun(slice0[, slice1, ...], *args, **kwargs)`` where each slice is
|
|
149
|
+
the corresponding sub-array with its margin axis removed.
|
|
150
|
+
|
|
151
|
+
Parameters
|
|
152
|
+
----------
|
|
153
|
+
nodes : gdsnode or a list of gdsnode
|
|
154
|
+
margins : int or a list of int
|
|
155
|
+
the axis (0-based, numpy C-order) to iterate over, per node
|
|
156
|
+
fun : callable
|
|
157
|
+
as_is : str
|
|
158
|
+
'none', 'list', or 'unlist' (see gdsnode.apply)
|
|
159
|
+
|
|
160
|
+
Returns
|
|
161
|
+
-------
|
|
162
|
+
None, list, or numpy array depending on 'as_is'
|
|
163
|
+
|
|
164
|
+
Examples
|
|
165
|
+
--------
|
|
166
|
+
>>> f = gds.gdsfile()
|
|
167
|
+
>>> f.open(gds.get_example_path('ceu_exon.gds'))
|
|
168
|
+
>>> chrom, pos = f.index('chromosome'), f.index('position')
|
|
169
|
+
>>> s = gds.apply_gdsn([chrom, pos], [0, 0],
|
|
170
|
+
... lambda c, p: '%s:%d' % (c, p), as_is='list')
|
|
171
|
+
>>> s[:3]
|
|
172
|
+
['1:1105366', '1:1105411', '1:1110294']
|
|
173
|
+
>>> f.close()
|
|
174
|
+
"""
|
|
175
|
+
single = isinstance(nodes, gdsnode)
|
|
176
|
+
if single:
|
|
177
|
+
nodes = [nodes]
|
|
178
|
+
margins = [margins]
|
|
179
|
+
if len(nodes) != len(margins):
|
|
180
|
+
raise ValueError("'nodes' and 'margins' must have the same length.")
|
|
181
|
+
|
|
182
|
+
dims = [n.description()['dim'] for n in nodes]
|
|
183
|
+
counts = [dims[k][margins[k]] for k in range(len(nodes))]
|
|
184
|
+
N = counts[0]
|
|
185
|
+
if any(c != N for c in counts):
|
|
186
|
+
raise ValueError("All nodes must have the same length along their margins.")
|
|
187
|
+
|
|
188
|
+
results = []
|
|
189
|
+
for i in range(N):
|
|
190
|
+
slices = []
|
|
191
|
+
for k, n in enumerate(nodes):
|
|
192
|
+
m = margins[k]
|
|
193
|
+
dim = dims[k]
|
|
194
|
+
sel = [None] * len(dim)
|
|
195
|
+
bv = np.zeros(dim[m], dtype=bool)
|
|
196
|
+
bv[i] = True
|
|
197
|
+
sel[m] = bv
|
|
198
|
+
s = n.readex(sel)
|
|
199
|
+
s = np.asarray(s)
|
|
200
|
+
if s.ndim == len(dim) and s.shape[m] == 1:
|
|
201
|
+
s = np.squeeze(s, axis=m)
|
|
202
|
+
slices.append(s)
|
|
203
|
+
r = fun(slices[0], *args, **kwargs) if single \
|
|
204
|
+
else fun(*slices, *args, **kwargs)
|
|
205
|
+
if as_is != 'none':
|
|
206
|
+
results.append(r)
|
|
207
|
+
|
|
208
|
+
if as_is == 'list':
|
|
209
|
+
return results
|
|
210
|
+
elif as_is == 'unlist':
|
|
211
|
+
if not results:
|
|
212
|
+
return np.array([])
|
|
213
|
+
return np.concatenate([np.atleast_1d(np.asarray(r)).ravel()
|
|
214
|
+
for r in results])
|
|
215
|
+
return None
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
# ===========================================================================
|
|
219
|
+
|
|
220
|
+
class gdsfile:
|
|
221
|
+
"""
|
|
222
|
+
Common base class for GDS files
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
def __init__(self):
|
|
226
|
+
self.filename = ''
|
|
227
|
+
self.fileid = -1
|
|
228
|
+
|
|
229
|
+
def __del__(self):
|
|
230
|
+
self.close()
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def create(self, filename, allow_dup=False):
|
|
234
|
+
"""Create a GDS file
|
|
235
|
+
|
|
236
|
+
Create a new CoreArray Genomic Data Structure (GDS) file.
|
|
237
|
+
|
|
238
|
+
Parameters
|
|
239
|
+
----------
|
|
240
|
+
filename : str
|
|
241
|
+
the file name of a new GDS file to be created
|
|
242
|
+
allow_dup : bool
|
|
243
|
+
if True, it is allowed to open a GDS file with read-only mode when it has been opened in the same session
|
|
244
|
+
|
|
245
|
+
Returns
|
|
246
|
+
-------
|
|
247
|
+
None
|
|
248
|
+
|
|
249
|
+
See Also
|
|
250
|
+
--------
|
|
251
|
+
open : open an existing GDS file
|
|
252
|
+
close: close a GDS file
|
|
253
|
+
|
|
254
|
+
Examples
|
|
255
|
+
--------
|
|
256
|
+
>>> f = gds.gdsfile()
|
|
257
|
+
>>> f.create('test.gds')
|
|
258
|
+
>>> n = f.root().add('x', np.arange(5, dtype=np.int32))
|
|
259
|
+
>>> f.close()
|
|
260
|
+
>>> import os
|
|
261
|
+
>>> os.remove('test.gds')
|
|
262
|
+
"""
|
|
263
|
+
self.fileid = cc.create_gds(filename, allow_dup)
|
|
264
|
+
self.filename = os.path.abspath(filename)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def open(self, filename, readonly=True, allow_dup=False):
|
|
268
|
+
"""Open an GDS file
|
|
269
|
+
|
|
270
|
+
Open an existing file of CoreArray Genomic Data Structure (GDS) for reading or writing.
|
|
271
|
+
|
|
272
|
+
Parameters
|
|
273
|
+
----------
|
|
274
|
+
filename : str
|
|
275
|
+
the file name of an existing GDS file, or a URL of a GDS file on
|
|
276
|
+
cloud storage ('http://', 'https://', 's3://', 'gs://' or 'az://',
|
|
277
|
+
see pycorearray.cloud), which is read-only
|
|
278
|
+
readonly : bool
|
|
279
|
+
if True, the file is opened read-only; otherwise, it is allowed to write data to the file
|
|
280
|
+
allow_dup : bool
|
|
281
|
+
if True, it is allowed to open a GDS file with read-only mode when it has been opened in the same session
|
|
282
|
+
|
|
283
|
+
Returns
|
|
284
|
+
-------
|
|
285
|
+
None
|
|
286
|
+
|
|
287
|
+
See Also
|
|
288
|
+
--------
|
|
289
|
+
create : create a GDS file
|
|
290
|
+
close: close a GDS file
|
|
291
|
+
|
|
292
|
+
Examples
|
|
293
|
+
--------
|
|
294
|
+
>>> f = gds.gdsfile()
|
|
295
|
+
>>> f.open(gds.get_example_path('ceu_exon.gds'))
|
|
296
|
+
>>> f.root().ls()
|
|
297
|
+
['description', 'sample.id', 'variant.id', 'position', 'chromosome',
|
|
298
|
+
'allele', 'genotype', 'phase', 'annotation', 'sample.annotation']
|
|
299
|
+
>>> f.close()
|
|
300
|
+
|
|
301
|
+
A file on cloud storage:
|
|
302
|
+
|
|
303
|
+
>>> f.open('s3://gds-stat/download/hapmap/hapmap_r23a.gds') # doctest: +SKIP
|
|
304
|
+
>>> f.close() # doctest: +SKIP
|
|
305
|
+
"""
|
|
306
|
+
if cloud._is_url(filename):
|
|
307
|
+
if not readonly:
|
|
308
|
+
raise ValueError('A GDS file on cloud storage can only be '
|
|
309
|
+
'opened read-only.')
|
|
310
|
+
cloud._open_into(self, filename)
|
|
311
|
+
return
|
|
312
|
+
self.fileid = cc.open_gds(filename, readonly, allow_dup)
|
|
313
|
+
self.filename = os.path.abspath(filename)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def close(self):
|
|
317
|
+
"""Close a GDS file
|
|
318
|
+
|
|
319
|
+
Close a CoreArray Genomic Data Structure (GDS) file.
|
|
320
|
+
|
|
321
|
+
Returns
|
|
322
|
+
-------
|
|
323
|
+
None
|
|
324
|
+
|
|
325
|
+
See Also
|
|
326
|
+
--------
|
|
327
|
+
create : create a GDS file
|
|
328
|
+
open : open an existing GDS file
|
|
329
|
+
"""
|
|
330
|
+
cc.close_gds(self.fileid)
|
|
331
|
+
self.fileid = -1
|
|
332
|
+
self.filename = ''
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def sync(self):
|
|
336
|
+
"""Synchronize a GDS file
|
|
337
|
+
|
|
338
|
+
Write the data cached in memory to disk.
|
|
339
|
+
|
|
340
|
+
Returns
|
|
341
|
+
-------
|
|
342
|
+
None
|
|
343
|
+
"""
|
|
344
|
+
cc.sync_gds(self.fileid)
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def filesize(self):
|
|
348
|
+
"""Get the file size
|
|
349
|
+
|
|
350
|
+
Get the size of a GDS file.
|
|
351
|
+
|
|
352
|
+
Returns
|
|
353
|
+
-------
|
|
354
|
+
double
|
|
355
|
+
"""
|
|
356
|
+
return cc.filesize(self.fileid)
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def root(self):
|
|
360
|
+
"""GDS root node
|
|
361
|
+
|
|
362
|
+
Get the root of a GDS file.
|
|
363
|
+
|
|
364
|
+
Returns
|
|
365
|
+
-------
|
|
366
|
+
gdsnode
|
|
367
|
+
"""
|
|
368
|
+
v = gdsnode()
|
|
369
|
+
(v.idx, v.pid) = cc.root_gds(self.fileid)
|
|
370
|
+
return v
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def index(self, path, silent=False):
|
|
374
|
+
"""Get a GDS node
|
|
375
|
+
|
|
376
|
+
Get a specified GDS node with a given path
|
|
377
|
+
|
|
378
|
+
Parameters
|
|
379
|
+
----------
|
|
380
|
+
path : str
|
|
381
|
+
the path specifying a GDS node with '/' as a separator
|
|
382
|
+
silent : bool
|
|
383
|
+
if True, return None if the specified node does not exist; otherwise raise an error
|
|
384
|
+
|
|
385
|
+
Returns
|
|
386
|
+
-------
|
|
387
|
+
gdsnode
|
|
388
|
+
|
|
389
|
+
Examples
|
|
390
|
+
--------
|
|
391
|
+
>>> f = gds.gdsfile()
|
|
392
|
+
>>> f.open(gds.get_example_path('ceu_exon.gds'))
|
|
393
|
+
>>> f.index('genotype/data').description()['dim']
|
|
394
|
+
[1348, 90, 2]
|
|
395
|
+
>>> f.index('genotype/xyz', silent=True) is None
|
|
396
|
+
True
|
|
397
|
+
>>> f.close()
|
|
398
|
+
"""
|
|
399
|
+
v = gdsnode()
|
|
400
|
+
v.idx, v.pid = cc.index_gds(self.fileid, path, silent)
|
|
401
|
+
if v.idx >= 0:
|
|
402
|
+
return v
|
|
403
|
+
else:
|
|
404
|
+
return None
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def show(self, attribute=False, all=False):
|
|
408
|
+
r"""Display a GDS file
|
|
409
|
+
|
|
410
|
+
Display the structure of a GDS file: a folder is shown as '[ ]', and
|
|
411
|
+
a data set as '{ }' with its data type, dimensions, compression method
|
|
412
|
+
and ratio, and stored size; '*' marks a node with attributes.
|
|
413
|
+
|
|
414
|
+
Parameters
|
|
415
|
+
----------
|
|
416
|
+
attribute : bool
|
|
417
|
+
if True, show the attribute(s)
|
|
418
|
+
all : bool
|
|
419
|
+
if False, hide GDS nodes with an attribute "R.invisible"
|
|
420
|
+
|
|
421
|
+
Returns
|
|
422
|
+
-------
|
|
423
|
+
None
|
|
424
|
+
|
|
425
|
+
Examples
|
|
426
|
+
--------
|
|
427
|
+
>>> f = gds.gdsfile()
|
|
428
|
+
>>> f.open(gds.get_example_path('ceu_exon.gds'))
|
|
429
|
+
>>> f.show()
|
|
430
|
+
File: ...ceu_exon.gds (32.5K)
|
|
431
|
+
+ [ ] *
|
|
432
|
+
|--+ description [ ] *
|
|
433
|
+
|--+ sample.id { Str8 90 LZMA_ra(35.8%), 258B } *
|
|
434
|
+
|--+ variant.id { Int32 1348 LZMA_ra(16.8%), 906B } *
|
|
435
|
+
|--+ position { Int32 1348 LZMA_ra(64.6%), 3.4K } *
|
|
436
|
+
|--+ chromosome { Str8 1348 LZMA_ra(4.63%), 158B } *
|
|
437
|
+
|--+ allele { Str8 1348 LZMA_ra(16.7%), 902B } *
|
|
438
|
+
|--+ genotype [ ] *
|
|
439
|
+
| |--+ data { Bit2 1348x90x2 LZMA_ra(26.3%), 15.6K } *
|
|
440
|
+
| |--+ extra.index { Int32 0x3 LZMA_ra, 19B } *
|
|
441
|
+
| \--+ extra { Int16 0 LZMA_ra, 19B }
|
|
442
|
+
|--+ phase [ ]
|
|
443
|
+
| |--+ data { Bit1 1348x90 LZMA_ra(0.91%), 138B } *
|
|
444
|
+
| |--+ extra.index { Int32 0x3 LZMA_ra, 19B } *
|
|
445
|
+
| \--+ extra { Bit1 0 LZMA_ra, 19B }
|
|
446
|
+
|--+ annotation [ ]
|
|
447
|
+
| |--+ id { Str8 1348 LZMA_ra(38.4%), 5.5K } *
|
|
448
|
+
| |--+ qual { Float32 1348 LZMA_ra(2.26%), 122B } *
|
|
449
|
+
| \--+ filter { Int32,factor 1348 LZMA_ra(2.26%), 122B } *
|
|
450
|
+
\--+ sample.annotation [ ]
|
|
451
|
+
\--+ family { Str8 90 LZMA_ra(57.1%), 222B }
|
|
452
|
+
>>> f.close()
|
|
453
|
+
"""
|
|
454
|
+
sz = self.filesize()
|
|
455
|
+
if sz >= 1073741824:
|
|
456
|
+
s = '(%.1fG)' % (sz/1073741824)
|
|
457
|
+
elif sz >= 1048576:
|
|
458
|
+
s = '(%.1fM)' % (sz/1048576)
|
|
459
|
+
elif sz >= 1024:
|
|
460
|
+
s = '(%.1fK)' % (sz/1024)
|
|
461
|
+
else:
|
|
462
|
+
s = '(%gB)' % sz
|
|
463
|
+
print('File:', self.filename, s)
|
|
464
|
+
self.root().show(attribute, all)
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def diagnosis(self):
|
|
468
|
+
"""Diagnose the GDS file
|
|
469
|
+
|
|
470
|
+
Returns
|
|
471
|
+
-------
|
|
472
|
+
dict : {num_fragment, size} - the number of data fragments and the
|
|
473
|
+
total file size in bytes
|
|
474
|
+
"""
|
|
475
|
+
return cc.diagnosis_gds(self.fileid)
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
# ===========================================================================
|
|
482
|
+
|
|
483
|
+
# TODO: require Py_ssize_t (Python >=2.5)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
class gdsnode:
|
|
487
|
+
"""
|
|
488
|
+
Common base class for GDS node
|
|
489
|
+
"""
|
|
490
|
+
|
|
491
|
+
def __init__(self):
|
|
492
|
+
self.idx = -1
|
|
493
|
+
self.pid = 0
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def ls(self, inc_hidden=False):
|
|
497
|
+
"""Get a list of names
|
|
498
|
+
|
|
499
|
+
Get a list of names for its child nodes.
|
|
500
|
+
|
|
501
|
+
Parameters
|
|
502
|
+
----------
|
|
503
|
+
inc_hidden : bool
|
|
504
|
+
whether including hidden variables or folders
|
|
505
|
+
|
|
506
|
+
Returns
|
|
507
|
+
-------
|
|
508
|
+
a list of strings
|
|
509
|
+
|
|
510
|
+
Examples
|
|
511
|
+
--------
|
|
512
|
+
>>> f = gds.gdsfile()
|
|
513
|
+
>>> f.open(gds.get_example_path('ceu_exon.gds'))
|
|
514
|
+
>>> f.index('genotype').ls()
|
|
515
|
+
['data', 'extra.index', 'extra']
|
|
516
|
+
>>> f.close()
|
|
517
|
+
"""
|
|
518
|
+
return cc.ls_gdsn(self.idx, self.pid, inc_hidden)
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def index(self, path, silent=False):
|
|
522
|
+
"""Get a GDS node
|
|
523
|
+
|
|
524
|
+
Get a specified GDS node with a given path
|
|
525
|
+
|
|
526
|
+
Parameters
|
|
527
|
+
----------
|
|
528
|
+
path : str
|
|
529
|
+
the path specifying a GDS node with '/' as a separator
|
|
530
|
+
silent : bool
|
|
531
|
+
if True, return None if the specified node does not exist; otherwise raise an error
|
|
532
|
+
|
|
533
|
+
Returns
|
|
534
|
+
-------
|
|
535
|
+
gdsnode
|
|
536
|
+
"""
|
|
537
|
+
v = gdsnode()
|
|
538
|
+
v.idx, v.pid = cc.index_gdsn(self.idx, self.pid, path, silent)
|
|
539
|
+
if v.idx >= 0:
|
|
540
|
+
return v
|
|
541
|
+
else:
|
|
542
|
+
return None
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
def name(self, full=False):
|
|
546
|
+
"""Get the node name
|
|
547
|
+
|
|
548
|
+
Get the variable name of a GDS node.
|
|
549
|
+
|
|
550
|
+
Parameters
|
|
551
|
+
----------
|
|
552
|
+
full : bool
|
|
553
|
+
if False, return the node name; otherwise the name with a full path
|
|
554
|
+
|
|
555
|
+
Returns
|
|
556
|
+
-------
|
|
557
|
+
string
|
|
558
|
+
"""
|
|
559
|
+
return cc.name_gdsn(self.idx, self.pid, full)
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
def rename(self, newname):
|
|
563
|
+
"""Rename a GDS node
|
|
564
|
+
|
|
565
|
+
Rename a GDS node.
|
|
566
|
+
|
|
567
|
+
Parameters
|
|
568
|
+
----------
|
|
569
|
+
newname : str
|
|
570
|
+
the new name of a specified node
|
|
571
|
+
|
|
572
|
+
Returns
|
|
573
|
+
-------
|
|
574
|
+
None
|
|
575
|
+
"""
|
|
576
|
+
cc.rename_gdsn(self.idx, self.pid, newname)
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
def description(self):
|
|
580
|
+
"""Variable description
|
|
581
|
+
|
|
582
|
+
Get the description of a GDS node.
|
|
583
|
+
|
|
584
|
+
Returns
|
|
585
|
+
-------
|
|
586
|
+
dictionary:
|
|
587
|
+
name: the variable name of a specified node
|
|
588
|
+
fullname: the full name of a specified node
|
|
589
|
+
storage: the storage mode in the GDS file
|
|
590
|
+
trait: the description of data field, like "Int8"
|
|
591
|
+
type: a factor indicating the storage mode
|
|
592
|
+
Label -- a label node,
|
|
593
|
+
Folder -- a directory,
|
|
594
|
+
VFolder -- a virtual folder linking to another GDS file,
|
|
595
|
+
Raw -- raw data
|
|
596
|
+
Integer -- integers,
|
|
597
|
+
Factor -- factor values,
|
|
598
|
+
Logical -- logical values
|
|
599
|
+
Real -- floating numbers,
|
|
600
|
+
String -- characters,
|
|
601
|
+
Unknown -- unknown type
|
|
602
|
+
dim: the dimension of data field
|
|
603
|
+
encoder: encoder for compressed data, such like "ZIP"
|
|
604
|
+
compress: the compression method: "", "ZIP.max", etc
|
|
605
|
+
cpratio: data compression ratio, NaN indicates no compression
|
|
606
|
+
size: the size of data stored in the GDS file
|
|
607
|
+
good: logical, indicates the state of GDS file, e.g., False if the virtual folder fails to link the target GDS file
|
|
608
|
+
hidden: logical, True if it is a hidden node
|
|
609
|
+
message: if applicable, messages of the GDS node, such like error messages, log information
|
|
610
|
+
|
|
611
|
+
Examples
|
|
612
|
+
--------
|
|
613
|
+
>>> f = gds.gdsfile()
|
|
614
|
+
>>> f.open(gds.get_example_path('ceu_exon.gds'))
|
|
615
|
+
>>> d = f.index('genotype/data').description()
|
|
616
|
+
>>> d['trait'], d['dim'], d['compress']
|
|
617
|
+
('Bit2', [1348, 90, 2], 'LZMA_RA.def:256K')
|
|
618
|
+
>>> f.close()
|
|
619
|
+
"""
|
|
620
|
+
return cc.desp_gdsn(self.idx, self.pid)
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
def getattr(self):
|
|
624
|
+
"""Get attributes
|
|
625
|
+
|
|
626
|
+
Get the attributes of a GDS node.
|
|
627
|
+
|
|
628
|
+
Returns
|
|
629
|
+
-------
|
|
630
|
+
dictionary of attributes
|
|
631
|
+
|
|
632
|
+
Examples
|
|
633
|
+
--------
|
|
634
|
+
>>> f = gds.gdsfile()
|
|
635
|
+
>>> f.open(gds.get_example_path('ceu_exon.gds'))
|
|
636
|
+
>>> f.index('annotation/filter').getattr()['R.levels']
|
|
637
|
+
['PASS', 'q10']
|
|
638
|
+
>>> f.close()
|
|
639
|
+
"""
|
|
640
|
+
return cc.getattr_gdsn(self.idx, self.pid)
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
def read(self, start=None, count=None, cvt=''):
|
|
644
|
+
"""Read data
|
|
645
|
+
|
|
646
|
+
Read data field of a GDS node.
|
|
647
|
+
|
|
648
|
+
Parameters
|
|
649
|
+
----------
|
|
650
|
+
start : a list of integers
|
|
651
|
+
starting from 0 for each dimension component
|
|
652
|
+
count : a list of integers
|
|
653
|
+
the length of each dimnension, -1 indicates that all entries along that dimension should be read, starting from 'start'
|
|
654
|
+
cvt : str
|
|
655
|
+
'': no conversion; 'int8', 'uint8', 'int16', 'uint16', 'int32', 'uint32', 'int64', 'uint64': signed and unsigned integer;
|
|
656
|
+
'utf8': UTF-8 string; 'utf16': UTF-16 string
|
|
657
|
+
|
|
658
|
+
Returns
|
|
659
|
+
-------
|
|
660
|
+
a numpy array object
|
|
661
|
+
|
|
662
|
+
Examples
|
|
663
|
+
--------
|
|
664
|
+
>>> f = gds.gdsfile()
|
|
665
|
+
>>> f.open(gds.get_example_path('ceu_exon.gds'))
|
|
666
|
+
>>> f.index('position').read(start=[0], count=[5])
|
|
667
|
+
array([1105366, 1105411, 1110294, 3537996, 3538692], dtype=int32)
|
|
668
|
+
>>> f.index('sample.id').read(start=[0], count=[3])
|
|
669
|
+
array(['NA06984', 'NA06985', 'NA06986'], dtype=object)
|
|
670
|
+
|
|
671
|
+
The first two variants of the first three samples, all alleles:
|
|
672
|
+
|
|
673
|
+
>>> f.index('genotype/data').read(start=[0, 0, 0], count=[2, 3, -1])
|
|
674
|
+
array([[[3, 3],
|
|
675
|
+
[3, 3],
|
|
676
|
+
[0, 0]],
|
|
677
|
+
<BLANKLINE>
|
|
678
|
+
[[3, 3],
|
|
679
|
+
[3, 3],
|
|
680
|
+
[0, 0]]], dtype=uint8)
|
|
681
|
+
>>> f.close()
|
|
682
|
+
"""
|
|
683
|
+
return cc.read_gdsn(self.idx, self.pid, start, count, cvt)
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
def readex(self, sel, cvt=''):
|
|
687
|
+
"""Read data with selection
|
|
688
|
+
|
|
689
|
+
Read data field of a GDS node with selection
|
|
690
|
+
|
|
691
|
+
Parameters
|
|
692
|
+
----------
|
|
693
|
+
sel : a list of bool vectors
|
|
694
|
+
bool vectors indicating dimension selection, or None for all entries along that dimension
|
|
695
|
+
cvt : str
|
|
696
|
+
'': no conversion; 'int8', 'uint8', 'int16', 'uint16', 'int32', 'uint32', 'int64', 'uint64': signed and unsigned integer;
|
|
697
|
+
'utf8': UTF-8 string; 'utf16': UTF-16 string
|
|
698
|
+
|
|
699
|
+
Returns
|
|
700
|
+
-------
|
|
701
|
+
a numpy array object
|
|
702
|
+
|
|
703
|
+
Examples
|
|
704
|
+
--------
|
|
705
|
+
>>> f = gds.gdsfile()
|
|
706
|
+
>>> f.open(gds.get_example_path('ceu_exon.gds'))
|
|
707
|
+
>>> n = f.index('genotype/data') # variant x sample x allele
|
|
708
|
+
>>> sel = [ np.arange(1348) < 10, None, np.array([True, False]) ]
|
|
709
|
+
>>> n.readex(sel).shape
|
|
710
|
+
(10, 90, 1)
|
|
711
|
+
>>> f.close()
|
|
712
|
+
"""
|
|
713
|
+
return cc.read2_gdsn(self.idx, self.pid, sel, cvt)
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
# -------------------------------------------------------------------
|
|
717
|
+
# Node creation and data writing
|
|
718
|
+
# -------------------------------------------------------------------
|
|
719
|
+
|
|
720
|
+
def add(self, name, val=None, storage='', compress='', valdim=None,
|
|
721
|
+
closezip=False, replace=False, visible=True, check=True,
|
|
722
|
+
maxlen=0, offset=float('nan'), scale=float('nan')):
|
|
723
|
+
"""Add a new GDS node
|
|
724
|
+
|
|
725
|
+
Create a new GDS node under this folder, optionally writing data.
|
|
726
|
+
|
|
727
|
+
Parameters
|
|
728
|
+
----------
|
|
729
|
+
name : str
|
|
730
|
+
the name of the new node
|
|
731
|
+
val : array-like, optional
|
|
732
|
+
initial data (a numpy array, a sequence, or a list of strings);
|
|
733
|
+
if None, an empty node is created
|
|
734
|
+
storage : str
|
|
735
|
+
the storage mode, e.g. 'int8', 'int32', 'float64', 'string',
|
|
736
|
+
'packedreal16', 'fstring'; if '', it is inferred from 'val'
|
|
737
|
+
compress : str
|
|
738
|
+
the compression method, e.g. '', 'ZIP', 'ZIP_RA', 'LZMA',
|
|
739
|
+
'LZMA_RA', 'LZ4', 'LZ4_RA'
|
|
740
|
+
valdim : a list of ints, optional
|
|
741
|
+
reset the dimensions after writing, or the dimensions of an empty
|
|
742
|
+
node
|
|
743
|
+
closezip : bool
|
|
744
|
+
if True, close the compression after writing the initial data;
|
|
745
|
+
if False (the default, as in R), more data can be appended and
|
|
746
|
+
readmode() should be called before reading
|
|
747
|
+
replace : bool
|
|
748
|
+
if True, replace an existing node of the same name
|
|
749
|
+
visible : bool
|
|
750
|
+
if False, mark the node hidden (R.invisible)
|
|
751
|
+
maxlen : int
|
|
752
|
+
maximum length for fixed-length strings (fstring*)
|
|
753
|
+
offset, scale : float
|
|
754
|
+
offset / scale for packed real numbers (packedreal*)
|
|
755
|
+
|
|
756
|
+
Returns
|
|
757
|
+
-------
|
|
758
|
+
gdsnode
|
|
759
|
+
|
|
760
|
+
Examples
|
|
761
|
+
--------
|
|
762
|
+
>>> f = gds.gdsfile()
|
|
763
|
+
>>> f.create('test.gds')
|
|
764
|
+
>>> r = f.root()
|
|
765
|
+
>>> n = r.add('int', np.arange(10, dtype=np.int32), compress='LZMA_RA')
|
|
766
|
+
>>> n = r.add('str', ['a', 'bc', 'def'])
|
|
767
|
+
>>> n.description()['storage']
|
|
768
|
+
'dStr8'
|
|
769
|
+
>>> n = r.add('real', storage='packedreal16', offset=0, scale=0.01)
|
|
770
|
+
>>> n = r.add('mat', storage='float64', valdim=[0, 3]) # empty
|
|
771
|
+
>>> n.description()['dim']
|
|
772
|
+
[0, 3]
|
|
773
|
+
>>> r.ls()
|
|
774
|
+
['int', 'str', 'real', 'mat']
|
|
775
|
+
>>> f.close()
|
|
776
|
+
>>> import os
|
|
777
|
+
>>> os.remove('test.gds')
|
|
778
|
+
"""
|
|
779
|
+
if storage == '' or storage is None:
|
|
780
|
+
storage = _infer_storage(val) if val is not None else ''
|
|
781
|
+
v = gdsnode()
|
|
782
|
+
v.idx, v.pid = cc.add_gdsn(self.idx, self.pid, name, storage, compress,
|
|
783
|
+
bool(visible), bool(replace), int(maxlen),
|
|
784
|
+
float(offset), float(scale))
|
|
785
|
+
if val is not None:
|
|
786
|
+
obj, dim, is_str = _prepare_value(val)
|
|
787
|
+
cc.writeall_gdsn(v.idx, v.pid, obj, [int(d) for d in dim])
|
|
788
|
+
if compress != '' and closezip:
|
|
789
|
+
cc.readmode_gdsn(v.idx, v.pid)
|
|
790
|
+
if valdim is not None:
|
|
791
|
+
cc.setdim_gdsn(v.idx, v.pid, [int(d) for d in valdim], False)
|
|
792
|
+
elif valdim is not None:
|
|
793
|
+
cc.setdim_gdsn(v.idx, v.pid, [int(d) for d in valdim], False)
|
|
794
|
+
return v
|
|
795
|
+
|
|
796
|
+
|
|
797
|
+
def addfolder(self, name, type='directory', gds_fn='', replace=False,
|
|
798
|
+
visible=True):
|
|
799
|
+
"""Add a new folder node
|
|
800
|
+
|
|
801
|
+
Parameters
|
|
802
|
+
----------
|
|
803
|
+
name : str
|
|
804
|
+
the name of the new folder
|
|
805
|
+
type : str
|
|
806
|
+
'directory' for a sub folder, or 'virtual' to link another GDS file
|
|
807
|
+
gds_fn : str
|
|
808
|
+
the file name of an existing GDS file (when type='virtual')
|
|
809
|
+
replace : bool
|
|
810
|
+
if True, replace an existing node of the same name
|
|
811
|
+
visible : bool
|
|
812
|
+
if False, mark the folder hidden
|
|
813
|
+
|
|
814
|
+
Returns
|
|
815
|
+
-------
|
|
816
|
+
gdsnode
|
|
817
|
+
|
|
818
|
+
Examples
|
|
819
|
+
--------
|
|
820
|
+
>>> f = gds.gdsfile()
|
|
821
|
+
>>> f.create('test.gds')
|
|
822
|
+
>>> d = f.root().addfolder('sample')
|
|
823
|
+
>>> n = d.add('id', ['s1', 's2', 's3'])
|
|
824
|
+
>>> f.index('sample/id').read()
|
|
825
|
+
array(['s1', 's2', 's3'], dtype=object)
|
|
826
|
+
>>> f.close()
|
|
827
|
+
>>> import os
|
|
828
|
+
>>> os.remove('test.gds')
|
|
829
|
+
"""
|
|
830
|
+
v = gdsnode()
|
|
831
|
+
v.idx, v.pid = cc.addfolder_gdsn(self.idx, self.pid, name, type, gds_fn,
|
|
832
|
+
bool(visible), bool(replace))
|
|
833
|
+
return v
|
|
834
|
+
|
|
835
|
+
|
|
836
|
+
def delete(self, child=None, force=False):
|
|
837
|
+
"""Delete a GDS node
|
|
838
|
+
|
|
839
|
+
Delete this node, or a named child node when 'child' is given.
|
|
840
|
+
|
|
841
|
+
Parameters
|
|
842
|
+
----------
|
|
843
|
+
child : str, optional
|
|
844
|
+
the name of a child node to delete; if None, delete this node
|
|
845
|
+
force : bool
|
|
846
|
+
if True, also delete a non-empty folder
|
|
847
|
+
|
|
848
|
+
Examples
|
|
849
|
+
--------
|
|
850
|
+
>>> f = gds.gdsfile()
|
|
851
|
+
>>> f.create('test.gds')
|
|
852
|
+
>>> r = f.root()
|
|
853
|
+
>>> n = r.add('x', np.arange(5, dtype=np.int32))
|
|
854
|
+
>>> n = r.add('y', np.arange(5, dtype=np.int32))
|
|
855
|
+
>>> r.delete('x') # or r.index('x').delete()
|
|
856
|
+
>>> r.ls()
|
|
857
|
+
['y']
|
|
858
|
+
>>> f.close()
|
|
859
|
+
>>> import os
|
|
860
|
+
>>> os.remove('test.gds')
|
|
861
|
+
"""
|
|
862
|
+
if child is not None:
|
|
863
|
+
n = self.index(child)
|
|
864
|
+
if n is not None:
|
|
865
|
+
n.delete(force=force)
|
|
866
|
+
return
|
|
867
|
+
cc.delete_gdsn(self.idx, self.pid, bool(force))
|
|
868
|
+
self.idx = -1
|
|
869
|
+
self.pid = 0
|
|
870
|
+
|
|
871
|
+
|
|
872
|
+
def append(self, val):
|
|
873
|
+
"""Append data to an array node
|
|
874
|
+
|
|
875
|
+
The data are appended along the first dimension (numpy C-order).
|
|
876
|
+
|
|
877
|
+
Parameters
|
|
878
|
+
----------
|
|
879
|
+
val : array-like
|
|
880
|
+
a numpy array, a sequence, or a list of strings
|
|
881
|
+
|
|
882
|
+
Examples
|
|
883
|
+
--------
|
|
884
|
+
>>> f = gds.gdsfile()
|
|
885
|
+
>>> f.create('test.gds')
|
|
886
|
+
>>> n = f.root().add('x', storage='int32', valdim=[0, 3])
|
|
887
|
+
>>> for i in range(4):
|
|
888
|
+
... n.append(np.full((1, 3), i, dtype=np.int32))
|
|
889
|
+
>>> n.read()
|
|
890
|
+
array([[0, 0, 0],
|
|
891
|
+
[1, 1, 1],
|
|
892
|
+
[2, 2, 2],
|
|
893
|
+
[3, 3, 3]], dtype=int32)
|
|
894
|
+
|
|
895
|
+
A compressed node needs readmode() before reading:
|
|
896
|
+
|
|
897
|
+
>>> n = f.root().add('z', storage='int32', compress='ZIP')
|
|
898
|
+
>>> n.append(np.arange(3, dtype=np.int32))
|
|
899
|
+
>>> n.append(np.arange(3, dtype=np.int32))
|
|
900
|
+
>>> n.readmode().read()
|
|
901
|
+
array([0, 1, 2, 0, 1, 2], dtype=int32)
|
|
902
|
+
>>> f.close()
|
|
903
|
+
>>> import os
|
|
904
|
+
>>> os.remove('test.gds')
|
|
905
|
+
"""
|
|
906
|
+
obj, _dim, _is_str = _prepare_value(val)
|
|
907
|
+
cc.append_gdsn(self.idx, self.pid, obj)
|
|
908
|
+
|
|
909
|
+
|
|
910
|
+
def write(self, val, start=None, count=None):
|
|
911
|
+
"""Write data to a node
|
|
912
|
+
|
|
913
|
+
Write data to the whole array, or to a sub-region when both 'start'
|
|
914
|
+
and 'count' are given.
|
|
915
|
+
|
|
916
|
+
Parameters
|
|
917
|
+
----------
|
|
918
|
+
val : array-like
|
|
919
|
+
a numpy array, a sequence, or a list of strings
|
|
920
|
+
start : a list of ints, optional
|
|
921
|
+
0-based starting position for each dimension
|
|
922
|
+
count : a list of ints, optional
|
|
923
|
+
the length along each dimension
|
|
924
|
+
|
|
925
|
+
Examples
|
|
926
|
+
--------
|
|
927
|
+
>>> f = gds.gdsfile()
|
|
928
|
+
>>> f.create('test.gds')
|
|
929
|
+
>>> n = f.root().add('m', np.zeros((3, 4), dtype=np.int32))
|
|
930
|
+
>>> n.write(np.array([[1, 2], [3, 4]], dtype=np.int32),
|
|
931
|
+
... start=[1, 1], count=[2, 2])
|
|
932
|
+
>>> n.read()
|
|
933
|
+
array([[0, 0, 0, 0],
|
|
934
|
+
[0, 1, 2, 0],
|
|
935
|
+
[0, 3, 4, 0]], dtype=int32)
|
|
936
|
+
>>> f.close()
|
|
937
|
+
>>> import os
|
|
938
|
+
>>> os.remove('test.gds')
|
|
939
|
+
"""
|
|
940
|
+
obj, dim, _is_str = _prepare_value(val)
|
|
941
|
+
if start is None and count is None:
|
|
942
|
+
cc.writeall_gdsn(self.idx, self.pid, obj, [int(d) for d in dim])
|
|
943
|
+
elif start is not None and count is not None:
|
|
944
|
+
cc.write_gdsn(self.idx, self.pid, obj,
|
|
945
|
+
[int(s) for s in start], [int(c) for c in count])
|
|
946
|
+
else:
|
|
947
|
+
raise ValueError("'start' and 'count' should be both None or both set.")
|
|
948
|
+
|
|
949
|
+
|
|
950
|
+
def setdim(self, dim, permute=False):
|
|
951
|
+
"""Set the dimensions of an array node
|
|
952
|
+
|
|
953
|
+
Parameters
|
|
954
|
+
----------
|
|
955
|
+
dim : a list of ints
|
|
956
|
+
the new dimensions
|
|
957
|
+
permute : bool
|
|
958
|
+
if True, permute the data when reshaping
|
|
959
|
+
|
|
960
|
+
Examples
|
|
961
|
+
--------
|
|
962
|
+
>>> f = gds.gdsfile()
|
|
963
|
+
>>> f.create('test.gds')
|
|
964
|
+
>>> n = f.root().add('x', np.arange(6, dtype=np.int32))
|
|
965
|
+
>>> n.setdim([2, 3])
|
|
966
|
+
>>> n.read()
|
|
967
|
+
array([[0, 1, 2],
|
|
968
|
+
[3, 4, 5]], dtype=int32)
|
|
969
|
+
>>> f.close()
|
|
970
|
+
>>> import os
|
|
971
|
+
>>> os.remove('test.gds')
|
|
972
|
+
"""
|
|
973
|
+
cc.setdim_gdsn(self.idx, self.pid, [int(d) for d in dim], bool(permute))
|
|
974
|
+
|
|
975
|
+
|
|
976
|
+
def compression(self, compress=''):
|
|
977
|
+
"""Set the compression mode of a node
|
|
978
|
+
|
|
979
|
+
Parameters
|
|
980
|
+
----------
|
|
981
|
+
compress : str
|
|
982
|
+
the compression method, e.g. '', 'ZIP', 'LZMA_RA', 'LZ4_RA'
|
|
983
|
+
|
|
984
|
+
Returns
|
|
985
|
+
-------
|
|
986
|
+
gdsnode (self)
|
|
987
|
+
|
|
988
|
+
Examples
|
|
989
|
+
--------
|
|
990
|
+
>>> f = gds.gdsfile()
|
|
991
|
+
>>> f.create('test.gds')
|
|
992
|
+
>>> n = f.root().add('x', np.arange(5, dtype=np.int32))
|
|
993
|
+
>>> n.compression('ZIP').readmode().description()['compress']
|
|
994
|
+
'ZIP.def'
|
|
995
|
+
|
|
996
|
+
Appending to a node whose compression is closed (closezip=True)
|
|
997
|
+
needs it decompressed first:
|
|
998
|
+
|
|
999
|
+
>>> n = f.root().add('y', np.arange(5, dtype=np.int32),
|
|
1000
|
+
... compress='ZIP', closezip=True)
|
|
1001
|
+
>>> n.compression('').append(np.arange(5, 8, dtype=np.int32))
|
|
1002
|
+
>>> n.read()
|
|
1003
|
+
array([0, 1, 2, 3, 4, 5, 6, 7], dtype=int32)
|
|
1004
|
+
>>> f.close()
|
|
1005
|
+
>>> import os
|
|
1006
|
+
>>> os.remove('test.gds')
|
|
1007
|
+
"""
|
|
1008
|
+
cc.compression_gdsn(self.idx, self.pid, compress)
|
|
1009
|
+
return self
|
|
1010
|
+
|
|
1011
|
+
|
|
1012
|
+
def readmode(self):
|
|
1013
|
+
"""Close the compression and switch to read mode
|
|
1014
|
+
|
|
1015
|
+
A compressed node stays open for appending after add() (closezip=False,
|
|
1016
|
+
as in R), and readmode() should be called before reading it.
|
|
1017
|
+
|
|
1018
|
+
Returns
|
|
1019
|
+
-------
|
|
1020
|
+
gdsnode (self)
|
|
1021
|
+
|
|
1022
|
+
Examples
|
|
1023
|
+
--------
|
|
1024
|
+
>>> f = gds.gdsfile()
|
|
1025
|
+
>>> f.create('test.gds')
|
|
1026
|
+
>>> n = f.root().add('x', np.arange(5, dtype=np.int32), compress='ZIP')
|
|
1027
|
+
>>> n.append(np.arange(5, 8, dtype=np.int32))
|
|
1028
|
+
>>> n.readmode().read()
|
|
1029
|
+
array([0, 1, 2, 3, 4, 5, 6, 7], dtype=int32)
|
|
1030
|
+
>>> f.close()
|
|
1031
|
+
>>> import os
|
|
1032
|
+
>>> os.remove('test.gds')
|
|
1033
|
+
"""
|
|
1034
|
+
cc.readmode_gdsn(self.idx, self.pid)
|
|
1035
|
+
return self
|
|
1036
|
+
|
|
1037
|
+
|
|
1038
|
+
def putattr(self, name, val):
|
|
1039
|
+
"""Add or set an attribute
|
|
1040
|
+
|
|
1041
|
+
Parameters
|
|
1042
|
+
----------
|
|
1043
|
+
name : str
|
|
1044
|
+
the attribute name
|
|
1045
|
+
val : int, float, bool, str, or a list of these
|
|
1046
|
+
the attribute value
|
|
1047
|
+
|
|
1048
|
+
Examples
|
|
1049
|
+
--------
|
|
1050
|
+
>>> f = gds.gdsfile()
|
|
1051
|
+
>>> f.create('test.gds')
|
|
1052
|
+
>>> n = f.root().add('position', np.arange(5, dtype=np.int32))
|
|
1053
|
+
>>> n.putattr('unit', 'bp')
|
|
1054
|
+
>>> n.putattr('tags', [1, 2, 3])
|
|
1055
|
+
>>> n.getattr()
|
|
1056
|
+
{'unit': 'bp', 'tags': [1, 2, 3]}
|
|
1057
|
+
>>> f.close()
|
|
1058
|
+
>>> import os
|
|
1059
|
+
>>> os.remove('test.gds')
|
|
1060
|
+
"""
|
|
1061
|
+
cc.putattr_gdsn(self.idx, self.pid, name, val)
|
|
1062
|
+
|
|
1063
|
+
|
|
1064
|
+
def delattr(self, name):
|
|
1065
|
+
"""Delete an attribute
|
|
1066
|
+
|
|
1067
|
+
Parameters
|
|
1068
|
+
----------
|
|
1069
|
+
name : str
|
|
1070
|
+
the attribute name
|
|
1071
|
+
"""
|
|
1072
|
+
cc.delattr_gdsn(self.idx, self.pid, name)
|
|
1073
|
+
|
|
1074
|
+
|
|
1075
|
+
def exist(self, path):
|
|
1076
|
+
"""Test whether a path exists under this folder node
|
|
1077
|
+
|
|
1078
|
+
Parameters
|
|
1079
|
+
----------
|
|
1080
|
+
path : str
|
|
1081
|
+
the path with '/' as a separator
|
|
1082
|
+
|
|
1083
|
+
Returns
|
|
1084
|
+
-------
|
|
1085
|
+
bool
|
|
1086
|
+
|
|
1087
|
+
Examples
|
|
1088
|
+
--------
|
|
1089
|
+
>>> f = gds.gdsfile()
|
|
1090
|
+
>>> f.open(gds.get_example_path('ceu_exon.gds'))
|
|
1091
|
+
>>> f.root().exist('genotype/data'), f.root().exist('genotype/xyz')
|
|
1092
|
+
(True, False)
|
|
1093
|
+
>>> f.close()
|
|
1094
|
+
"""
|
|
1095
|
+
return cc.exist_gdsn(self.idx, self.pid, path)
|
|
1096
|
+
|
|
1097
|
+
|
|
1098
|
+
def is_sparse(self):
|
|
1099
|
+
"""Test whether this node is a sparse array
|
|
1100
|
+
|
|
1101
|
+
Returns
|
|
1102
|
+
-------
|
|
1103
|
+
bool
|
|
1104
|
+
"""
|
|
1105
|
+
return cc.is_sparse_gdsn(self.idx, self.pid)
|
|
1106
|
+
|
|
1107
|
+
|
|
1108
|
+
# -------------------------------------------------------------------
|
|
1109
|
+
# Relocation, copy, caching and embedded files
|
|
1110
|
+
# -------------------------------------------------------------------
|
|
1111
|
+
|
|
1112
|
+
def moveto(self, loc_node, relpos='after'):
|
|
1113
|
+
"""Move this node relative to another node
|
|
1114
|
+
|
|
1115
|
+
Parameters
|
|
1116
|
+
----------
|
|
1117
|
+
loc_node : gdsnode
|
|
1118
|
+
the reference node (must be in the same folder unless relpos='into')
|
|
1119
|
+
relpos : str
|
|
1120
|
+
'after', 'before', 'replace', 'replace+rename', or 'into'
|
|
1121
|
+
(move into the folder 'loc_node')
|
|
1122
|
+
"""
|
|
1123
|
+
cc.moveto_gdsn(self.idx, self.pid, loc_node.idx, loc_node.pid, relpos)
|
|
1124
|
+
|
|
1125
|
+
|
|
1126
|
+
def copyto(self, name, source):
|
|
1127
|
+
"""Copy a source node into this folder under a new name
|
|
1128
|
+
|
|
1129
|
+
Parameters
|
|
1130
|
+
----------
|
|
1131
|
+
name : str
|
|
1132
|
+
the name of the copy
|
|
1133
|
+
source : gdsnode
|
|
1134
|
+
the node to copy
|
|
1135
|
+
|
|
1136
|
+
Returns
|
|
1137
|
+
-------
|
|
1138
|
+
gdsnode
|
|
1139
|
+
"""
|
|
1140
|
+
v = gdsnode()
|
|
1141
|
+
v.idx, v.pid = cc.copyto_gdsn(self.idx, self.pid, name,
|
|
1142
|
+
source.idx, source.pid)
|
|
1143
|
+
return v
|
|
1144
|
+
|
|
1145
|
+
|
|
1146
|
+
def assign(self, source):
|
|
1147
|
+
"""Assign (deep copy) the content of a source node to this node
|
|
1148
|
+
|
|
1149
|
+
Parameters
|
|
1150
|
+
----------
|
|
1151
|
+
source : gdsnode
|
|
1152
|
+
the source node
|
|
1153
|
+
"""
|
|
1154
|
+
cc.assign_gdsn(self.idx, self.pid, source.idx, source.pid)
|
|
1155
|
+
|
|
1156
|
+
|
|
1157
|
+
def cache(self):
|
|
1158
|
+
"""Cache the data associated with this node in memory"""
|
|
1159
|
+
cc.cache_gdsn(self.idx, self.pid)
|
|
1160
|
+
|
|
1161
|
+
|
|
1162
|
+
def unload(self):
|
|
1163
|
+
"""Unload this node from memory"""
|
|
1164
|
+
cc.unload_gdsn(self.idx, self.pid)
|
|
1165
|
+
|
|
1166
|
+
|
|
1167
|
+
def addfile(self, name, filename, compress='', replace=False, visible=True):
|
|
1168
|
+
"""Embed an external file as a stream container under this folder
|
|
1169
|
+
|
|
1170
|
+
Parameters
|
|
1171
|
+
----------
|
|
1172
|
+
name : str
|
|
1173
|
+
the name of the new node
|
|
1174
|
+
filename : str
|
|
1175
|
+
the path of the external file to embed
|
|
1176
|
+
compress : str
|
|
1177
|
+
the compression method
|
|
1178
|
+
replace : bool
|
|
1179
|
+
if True, replace an existing node of the same name
|
|
1180
|
+
visible : bool
|
|
1181
|
+
if False, mark the node hidden
|
|
1182
|
+
|
|
1183
|
+
Returns
|
|
1184
|
+
-------
|
|
1185
|
+
gdsnode
|
|
1186
|
+
"""
|
|
1187
|
+
v = gdsnode()
|
|
1188
|
+
v.idx, v.pid = cc.addfile_gdsn(self.idx, self.pid, name, filename,
|
|
1189
|
+
compress, bool(replace), bool(visible))
|
|
1190
|
+
return v
|
|
1191
|
+
|
|
1192
|
+
|
|
1193
|
+
def getfile(self, out_filename):
|
|
1194
|
+
"""Extract an embedded file node to an external file
|
|
1195
|
+
|
|
1196
|
+
Parameters
|
|
1197
|
+
----------
|
|
1198
|
+
out_filename : str
|
|
1199
|
+
the path of the output file
|
|
1200
|
+
"""
|
|
1201
|
+
cc.getfile_gdsn(self.idx, self.pid, out_filename)
|
|
1202
|
+
|
|
1203
|
+
|
|
1204
|
+
# -------------------------------------------------------------------
|
|
1205
|
+
# Computation over the data
|
|
1206
|
+
# -------------------------------------------------------------------
|
|
1207
|
+
|
|
1208
|
+
def apply(self, margin, fun, *args, as_is='none', **kwargs):
|
|
1209
|
+
"""Apply a function over a margin of this array node
|
|
1210
|
+
|
|
1211
|
+
Iterate over axis 'margin' (0-based, in numpy C-order), calling
|
|
1212
|
+
``fun(slice, *args, **kwargs)`` for each index, where 'slice' is the
|
|
1213
|
+
sub-array with the margin axis removed.
|
|
1214
|
+
|
|
1215
|
+
Parameters
|
|
1216
|
+
----------
|
|
1217
|
+
margin : int
|
|
1218
|
+
the axis to iterate over (0-based)
|
|
1219
|
+
fun : callable
|
|
1220
|
+
the function applied to each slice
|
|
1221
|
+
as_is : str
|
|
1222
|
+
'none' (return None), 'list' (collect results into a list), or
|
|
1223
|
+
'unlist' (concatenate results into a single numpy array)
|
|
1224
|
+
|
|
1225
|
+
Returns
|
|
1226
|
+
-------
|
|
1227
|
+
None, list, or numpy array depending on 'as_is'
|
|
1228
|
+
|
|
1229
|
+
Examples
|
|
1230
|
+
--------
|
|
1231
|
+
The number of reference alleles (coded as 0) of the first variants:
|
|
1232
|
+
|
|
1233
|
+
>>> f = gds.gdsfile()
|
|
1234
|
+
>>> f.open(gds.get_example_path('ceu_exon.gds'))
|
|
1235
|
+
>>> n = f.index('genotype/data') # variant x sample x allele
|
|
1236
|
+
>>> n.apply(0, lambda g: int((g == 0).sum()), as_is='unlist')[:5]
|
|
1237
|
+
array([110, 105, 148, 18, 165])
|
|
1238
|
+
>>> f.close()
|
|
1239
|
+
"""
|
|
1240
|
+
return apply_gdsn(self, margin, fun, *args, as_is=as_is, **kwargs)
|
|
1241
|
+
|
|
1242
|
+
|
|
1243
|
+
def digest(self, algorithm='md5'):
|
|
1244
|
+
"""Compute a hash digest of this node's data
|
|
1245
|
+
|
|
1246
|
+
The digest is computed over the bytes of the data as read by read(),
|
|
1247
|
+
in storage order (little-endian for numeric data; UTF-8 for strings).
|
|
1248
|
+
For 32-bit integers, integers of up to 8 bits and 64-bit floating
|
|
1249
|
+
numbers, it is the same as ``digest.gdsn(node, action="Robject")`` of
|
|
1250
|
+
R gdsfmt (not the default action, which digests the stored, possibly
|
|
1251
|
+
compressed, bytes).
|
|
1252
|
+
|
|
1253
|
+
Parameters
|
|
1254
|
+
----------
|
|
1255
|
+
algorithm : str
|
|
1256
|
+
a hashlib algorithm name, e.g. 'md5', 'sha1', 'sha256', 'sha512'
|
|
1257
|
+
|
|
1258
|
+
Returns
|
|
1259
|
+
-------
|
|
1260
|
+
str : the hex digest
|
|
1261
|
+
|
|
1262
|
+
Examples
|
|
1263
|
+
--------
|
|
1264
|
+
>>> f = gds.gdsfile()
|
|
1265
|
+
>>> f.open(gds.get_example_path('ceu_exon.gds'))
|
|
1266
|
+
>>> f.index('position').digest()
|
|
1267
|
+
'3149ab9bd8ae18ba8b33e61deedeb204'
|
|
1268
|
+
>>> f.close()
|
|
1269
|
+
"""
|
|
1270
|
+
import hashlib
|
|
1271
|
+
data = self.read()
|
|
1272
|
+
arr = np.asarray(data)
|
|
1273
|
+
if arr.dtype.kind in ('U', 'S', 'O'):
|
|
1274
|
+
flat = arr.ravel(order='C').tolist()
|
|
1275
|
+
b = ''.join('' if x is None else str(x) for x in flat).encode('utf-8')
|
|
1276
|
+
else:
|
|
1277
|
+
b = np.ascontiguousarray(arr).tobytes()
|
|
1278
|
+
h = hashlib.new(algorithm)
|
|
1279
|
+
h.update(b)
|
|
1280
|
+
return h.hexdigest()
|
|
1281
|
+
|
|
1282
|
+
|
|
1283
|
+
def summarize(self):
|
|
1284
|
+
"""Summarize the data of this node
|
|
1285
|
+
|
|
1286
|
+
Returns
|
|
1287
|
+
-------
|
|
1288
|
+
dict : for numeric data {min, max, mean, num_na, n}; otherwise {n}
|
|
1289
|
+
|
|
1290
|
+
Examples
|
|
1291
|
+
--------
|
|
1292
|
+
>>> f = gds.gdsfile()
|
|
1293
|
+
>>> f.open(gds.get_example_path('ceu_exon.gds'))
|
|
1294
|
+
>>> s = f.index('position').summarize()
|
|
1295
|
+
>>> s['min'], s['max'], s['num_na'], s['n']
|
|
1296
|
+
(610217.0, 237954571.0, 0, 1348)
|
|
1297
|
+
>>> f.close()
|
|
1298
|
+
"""
|
|
1299
|
+
d = self.description()
|
|
1300
|
+
data = self.read()
|
|
1301
|
+
arr = np.asarray(data)
|
|
1302
|
+
if d['type'] in ('Integer', 'Factor', 'Logical', 'Real') and \
|
|
1303
|
+
arr.dtype.kind not in ('U', 'S', 'O'):
|
|
1304
|
+
v = arr.astype(float, copy=False)
|
|
1305
|
+
finite = v[np.isfinite(v)]
|
|
1306
|
+
n = int(v.size)
|
|
1307
|
+
if finite.size:
|
|
1308
|
+
return dict(min=float(finite.min()), max=float(finite.max()),
|
|
1309
|
+
mean=float(finite.mean()), num_na=n - int(finite.size), n=n)
|
|
1310
|
+
return dict(min=float('nan'), max=float('nan'), mean=float('nan'),
|
|
1311
|
+
num_na=n, n=n)
|
|
1312
|
+
return dict(n=int(arr.size))
|
|
1313
|
+
|
|
1314
|
+
|
|
1315
|
+
def show(self, attribute=False, all=False, expand=True):
|
|
1316
|
+
r"""Display a GDS node
|
|
1317
|
+
|
|
1318
|
+
Display the structure of a GDS node, see gdsfile.show().
|
|
1319
|
+
|
|
1320
|
+
Parameters
|
|
1321
|
+
----------
|
|
1322
|
+
attribute : bool
|
|
1323
|
+
if True, show the attribute(s)
|
|
1324
|
+
all : bool
|
|
1325
|
+
if False, hide GDS nodes with an attribute "R.invisible"
|
|
1326
|
+
expand : bool
|
|
1327
|
+
if True, show the nodes in a folder
|
|
1328
|
+
|
|
1329
|
+
Returns
|
|
1330
|
+
-------
|
|
1331
|
+
None
|
|
1332
|
+
|
|
1333
|
+
Examples
|
|
1334
|
+
--------
|
|
1335
|
+
>>> f = gds.gdsfile()
|
|
1336
|
+
>>> f.open(gds.get_example_path('ceu_exon.gds'))
|
|
1337
|
+
>>> f.index('genotype').show()
|
|
1338
|
+
+ genotype [ ] *
|
|
1339
|
+
|--+ data { Bit2 1348x90x2 LZMA_ra(26.3%), 15.6K } *
|
|
1340
|
+
|--+ extra.index { Int32 0x3 LZMA_ra, 19B } *
|
|
1341
|
+
\--+ extra { Int16 0 LZMA_ra, 19B }
|
|
1342
|
+
>>> f.close()
|
|
1343
|
+
"""
|
|
1344
|
+
|
|
1345
|
+
def enum(node, prefix, fullname, last, attr, expand):
|
|
1346
|
+
d = node.description()
|
|
1347
|
+
if d['type'] == 'Label':
|
|
1348
|
+
lText = ' '; rText = ' '
|
|
1349
|
+
elif d['type'] == 'VFolder':
|
|
1350
|
+
if d['good']:
|
|
1351
|
+
lText = '[ -->'; rText = ']'
|
|
1352
|
+
else:
|
|
1353
|
+
lText = '[ -X-'; rText = ']'; expand = False
|
|
1354
|
+
elif d['type'] == 'Folder':
|
|
1355
|
+
lText = '['; rText = ']'
|
|
1356
|
+
elif d['type'] == 'Unknown':
|
|
1357
|
+
lText = ' -X-'; rText = ''; expand = False
|
|
1358
|
+
else:
|
|
1359
|
+
lText = '{'; rText = '}'
|
|
1360
|
+
|
|
1361
|
+
s = ''.join([ prefix, '+ ', node.name(fullname), ' ', lText,
|
|
1362
|
+
' ', d['trait'] ])
|
|
1363
|
+
|
|
1364
|
+
# if logical, factor, list, or data.frame
|
|
1365
|
+
if d['type'] == 'Logical':
|
|
1366
|
+
s = s + ',logical'
|
|
1367
|
+
elif d['type'] == 'Factor':
|
|
1368
|
+
s = s + ',factor'
|
|
1369
|
+
|
|
1370
|
+
# show the dimension
|
|
1371
|
+
if d['dim'] != None:
|
|
1372
|
+
s = s + ' ' + ('x'.join([str(i) for i in d['dim']]))
|
|
1373
|
+
|
|
1374
|
+
# show compression
|
|
1375
|
+
if d['encoder'] != '':
|
|
1376
|
+
if attribute:
|
|
1377
|
+
s = s + ' ' + d['compress']
|
|
1378
|
+
else:
|
|
1379
|
+
s = s + ' ' + d['encoder']
|
|
1380
|
+
|
|
1381
|
+
if np.isfinite(d['cpratio']):
|
|
1382
|
+
if d['cpratio'] >= 0.10:
|
|
1383
|
+
s = s + ('(%0.1f%%)' % (100*d['cpratio']))
|
|
1384
|
+
else:
|
|
1385
|
+
s = s + ('(%0.2f%%)' % (100*d['cpratio']))
|
|
1386
|
+
|
|
1387
|
+
sz = d['size']
|
|
1388
|
+
if np.isfinite(sz):
|
|
1389
|
+
if sz >= 1073741824:
|
|
1390
|
+
s = s + ', %.1fG' % (sz/1073741824)
|
|
1391
|
+
elif sz >= 1048576:
|
|
1392
|
+
s = s + ', %.1fM' % (sz/1048576)
|
|
1393
|
+
elif sz >= 1024:
|
|
1394
|
+
s = s + ', %.1fK' % (sz/1024)
|
|
1395
|
+
else:
|
|
1396
|
+
s = s + ', %gB' % sz
|
|
1397
|
+
|
|
1398
|
+
s = s + ' ' + rText
|
|
1399
|
+
|
|
1400
|
+
# attributes
|
|
1401
|
+
at = node.getattr()
|
|
1402
|
+
if at != None:
|
|
1403
|
+
s = s + ' *'
|
|
1404
|
+
if attr:
|
|
1405
|
+
s = s + '< ' + str(at)
|
|
1406
|
+
|
|
1407
|
+
print(s)
|
|
1408
|
+
|
|
1409
|
+
if expand and d['type']=='Folder':
|
|
1410
|
+
nm = node.ls(all)
|
|
1411
|
+
for i in range(len(nm)):
|
|
1412
|
+
n = len(prefix)
|
|
1413
|
+
if i < len(nm)-1:
|
|
1414
|
+
if n >= 3:
|
|
1415
|
+
if last:
|
|
1416
|
+
s = prefix[:n-3] + ' |--'
|
|
1417
|
+
else:
|
|
1418
|
+
s = prefix[:n-3] + '| |--'
|
|
1419
|
+
else:
|
|
1420
|
+
s = '|--'
|
|
1421
|
+
else:
|
|
1422
|
+
if n >= 3:
|
|
1423
|
+
if last:
|
|
1424
|
+
s = prefix[:n-3] + ' \\--'
|
|
1425
|
+
else:
|
|
1426
|
+
s = prefix[:n-3] + '| \\--'
|
|
1427
|
+
else:
|
|
1428
|
+
s = '\\--'
|
|
1429
|
+
enum(node.index(nm[i]), s, False, i>=len(nm)-1, attr, expand)
|
|
1430
|
+
|
|
1431
|
+
return None
|
|
1432
|
+
|
|
1433
|
+
enum(self, "", True, True, attribute, expand)
|
|
1434
|
+
|
|
1435
|
+
|
|
1436
|
+
|
|
1437
|
+
|
|
1438
|
+
# cloud access (pycorearray.cloud), used by gdsfile.open()
|
|
1439
|
+
from pycorearray import cloud
|