Skip to content

Commit 7ec3982

Browse files
authored
Merge pull request #193 from sandialabs/add-json-converter
Add json converter
2 parents c9906d7 + addde54 commit 7ec3982

File tree

9 files changed

+113
-89
lines changed

9 files changed

+113
-89
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Passed with test_instrument_driver version v0.1.0 tested on pyscan version v0.5.3 at 2024-07-05 11:41:28
1+
Passed with test_instrument_driver version v0.1.0 tested on pyscan version v0.5.4 at 2024-07-09 09:46:08
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Passed with test_voltage version v0.1.0 tested on pyscan version v0.5.3 at 2024-07-05 11:41:28
1+
Passed with test_voltage version v0.1.0 tested on pyscan version v0.5.4 at 2024-07-09 09:46:08

pyscan/general/__init__.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
1+
# objects
2+
from .item_attribute import ItemAttribute
3+
from .pyscan_json_encoder import PyscanJSONEncoder
14

25
# methods
36
from .d_range import drange
47
from .first_string import first_string
58
from .is_list_type import is_list_type
69
from .is_numeric_type import is_numeric_type
710
from .quadrature_sum import quadrature_sum
8-
from .recursive_to_dict import recursive_to_dict
9-
from .recursive_to_item_attribute import recursive_to_itemattribute
1011
from .same_length import same_length
1112
from .set_difference import set_difference
1213
from .stack_or_append import stack_or_append
1314
from .get_pyscan_version import get_pyscan_version
14-
15-
# objects
16-
from .item_attribute import ItemAttribute
15+
from .pyscan_json_decoder import PyscanJSONDecoder
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from pyscan.general import ItemAttribute
2+
import json
3+
4+
5+
class PyscanJSONDecoder(json.JSONDecoder):
6+
def __init__(self, *args, **kwargs):
7+
super().__init__(object_hook=self.item_attribute_object_hook, *args, **kwargs)
8+
9+
def item_attribute_object_hook(self, data):
10+
'''
11+
Function to be used as the object_hook in json.loads to convert dictionaries
12+
into ItemAttribute objects.
13+
14+
Parameters
15+
----------
16+
data : dict
17+
The dictionary to convert.
18+
19+
Returns
20+
-------
21+
ItemAttribute
22+
'''
23+
if type(data) is dict:
24+
new_data = ItemAttribute(data)
25+
else:
26+
new_data = data
27+
28+
return new_data
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import json
2+
import numpy as np
3+
from pyscan.general.item_attribute import ItemAttribute
4+
from pyscan.drivers.instrument_driver import InstrumentDriver
5+
import inspect
6+
from pathlib import Path, WindowsPath
7+
8+
9+
class PyscanJSONEncoder(json.JSONEncoder):
10+
"""
11+
A custom JSON encoder subclass that extends json.JSONEncoder to handle additional Python data types
12+
not supported by the default JSON encoder. This includes handling of custom objects, numpy data types,
13+
function objects, and pathlib Path objects, among others.
14+
15+
The encoder attempts to serialize various non-standard objects to a JSON-compatible format, applying
16+
specific conversions based on the object type. If an object is already JSON serializable, the encoder
17+
falls back to the default method provided by the superclass.
18+
"""
19+
20+
def default(self, obj, debug=False):
21+
"""
22+
Convert non-serializable objects to a serializable format.
23+
24+
Parameters:
25+
- obj: The object to serialize.
26+
27+
Returns:
28+
- A serializable representation of `obj` or raises a TypeError if the object cannot be serialized.
29+
"""
30+
if debug is True:
31+
print(f"Processing object {obj} of type: {type(obj)}")
32+
try:
33+
print(f"Obj name is: {obj.__name__}")
34+
except:
35+
pass
36+
# keys_to_skip = {'logger', 'expt_thread', 'data_path', 'instrument', 'module_id_string', 'spec'}
37+
38+
if type(obj) is type:
39+
return obj.__name__
40+
elif isinstance(obj, (InstrumentDriver, ItemAttribute)):
41+
if debug is True:
42+
print(f"obj {obj} was instance of InstrumentDriver and or ItemAttribute.")
43+
return obj.__dict__
44+
elif isinstance(obj, (range, tuple)):
45+
if debug is True:
46+
print(f"obj {obj} was instance of {type(obj)}.")
47+
return list(obj)
48+
# Handle numpy integers
49+
elif isinstance(obj, np.integer):
50+
if debug is True:
51+
print(f"Object {obj} is a numpy integer, converting to int.")
52+
return int(obj)
53+
# Handle numpy floating values
54+
elif isinstance(obj, np.floating):
55+
if debug is True:
56+
print(f"Object {obj} is a numpy floating value, converting to float.")
57+
return float(obj)
58+
# Handle numpy arrays
59+
elif isinstance(obj, np.ndarray):
60+
if debug is True:
61+
print(f"Object {obj} is a numpy array, converting to list.")
62+
return obj.tolist()
63+
elif callable(obj):
64+
if debug is True:
65+
print(f"obj {obj} is a function, returning source code.")
66+
return inspect.getsource(obj) # Talk with Andy about this and perhaps implementing in load_expt?
67+
elif isinstance(obj, (WindowsPath, Path)): # This covers both WindowsPath and PosixPath
68+
if debug is True:
69+
print(f"obj {obj} is a Path or WindowsPath, returning string of the path.")
70+
return str(obj)
71+
else:
72+
return super().default(obj)

