pycorearray 0.2.0__cp313-cp313-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.
@@ -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