nbs-bl 0.2.0__py3-none-any.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.
Files changed (64) hide show
  1. nbs_bl/__init__.py +15 -0
  2. nbs_bl/beamline.py +450 -0
  3. nbs_bl/configuration.py +838 -0
  4. nbs_bl/detectors.py +89 -0
  5. nbs_bl/devices/__init__.py +12 -0
  6. nbs_bl/devices/detectors.py +154 -0
  7. nbs_bl/devices/motors.py +242 -0
  8. nbs_bl/devices/sampleholders.py +360 -0
  9. nbs_bl/devices/shutters.py +120 -0
  10. nbs_bl/devices/slits.py +51 -0
  11. nbs_bl/gGrEqns.py +171 -0
  12. nbs_bl/geometry/__init__.py +0 -0
  13. nbs_bl/geometry/affine.py +197 -0
  14. nbs_bl/geometry/bars.py +189 -0
  15. nbs_bl/geometry/frames.py +534 -0
  16. nbs_bl/geometry/linalg.py +138 -0
  17. nbs_bl/geometry/polygons.py +56 -0
  18. nbs_bl/help.py +126 -0
  19. nbs_bl/hw.py +270 -0
  20. nbs_bl/load.py +113 -0
  21. nbs_bl/motors.py +19 -0
  22. nbs_bl/planStatus.py +5 -0
  23. nbs_bl/plans/__init__.py +8 -0
  24. nbs_bl/plans/batches.py +174 -0
  25. nbs_bl/plans/conditions.py +77 -0
  26. nbs_bl/plans/flyscan_base.py +180 -0
  27. nbs_bl/plans/groups.py +55 -0
  28. nbs_bl/plans/maximizers.py +423 -0
  29. nbs_bl/plans/metaplans.py +179 -0
  30. nbs_bl/plans/plan_stubs.py +246 -0
  31. nbs_bl/plans/preprocessors.py +160 -0
  32. nbs_bl/plans/scan_base.py +58 -0
  33. nbs_bl/plans/scan_decorators.py +524 -0
  34. nbs_bl/plans/scans.py +145 -0
  35. nbs_bl/plans/suspenders.py +87 -0
  36. nbs_bl/plans/time_estimation.py +168 -0
  37. nbs_bl/plans/xas.py +123 -0
  38. nbs_bl/printing.py +221 -0
  39. nbs_bl/qt/models/beamline.py +11 -0
  40. nbs_bl/qt/models/energy.py +53 -0
  41. nbs_bl/qt/widgets/energy.py +225 -0
  42. nbs_bl/queueserver.py +249 -0
  43. nbs_bl/redisDevice.py +96 -0
  44. nbs_bl/run_engine.py +63 -0
  45. nbs_bl/samples.py +130 -0
  46. nbs_bl/settings.py +68 -0
  47. nbs_bl/shutters.py +39 -0
  48. nbs_bl/sim/__init__.py +2 -0
  49. nbs_bl/sim/config/polphase.nc +0 -0
  50. nbs_bl/sim/energy.py +403 -0
  51. nbs_bl/sim/manipulator.py +14 -0
  52. nbs_bl/sim/utils.py +36 -0
  53. nbs_bl/startup.py +27 -0
  54. nbs_bl/status.py +114 -0
  55. nbs_bl/tests/__init__.py +0 -0
  56. nbs_bl/tests/modify_regions.py +160 -0
  57. nbs_bl/tests/test_frames.py +99 -0
  58. nbs_bl/tests/test_panels.py +69 -0
  59. nbs_bl/utils.py +235 -0
  60. nbs_bl-0.2.0.dist-info/METADATA +71 -0
  61. nbs_bl-0.2.0.dist-info/RECORD +64 -0
  62. nbs_bl-0.2.0.dist-info/WHEEL +4 -0
  63. nbs_bl-0.2.0.dist-info/entry_points.txt +2 -0
  64. nbs_bl-0.2.0.dist-info/licenses/LICENSE +13 -0
