Coverage for install/scipp/compat/dict.py: 82%
84 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-11-17 01:51 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2024-11-17 01:51 +0000
1# SPDX-License-Identifier: BSD-3-Clause
2# Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
3# @author Neil Vaytet
5from __future__ import annotations
7from collections import defaultdict
9import numpy as np
11from ..core import (
12 DataArray,
13 Dataset,
14 DType,
15 Variable,
16 vector,
17 vectors,
18)
19from ..spatial import linear_transform, linear_transforms
20from ..typing import VariableLike
23def to_dict(scipp_obj: VariableLike) -> dict:
24 """Convert a Scipp object (Variable, DataArray or Dataset)
25 to a Python :class:`dict`.
27 Parameters
28 ----------
29 scipp_obj:
30 Scipp object to be converted to a python dict.
32 Returns
33 -------
34 :
35 A dict containing all the information necessary to fully define
36 the supplied Scipp object.
38 See Also
39 --------
40 scipp.from_dict
41 """
42 if isinstance(scipp_obj, Variable):
43 return _variable_to_dict(scipp_obj)
44 elif isinstance(scipp_obj, DataArray):
45 return _data_array_to_dict(scipp_obj)
46 elif isinstance(scipp_obj, Dataset):
47 # TODO: This currently duplicates all coordinates that would otherwise
48 # be at the Dataset level onto the individual DataArrays. We are also
49 # manually duplicating all attributes, since these are not carried when
50 # accessing items of a Dataset.
51 out = {}
52 for name, item in scipp_obj.items():
53 out[name] = _data_array_to_dict(item)
54 return out
57def _vec_parser(x, shp):
58 """Parse vector_3_float to 2D NumPy array."""
59 return np.array(x)
62def _variable_to_dict(v):
63 """Convert a Scipp Variable to a python dict."""
64 out = {
65 "dims": _dims_to_strings(v.dims),
66 "shape": v.shape,
67 "unit": v.unit,
68 "dtype": v.dtype,
69 }
70 if not v.aligned:
71 out["aligned"] = False
73 # Use defaultdict to return the raw values/variances by default
74 dtype_parser = defaultdict(lambda: lambda x, y: x)
75 # Using raw dtypes as dict keys doesn't appear to work, so we need to
76 # convert to strings.
77 dtype_parser.update(
78 {
79 str(DType.vector3): _vec_parser,
80 str(DType.linear_transform3): _vec_parser,
81 str(DType.string): _vec_parser,
82 }
83 )
85 str_dtype = str(v.dtype)
87 # Check if variable is 0D:
88 suffix = "s" if len(out["dims"]) > 0 else ""
89 out["values"] = dtype_parser[str_dtype](getattr(v, "value" + suffix), v.shape)
90 var = getattr(v, "variance" + suffix)
91 out["variances"] = (
92 dtype_parser[str_dtype](var, v.shape) if var is not None else None
93 )
94 return out
97def _data_array_to_dict(da):
98 """Convert a Scipp DataArray to a python dict."""
99 out = {"coords": {}, "masks": {}, "attrs": {}}
100 for key in out.keys():
101 for name, item in getattr(da, key).items():
102 out[key][str(name)] = _variable_to_dict(item)
103 out['coords'] = out.pop('coords')
104 out["data"] = _variable_to_dict(da.data)
105 out["name"] = da.name
106 return out
109def _dims_to_strings(dims):
110 """Convert dims that may or may not be strings to strings."""
111 return tuple(str(dim) for dim in dims)
114def from_dict(dict_obj: dict) -> VariableLike:
115 """Convert a Python dict to a Scipp Variable, DataArray or Dataset.
117 If the input keys contain both `'coords'` and `'data'`, then a DataArray is
118 returned.
119 If the input keys contain both `'dims'` and `'values'`, as Variable is
120 returned.
121 Otherwise, a Dataset is returned.
123 Parameters
124 ----------
125 dict_obj:
126 A python dict to be converted to a scipp object.
128 Returns
129 -------
130 :
131 A Scipp Variable, DataArray or Dataset.
133 See Also
134 --------
135 scipp.to_dict
136 """
137 keys_as_set = set(dict_obj.keys())
138 if {"coords", "data"}.issubset(keys_as_set):
139 # Case of a DataArray-like dict (most-likely)
140 return _dict_to_data_array(dict_obj)
141 elif keys_as_set.issubset(
142 {"dims", "values", "variances", "unit", "dtype", "shape", "aligned"}
143 ):
144 # Case of a Variable-like dict (most-likely)
145 return _dict_to_variable(dict_obj)
146 else:
147 # Case of a Dataset-like dict
148 out = Dataset(
149 {key: _dict_to_data_array(item) for key, item in dict_obj.items()}
150 )
151 return out
154def _dict_to_variable(d):
155 """Convert a Python dict to a Scipp Variable."""
156 d = dict(d)
157 # The Variable constructor does not accept both `shape` and `values`. If
158 # `values` is present, remove `shape` from the list.
159 keylist = set(d.keys())
160 if "values" in keylist and "shape" in keylist:
161 keylist.remove("shape")
162 out = {}
164 for key in keylist:
165 if key == "dtype" and isinstance(d[key], str):
166 out[key] = getattr(DType, d[key])
167 else:
168 out[key] = d[key]
169 # Hack for types that cannot be directly constructed using Variable()
170 if out['dims']:
171 init = {'vector3': vectors, 'linear_transform3': linear_transforms}
172 else:
173 init = {'vector3': vector, 'linear_transform3': linear_transform}
174 make_var = init.get(str(out.get('dtype', None)), Variable)
175 if make_var != Variable:
176 if not out['dims']:
177 out['value'] = out['values']
178 del out['values']
179 del out['dims']
180 for key in ['dtype', 'variance', 'variances']:
181 if key in out:
182 del out[key]
183 var = make_var(**out)
184 return var
187def _dict_to_data_array(d):
188 """Convert a Python dict to a Scipp DataArray."""
189 d = dict(d)
190 if "data" not in d:
191 raise KeyError(
192 "To create a DataArray, the supplied dict must contain "
193 f"'data'. Got {d.keys()}."
194 )
195 out = {"coords": {}, "masks": {}, "attrs": {}}
196 for key in out.keys():
197 if key in d:
198 for name, item in d[key].items():
199 out[key][name] = _dict_to_variable(item)
200 out["data"] = _dict_to_variable(d["data"])
201 return DataArray(**out)