pyscan/general/recursive_to_dict.py

Lines changed: 0 additions & 44 deletions
This file was deleted.

pyscan/general/recursive_to_item_attribute.py

Lines changed: 0 additions & 28 deletions
This file was deleted.

pyscan/measurement/abstract_experiment.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
from threading import Thread as thread
77
from time import strftime
88
from pyscan.general import (ItemAttribute,
9-
recursive_to_dict,
109
is_list_type)
1110
from pyscan.measurement.scans import PropertyScan, RepeatScan
11+
from pyscan.general.pyscan_json_encoder import PyscanJSONEncoder
1212

1313

1414
class AbstractExperiment(ItemAttribute):
@@ -235,11 +235,9 @@ def save_metadata(self):
235235
save_path = self.runinfo.data_path / '{}.hdf5'.format(self.runinfo.long_name)
236236
save_name = str(save_path.absolute())
237237

238-
data = recursive_to_dict(self.__dict__)
239-
240238
with h5py.File(save_name, 'a') as f:
241-
f.attrs['runinfo'] = json.dumps(data['runinfo'])
242-
f.attrs['devices'] = json.dumps(data['devices'])
239+
f.attrs['runinfo'] = json.dumps(self.runinfo, cls=PyscanJSONEncoder)
240+
f.attrs['devices'] = json.dumps(self.devices, cls=PyscanJSONEncoder)
243241

244242
def start_thread(self):
245243
'''Starts experiment as a background thread, this works in conjunction with live plot

pyscan/measurement/load_experiment.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
import pickle
44
import json
55
from pathlib import Path
6-
from pyscan.general import ItemAttribute, recursive_to_itemattribute
6+
from pyscan.general.item_attribute import ItemAttribute
7+
from pyscan.general.pyscan_json_decoder import PyscanJSONDecoder
78

89

910
def load_experiment(file_name):
@@ -57,11 +58,9 @@ def load_experiment(file_name):
5758
expt.devices = ItemAttribute()
5859

5960
f = h5py.File('{}'.format(file_name), 'r')
60-
runinfo = json.loads(f.attrs['runinfo'])
61-
expt.runinfo = recursive_to_itemattribute(runinfo)
61+
expt.runinfo = json.loads(f.attrs['runinfo'], cls=PyscanJSONDecoder)
6262

63-
devices = json.loads(f.attrs['devices'])
64-
expt.devices = recursive_to_itemattribute(devices)
63+
expt.devices = json.loads(f.attrs['devices'], cls=PyscanJSONDecoder)
6564

6665
for key, value in f.items():
6766
expt[key] = (f[key][:]).astype('float64')

0 commit comments

Comments
 (0)