nbs_bl/gGrEqns.py ADDED
@@ -0,0 +1,171 @@
1
+ import numpy as np
2
+ import scipy.optimize as opt
3
+ import bluesky.plan_stubs as bps
4
+ from .printing import run_report
5
+
6
+ run_report(__file__)
7
+
8
+ #!/usr/bin/env python3
9
+ # -*- coding: utf-8 -*-
10
+ """
11
+ basic general equations applying to gratings and grating instruments
12
+ assumes all angular arguments are passed as degrees
13
+ assumes length units are in mm
14
+ @author: dvorak
15
+ """
16
+
17
+
18
+ def getBetaDeg(en_eV, alpha_deg, k_invmm, m):
19
+ # calculate beta in deg
20
+ # en_eV: energy units of electron Volts
21
+ # alpha_deg: incident angle in degrees
22
+ # k_invmm: central line density in mm^-1
23
+ # m: diffraction order unitless integer
24
+ hc = 0.0012398 # in units of eV mm
25
+ lambda_mm = hc / en_eV # wavelength in mm
26
+ alpha = np.radians(alpha_deg) # alpha angle in radians
27
+ beta = np.arcsin((m * k_invmm * lambda_mm - np.sin(alpha))) # beta angle in radians
28
+ return np.degrees(beta)
29
+
30
+
31
+ def ruben2005eqn8m(en_eV, cff, k_invmm, m):
32
+ # eqn 8, doi:10.1016/j.nima.2004.09.007, to calculate alpha from cff
33
+ # generalized to include higher orders
34
+ # en_eV is the energy in electron volts
35
+ # cff is unitless constant of fixed focus
36
+ # k_invmm is central line density in mm-1
37
+ # m is the diffraction order, integer, unitless
38
+ # returns alpha in degrees
39
+ # works for grazing incidence, 2000 eV, 1800 mm-1, cff=2, m=+1 thru +5
40
+ # works for grazing incidence, 2000 eV, 1800 mm-1, cff=0.2, m=-1 thru -5
41
+ aa = 1 / (cff ** 2 - 1) ## unitless
42
+ hc = 0.0012398 # in units of eV mm
43
+ lambda_mm = hc / en_eV ## in mm
44
+ return np.degrees(np.arcsin(-m * k_invmm * lambda_mm * aa + np.sqrt(1 + (cff * m * k_invmm * lambda_mm * aa) ** 2)))
45
+
46
+
47
+ def get_mirror_grating_angles(en_eV, cff, k_invmm, m):
48
+ a = ruben2005eqn8m(en_eV, cff, k_invmm, m) # twice the angle between mirror and grating in equation land
49
+ b = getBetaDeg(en_eV, a, k_invmm, m) # angle of pre-mirror (in degrees) in equation land
50
+ mirror_angle = -(180 - a + b) / 2 # tangle of the mirror pitch for the actual motor (degrees)
51
+ grating_angle = -90 - b # angle of the grating pitch for the actual motor (degrees)
52
+ return [mirror_angle, grating_angle]
53
+
54
+
55
+ def energy(mirror,grating, k_invmm, m):
56
+ hc = 0.0012398 # in units of eV mm
57
+ b = np.radians(-90 - grating)
58
+ a = np.radians(2 * mirror + 90 - grating)
59
+ return hc * k_invmm * m / (np.sin(a) + np.sin(b))
60
+
61
+
62
+ def find_best_offsets(mirror_pitches, grating_pitches, mguesses, eVs, k_invmm):
63
+ '''
64
+ given a bunch of measurements of mirror and grating pitches, fits the most likely constant offsets
65
+ for the mirror and grating pitch. Each measurement can be at a different order(m) and a different energy
66
+ the grating parameters should not change, so only the constant line density is needed
67
+
68
+ mirror_pitches : list or array of length n
69
+ experimental mirror pitch angles
70
+
71
+ grating_pitches : list or array of length n
72
+ experimental grating pitch angles
73
+
74
+ m_guesses : list or array of length n
75
+ the diffraction order which was scanned
76
+
77
+ eVs : list or array of length n
78
+ the energy of each measurement (allows for different calibrants)
79
+
80
+ k_invmm : single number the central spacing of the grating in mm^-1
81
+
82
+ output: the result of numpy.optimize = has a lot of components which might be useful, the most obvious of which are
83
+ output.x the optimized offsets of the mirror, and grating pitch
84
+ output.success wether the optimize function thinks that it succeeded or not. experince shows that good fits are still possible when this is false
85
+ '''
86
+ fit_result = opt.minimize(diffraction_pitch_offset_error_func,
87
+ x0=[0.0001, -0.0001],
88
+ args=(
89
+ mirror_pitches,
90
+ grating_pitches,
91
+ mguesses,
92
+ eVs,
93
+ k_invmm
94
+ )
95
+ )
96
+ return fit_result
97
+
98
+
99
+
100
+ def diffraction_pitch_offset_error_func(
101
+ fit_elements,
102
+ mirror_pitches,
103
+ grating_pitches,
104
+ m_guesses,
105
+ en_eVs,
106
+ k_invmm):
107
+ '''
108
+ fit_elements, an 2 element array:
109
+ mirror angle offset,
110
+ grating angle offset
111
+
112
+ mirror_pitches : list or array of length n
113
+ experimental mirror pitch angles
114
+
115
+ grating_pitches : list or array of length n
116
+ experimental grating pitch angles
117
+
118
+ m_guesses : list or array of length n
119
+ the diffraction order which was scanned
120
+
121
+ en_eV : list or array of length n
122
+ the energy of each measurement (allows for different calibrants)
123
+
124
+ k_invmm : single number the central spacing of the grating in mm^-1
125
+ '''
126
+ error = 0
127
+ for mp, gp, m, en_eV in zip(mirror_pitches, grating_pitches, m_guesses, en_eVs):
128
+ mir = mp - fit_elements[0] # the test mirror position
129
+ grat = gp - fit_elements[1] # the test grating position
130
+ en_theoretical = energy(mir,grat, k_invmm, m) # what energy these test positions would produce
131
+ error += (en_theoretical - en_eV) ** 2
132
+ return error
133
+ # for the RSoXS grating:
134
+ # RE(tune_pgm(cs=[1.35,1.4,1.45,1.5,1.55],ms=[1,1,1,1,1],energy=291.65,pol=90,k=250))
135
+
136
+
137
+ def set_pgm_offsets(error_object, energy_object):
138
+ yield from bps.mvr(energy_object.monoen.mirror2.user_offset,-error_object.x[0]) # right now we have to set the negative of the fit value as the delta in the offset
139
+ yield from bps.mvr(energy_object.monoen.grating.user_offset,-error_object.x[1])
140
+
141
+
142
+ def test_grating_fit(numpoints, minc, maxc, minm, maxm, energy, k, noise, moffset, goffset):
143
+ # create some fake measurements of grating and mirror positions to test the fit function
144
+
145
+ cs = np.linspace(minc, maxc, numpoints) # used to generate test pairs of pitches
146
+ ms = np.round(np.linspace(minm, maxm, numpoints))
147
+ evs = np.ones(numpoints) * energy
148
+ merr = np.random.normal(0, noise, numpoints)
149
+ gerr = np.random.normal(0, noise, numpoints)
150
+
151
+ mirrors, gratings = list(zip(*[get_mirror_grating_angles(energy, c, k, m) for c, m in zip(cs, ms)]))
152
+ mirrors += merr + moffset
153
+ gratings += gerr + goffset
154
+ return find_best_offsets(mirrors, gratings, ms, evs, k)
155
+
156
+
157
+
158
+
159
+ '''
160
+ mirror positions: [-3.6558274763042604, -3.3921585294849734, -3.1876054822737245, -3.0234213066329048, -2.8882065368134207, -2.774584471608641, -2.6775464182938222, -2.5935586706589646]
161
+ grating positions: [-4.107032615400001, -3.880574386500001, -3.709388039800004, -3.5753463783000043, -3.4671052284000012, -3.379157631200002, -3.3047008284000015, -3.242455403500003]
162
+ energy positions: [291.65, 291.65, 291.65, 291.65, 291.65, 291.65, 291.65, 291.65]
163
+ orders: [1, 1, 1, 1, 1, 1, 1, 1]
164
+
165
+
166
+ mirror positions: [-3.234388850111962, -3.0810948898872326, -2.9527235798924423, -4.572999932948626, -4.3563799893796045, -4.1749665476012225]
167
+ grating positions: [-3.7760457088000052, -3.6498169308000072, -3.546186037900007, -5.3394722246000015, -5.160893796100005, -5.014527626700001]
168
+ energy positions: [291.65, 291.65, 291.65, 291.65, 291.65, 291.65]
169
+ orders: [1, 1, 1, 2, 2, 2]
170
+
171
+ '''
File without changes
@@ -0,0 +1,197 @@
1
+ import numpy as np
2
+
3
+
4
+ class NullFrame:
5
+ def __init__(self, dim):
6
+ self.dim = dim
7
+ self.origin = np.zeros(dim)
8
+
9
+ def get_global(self):
10
+ return self
11
+
12
+ def to_global(self, coords):
13
+ return coords
14
+
15
+ def from_global(self, coords):
16
+ return coords
17
+
18
+ def make_child_frame(self, *axes, origin=None):
19
+ return Frame(*axes, origin=origin, parent=self)
20
+
21
+
22
+ class Frame:
23
+ def __init__(self, *axes, origin=None, parent=None):
24
+ if not axes and origin is None:
25
+ raise ValueError("Either axes or origin must be provided")
26
+
27
+ if origin is not None:
28
+ self.origin = np.array(origin)
29
+ self.dim = len(origin)
30
+
31
+ if not axes:
32
+ axes = [
33
+ tuple([1 if i == j else 0 for i in range(self.dim)])
34
+ for j in range(self.dim)
35
+ ]
36
+
37
+ self.axes = tuple(self._ensure_axis(ax) for ax in axes)
38
+ else:
39
+ self.axes = tuple(self._ensure_axis(ax) for ax in axes)
40
+ self.dim = self.axes[0].dim
41
+ self.origin = np.zeros(self.dim)
42
+
43
+ if parent is not None:
44
+ self.parent = parent
45
+ if self.parent.dim != self.dim:
46
+ raise ValueError("Frame must have same dimension as parent")
47
+ else:
48
+ self.parent = NullFrame(self.dim)
49
+
50
+ if len(self.origin) != self.dim:
51
+ raise ValueError("Origin must have same dimension as axes")
52
+
53
+ if any(axis.dim != self.dim for axis in self.axes):
54
+ raise ValueError("All axes must have the same dimension")
55
+
56
+ if len(self.axes) != self.dim:
57
+ raise ValueError("Number of axes must equal the dimension of each axis")
58
+
59
+ self.A = np.column_stack(
60
+ [axis.coords for axis in self.axes] + [np.append(self.origin, 1)]
61
+ )
62
+ self.Ainv = np.linalg.inv(self.A)
63
+
64
+ @staticmethod
65
+ def _ensure_axis(ax):
66
+ """
67
+ Convert coordinate tuple to Axis object if necessary.
68
+
69
+ Parameters
70
+ ----------
71
+ ax : Axis or tuple
72
+ The axis or coordinates to convert.
73
+
74
+ Returns
75
+ -------
76
+ Axis
77
+ An Axis object.
78
+ """
79
+ if isinstance(ax, Axis):
80
+ return ax
81
+ elif isinstance(ax, (tuple, list, np.ndarray)):
82
+ return Axis.from_coords(*ax)
83
+ else:
84
+ raise ValueError(
85
+ f"Invalid axis type: {type(ax)}. Expected Axis or coordinate tuple."
86
+ )
87
+
88
+ def get_global(self):
89
+ return self.parent.get_global()
90
+
91
+ def to_parent(self, coords):
92
+ if len(coords) != self.dim:
93
+ raise ValueError("Coordinates must have same Dimension as Frame")
94
+ return np.dot(self.A, np.append(coords, 1))[:-1]
95
+
96
+ def to_global(self, coords):
97
+ if len(coords) != self.dim:
98
+ raise ValueError("Coordinates must have same Dimension as Frame")
99
+ return self.parent.to_global(self.to_parent(coords))
100
+
101
+ def to_frame(self, coords, frame):
102
+ if self.get_global() != frame.get_global():
103
+ raise ValueError("Frames must have same global frame")
104
+ global_coords = self.to_global(coords)
105
+ return frame.from_global(global_coords)
106
+
107
+ def from_parent(self, coords):
108
+ """
109
+ coords in parent frame
110
+ """
111
+ return np.dot(self.Ainv, np.append(coords, 1))[:-1]
112
+
113
+ def from_global(self, coords):
114
+ """
115
+ coords in parent frame
116
+ """
117
+ parent_coords = self.parent.from_global(coords)
118
+ return self.from_parent(parent_coords)
119
+
120
+ def make_child_frame(self, *axes, origin=None):
121
+ return Frame(*axes, origin=origin, parent=self)
122
+
123
+ def rotate_in_plane(self, coords, phi, ax1=0, ax2=1):
124
+ """
125
+ Rotates around z-axis by default
126
+ """
127
+ M = np.zeros((self.dim, self.dim))
128
+ for i in range(self.dim):
129
+ if i != ax1 and i != ax2:
130
+ M[i, i] = 1
131
+ M[ax1, ax1] = np.cos(phi)
132
+ M[ax1, ax2] = -np.sin(phi)
133
+ M[ax2, ax1] = np.sin(phi)
134
+ M[ax2, ax2] = np.cos(phi)
135
+ return np.dot(M, np.array(coords))
136
+
137
+
138
+ class Axis:
139
+ def __init__(self, *coords):
140
+ self.coords = np.array(coords + (0,))
141
+ self.dim = len(coords)
142
+
143
+ @classmethod
144
+ def from_coords(cls, *coords):
145
+ """
146
+ Create an Axis object from a list of coordinates, normalizing them.
147
+
148
+ Parameters
149
+ ----------
150
+ coords : list or array-like
151
+ The coordinates to create the Axis from.
152
+
153
+ Returns
154
+ -------
155
+ Axis
156
+ A new Axis object with normalized coordinates.
157
+
158
+ """
159
+ coords = np.array(coords)
160
+ norm = np.linalg.norm(coords)
161
+ if norm == 0:
162
+ raise ValueError("Cannot create an Axis with zero-length vector")
163
+ normalized_coords = coords / norm
164
+ return cls(*normalized_coords)
165
+
166
+
167
+ def angle_between_vectors_2d(v1, v2):
168
+ """
169
+ Calculate the angle between two 2D vectors in the x-y plane.
170
+
171
+ Parameters:
172
+ v1, v2 : array-like
173
+ The two vectors to compare. Each should be a 2D vector [x, y].
174
+
175
+ Returns:
176
+ float
177
+ The angle between the vectors in radians.
178
+ """
179
+ # Ensure the vectors are numpy arrays
180
+ v1 = np.array(v1)
181
+ v2 = np.array(v2)
182
+
183
+ # Calculate the angle using atan2
184
+ angle = np.arctan2(v2[1], v2[0]) - np.arctan2(v1[1], v1[0])
185
+
186
+ # Normalize the angle to be between -pi and pi
187
+ return angle % (2 * np.pi)
188
+
189
+
190
+ def find_rotation(child_frame, child_ax, parent_frame, parent_ax, around_ax=2):
191
+ origin = child_frame.to_frame([0, 0, 0], parent_frame)
192
+ vector = child_frame.to_frame(child_ax, parent_frame) - origin
193
+
194
+ angle = angle_between_vectors_2d(
195
+ np.delete(vector, around_ax), np.delete(parent_ax, around_ax)
196
+ )
197
+ return angle
@@ -0,0 +1,189 @@
1
+ from abc import ABC, abstractmethod
2
+ import csv
3
+
4
+
5
+ class GeometryBase(ABC):
6
+ @abstractmethod
7
+ def make_sample_frame(self, position):
8
+ """
9
+ Create a sample frame for the given position.
10
+
11
+ Parameters
12
+ ----------
13
+ position : Any
14
+ The position for which to create a sample frame.
15
+
16
+ Returns
17
+ -------
18
+ Frame
19
+ The created sample frame.
20
+ """
21
+ pass
22
+
23
+ @abstractmethod
24
+ def generate_geometry(self):
25
+ """
26
+ Generate the geometry for the holder.
27
+ """
28
+ pass
29
+
30
+ @abstractmethod
31
+ def get_geometry(self):
32
+ """
33
+ Get the geometry of the holder.
34
+
35
+ Returns
36
+ -------
37
+ Any
38
+ The geometry of the holder.
39
+ """
40
+ pass
41
+
42
+ def attach_manipulator(self, manipframe):
43
+ self.manip_frame = manipframe
44
+ self.generate_geometry()
45
+
46
+ def read_sample_file(self, filename):
47
+ extension = filename.split(".")[-1]
48
+ if extension in ["csv"] and hasattr(self, "read_sample_csv"):
49
+ return self.read_sample_csv(filename)
50
+ else:
51
+ raise AttributeError(
52
+ f"File had extension {extension}, but this geometry has no read method"
53
+ )
54
+
55
+
56
+ class AbsoluteBar(GeometryBase):
57
+ """
58
+ This is a geometry that assumes that the position is absolute, and will never attempt
59
+ to do any coordinate transformations.
60
+ """
61
+
62
+ def __init__(self):
63
+ pass
64
+
65
+ def make_sample_frame(self, position):
66
+ """
67
+ Always assume that the position is absolute, and return the coordinates
68
+ """
69
+ return position
70
+
71
+ def generate_geometry(self):
72
+ pass
73
+
74
+ def get_geometry(self):
75
+ side_md = {}
76
+ side_frames = {}
77
+ return side_md, side_frames
78
+
79
+
80
+ class Standard4SidedBar(GeometryBase):
81
+ def __init__(self, width, length):
82
+ self.length = length
83
+ self.sides = 4
84
+ self.width = width
85
+
86
+ def make_sample_frame(self, position):
87
+ side = position.get("side")
88
+ # origin = position.get("origin", "bar")
89
+ # if origin in ["abs", "absolute"]:
90
+ # x, y, z, r = position.get("coordinates")
91
+ # origin = (x, y, z)
92
+ # parent_frame = self.manip_frame.parent
93
+ # sample_frame = parent_frame.make_child_frame(origin=origin)
94
+
95
+ # else:
96
+ x1, y1, x2, y2 = position.get("coordinates")
97
+ z = position.get("thickness", 0)
98
+ origin = (0.5 * (x1 + x2), 0.5 * (y1 + y2), z)
99
+ parent_frame = self.side_frames[int(side) - 1]
100
+ sample_frame = parent_frame.make_child_frame(origin=origin)
101
+ return sample_frame
102
+
103
+ def generate_geometry(self):
104
+ """Very brute force, could be refined to be more general"""
105
+ axes = [(1, 0, 0), (0, 0, 1), (0, -1, 0)]
106
+ origin = (0, 0, -1 * self.length)
107
+ self.bar_frame = self.manip_frame.make_child_frame(*axes, origin=origin)
108
+ side_axes = [
109
+ [(0, 0, 1), (0, 1, 0), (-1, 0, 0)],
110
+ [(-1, 0, 0), (0, 1, 0), (0, 0, -1)],
111
+ [(0, 0, -1), (0, 1, 0), (1, 0, 0)],
112
+ [(1, 0, 0), (0, 1, 0), (0, 0, 1)],
113
+ ]
114
+ hw = self.width / 2.0
115
+ side_origins = [(-hw, 0, -hw), (hw, 0, -hw), (hw, 0, hw), (-hw, 0, hw)]
116
+ self.side_frames = [
117
+ self.bar_frame.make_child_frame(*axes, origin=origin)
118
+ for axes, origin in zip(side_axes, side_origins)
119
+ ]
120
+
121
+ def get_geometry(self):
122
+ # Implement the method here
123
+ side_md = {}
124
+ side_frames = {}
125
+ for n in range(self.sides):
126
+ side_frame = self.side_frames[n]
127
+ side_str = f"side{n+1}" # Humans one-index sides...
128
+ side_dict = {
129
+ "name": side_str,
130
+ "id": side_str,
131
+ "description": side_str,
132
+ "position": [],
133
+ }
134
+ side_md[side_str] = side_dict
135
+ side_frames[side_str] = side_frame
136
+ return side_md, side_frames
137
+
138
+ def read_sample_csv(self, filename):
139
+ with open(filename, "r") as f:
140
+ sampleReader = csv.reader(f, skipinitialspace=True)
141
+ samplelist = [row for row in sampleReader]
142
+ rownames = [n for n in samplelist[0] if n != ""]
143
+ # rownames = ["sample_id", "sample_name", "side", "x1", "y1", "x2", "y2",
144
+ # "t", "tags"]
145
+ samples = {}
146
+ for sample in samplelist[1:]:
147
+ sample_id = sample[0]
148
+ sample_dict = {
149
+ key: sample[rownames.index(key)]
150
+ for key in rownames[1:]
151
+ if sample[rownames.index(key)] != ""
152
+ }
153
+ coordinates = (
154
+ float(sample_dict.pop("x1")),
155
+ float(sample_dict.pop("y1")),
156
+ float(sample_dict.pop("x2")),
157
+ float(sample_dict.pop("y2")),
158
+ )
159
+ thickness = sample_dict.pop("thickness", 0)
160
+ side = sample_dict.pop("side")
161
+ position = {
162
+ "side": side,
163
+ "coordinates": coordinates,
164
+ "thickness": thickness,
165
+ }
166
+ sample_dict["position"] = position
167
+ samples[sample_id] = sample_dict
168
+ if "sample_name" in sample_dict:
169
+ name = sample_dict.pop("sample_name")
170
+ sample_dict["name"] = name
171
+ return samples
172
+
173
+
174
+ class Bar1d(GeometryBase):
175
+ def __init__(self):
176
+ pass
177
+
178
+ def generate_geometry(self):
179
+ pass
180
+
181
+ def get_geometry(self):
182
+ side_md = {}
183
+ side_frames = {}
184
+ return side_md, side_frames
185
+
186
+ def make_sample_frame(self, position):
187
+ x = position.get("coordinates")
188
+ child_frame = self.manip_frame.make_child_frame(origin=x)
189
+ return child_frame