-
Notifications
You must be signed in to change notification settings - Fork 50
/
utilities.py
179 lines (148 loc) · 5.96 KB
/
utilities.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
"""
This file is part of the openPMD-viewer.
It defines a set of helper data and functions which
are used by the other files.
Copyright 2015-2016, openPMD-viewer contributors
Authors: Remi Lehe, Axel Huebl
License: 3-Clause-BSD-LBNL
"""
import numpy as np
def chunk_to_slice(chunk):
"""
Convert an openPMD_api.ChunkInfo to slice
"""
stops = [a + b for a, b in zip(chunk.offset, chunk.extent)]
indices_per_dim = zip(chunk.offset, stops)
index_tuple = map(lambda s: slice(s[0], s[1], None), indices_per_dim)
return tuple(index_tuple)
def get_data(series, record_component, i_slice=None, pos_slice=None,
output_type=None):
"""
Extract the data from a (possibly constant) dataset
Slice the data according to the parameters i_slice and pos_slice
Parameters:
-----------
series: openpmd_api.Series
An open, readable openPMD-api series object
record_component: an openPMD.Record_Component
pos_slice: int or list of int, optional
Slice direction(s).
When None, no slicing is performed
i_slice: int or list of int, optional
Indices of slices to be taken.
output_type: a numpy type
The type to which the returned array should be converted
Returns:
--------
An np.ndarray (non-constant dataset) or a single double (constant dataset)
"""
# For back-compatibility: Convert pos_slice and i_slice to
# single-element lists if they are not lists (e.g. float
# and int respectively).
if pos_slice is not None and not isinstance(pos_slice, list):
pos_slice = [pos_slice]
if i_slice is not None and not isinstance(i_slice, list):
i_slice = [i_slice]
# ADIOS2: Actual chunks, all other: one chunk
chunks = record_component.available_chunks()
# read whole data set
if pos_slice is None:
# mask invalid regions with NaN
# note: full_like triggers a full read, thus we avoid it #340
data = np.full(record_component.shape, np.nan, record_component.dtype)
for chunk in chunks:
chunk_slice = chunk_to_slice(chunk)
# skip empty slices
# https://github.com/ornladios/ADIOS2
volume = 1
for csl in chunk_slice:
volume *= csl.stop - csl.start
if volume == 0:
continue
# read only valid region
x = record_component[chunk_slice]
series.flush()
data[chunk_slice] = x
# slice: read only part of the data set
else:
full_shape = record_component.shape
slice_shape = list(full_shape) # copy
pos_slice_sorted = pos_slice.copy() # copy for in-place sort
pos_slice_sorted.sort(reverse=True)
for dir_index in pos_slice_sorted: # remove indices in list
del slice_shape[dir_index]
# mask invalid regions with NaN
data = np.full(slice_shape, np.nan, dtype=record_component.dtype)
# build requested ND slice with respect to full data
s = []
for d in range(len(full_shape)):
if d in pos_slice:
s.append(i_slice[pos_slice.index(d)]) # one index in such directions
else: # all indices in other direction
s.append(slice(None, None, None))
s = tuple(s)
# now we check which chunks contribute to the slice
for chunk in chunks:
skip_this_chunk = False
s_valid = list(s) # same as s but reduced to valid regions in chunk
s_target = [] # starts and stops in sliced array
chunk_slice = chunk_to_slice(chunk)
# skip empty slices
# https://github.com/ornladios/ADIOS2
volume = 1
for csl in chunk_slice:
volume *= csl.stop - csl.start
if volume == 0:
continue
# read only valid region
for d, slice_d in enumerate(s):
start = chunk_slice[d].start
stop = chunk_slice[d].stop
if isinstance(slice_d, int):
# Nothing to do for s_target (dimension sliced out)
# Nothing to do for s_valid (dimension index is set)
if slice_d < start or slice_d >= stop:
# chunk not in slice line/plane
skip_this_chunk = True
else:
if slice_d.start is None or slice_d.start < start:
s_valid[d] = slice(start, s_valid[d].stop)
if slice_d.stop is None or slice_d.stop > stop:
s_valid[d] = slice(s_valid[d].start, stop)
s_target.append(slice(start, stop))
s_valid = tuple(s_valid)
s_target = tuple(s_target)
# read
if not skip_this_chunk:
x = record_component[s_valid]
series.flush()
data[s_target] = x
# Convert to the right type
if (output_type is not None) and (data.dtype != output_type):
data = data.astype( output_type )
# Scale by the conversion factor
if record_component.unit_SI != 1.0:
if np.issubdtype(data.dtype, np.floating) or \
np.issubdtype(data.dtype, np.complexfloating):
data *= record_component.unit_SI
else:
data = data * record_component.unit_SI
return data
def join_infile_path(*paths):
"""
Join path components using '/' as separator.
This method is defined as an alternative to os.path.join, which uses '\\'
as separator in Windows environments and is therefore not valid to navigate
within data files.
Parameters:
-----------
*paths: all strings with path components to join
Returns:
--------
A string with the complete path using '/' as separator.
"""
# Join path components
path = '/'.join(paths)
# Correct double slashes, if any is present
path = path.replace('//', '/')
return path