moreniius 0.1.10__py3-none-any.whl → 0.2.1__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.
moreniius/additions.py CHANGED
@@ -8,15 +8,71 @@ log.debug('Extending moreniius.mccode.NXInstance translators')
8
8
  COMPONENT_TYPE_NAME_TO_NEXUS['ESS_butterfly'] = 'NXmoderator'
9
9
 
10
10
 
11
+ # BIFROST has 45 triplet detector modules. Each has its own position and orientation, and defines the positions
12
+ # and orientations of its cylindrical pixels in its own coordinate system.
13
+ # This module-level dictionary is used to dynamically populate their information, to be used when constructing the
14
+ # full detector during the readout-construction.
15
+ BIFROST_DETECTOR_MODULES = {}
16
+
17
+ BIFROST_DETECTOR_TOPIC = 'bifrost_detector'
18
+
19
+
11
20
  def readout_translator(instance):
12
21
  """BIFROST specific Readout Master, should be deprecated in favour of ReadoutCAEN"""
13
- from nexusformat.nexus import NXgroup
22
+ from numpy import einsum, ndarray, array
23
+ from nexusformat.nexus import NXdetector, NXcylindrical_geometry
14
24
  from .utils import ev44_event_data_group
15
- stream = ev44_event_data_group(source='caen', topic='SimulatedEvents')
16
-
17
- # Somehow define the NXdetector_module ... :/
18
-
19
- return NXgroup(data=stream)
25
+ stream = ev44_event_data_group(source='caen', topic=BIFROST_DETECTOR_TOPIC)
26
+
27
+ readout_pos, readout_rot = instance.obj.orientation.position_parts(), instance.obj.orientation.rotation_parts()
28
+
29
+ counts = [geometry.detector_number.size for _, _, geometry in BIFROST_DETECTOR_MODULES.values()]
30
+ total = sum(counts)
31
+ points = [geometry.vertices.shape[0] for _, _, geometry in BIFROST_DETECTOR_MODULES.values()]
32
+ total_points = sum(points)
33
+ all_cylinders = ndarray((total, 3), dtype='int32')
34
+ all_vertices = ndarray((total_points, 3), dtype='float32')
35
+ detector_number = ndarray((total,), dtype='int32')
36
+
37
+ offset = 0
38
+
39
+ for module_pos, module_rot, geometry in BIFROST_DETECTOR_MODULES.values():
40
+ # Find the vector and rotation matrix linking the two coordinate systems
41
+ #
42
+ # - The geometry is defined in the module coordinate system, where any point is p' = (x', y', z').
43
+ # - There is a rotation matrix rot1 that relates a vector in _a_ global coordinate system to the module
44
+ # Such that
45
+ # x' = rot1 x_global
46
+ # Or
47
+ # x_global = rot1.inv() x'
48
+ # - Similarly, there is a rotation matrix rot2 that relates a vector in the same global coordinate system to
49
+ # the readout's coordinate system.
50
+ # x = rot2 x_global
51
+ # - In this global coordinate system, the module position and readout positions have a vector connecting them
52
+ # v = p_module - p_readout
53
+ #
54
+ # - So, to express the geometry in the readout's coordinate system, we convert p' to the global coordinate
55
+ # system, add the difference vector between the two coordinate systems, then convert the result to the
56
+ # readout coordinate system:
57
+ # p = rot2 (v + rot1.inv() p')
58
+ #
59
+ v = instance.instr.expr2nx(tuple((module_pos - readout_pos).position())) # this is a mccode-antlr Vector
60
+ rot1 = array(instance.instr.expr2nx(tuple(module_rot.inverse().rotation()))).reshape((3, 3))
61
+ rot2 = array(instance.instr.expr2nx(tuple(readout_rot.rotation()))).reshape((3, 3))
62
+ #
63
+ vertices = einsum('ij,kj->ki', rot1, geometry.vertices)
64
+ vertices = vertices + v
65
+ vertices = einsum('ij,kj->ki', rot2, vertices)
66
+ #
67
+ cylinders = offset + geometry.cylinders
68
+ all_vertices[offset:(offset + vertices.shape[0])] = vertices
69
+ all_cylinders[geometry.detector_number - 1, :] = cylinders
70
+ detector_number[geometry.detector_number - 1] = geometry.detector_number
71
+ offset += vertices.shape[0]
72
+
73
+ geometry = NXcylindrical_geometry(vertices=all_vertices, cylinders=all_cylinders, detector_number=detector_number)
74
+
75
+ return NXdetector(data=stream, geometry=geometry)
20
76
 
21
77
 
22
78
  def monochromator_rowland_translator(nxinstance):
@@ -122,22 +178,25 @@ def detector_tubes_offsets_and_one_cylinder(self):
122
178
 
123
179
  halfi = (width - 2 * radius) / 2
124
180
  di = np.linspace(-halfi, halfi, ni)
125
- dj = np.linspace(-height / 2, height / 2, nj)
181
+
182
+ half_pixel = height / (nj + 1) / 2 # half the size of one pixel along the tube
183
+ dj = -np.linspace(-height / 2 + half_pixel, height / 2 - half_pixel, nj)
126
184
  Dj, Di = np.meshgrid(dj, di)
127
185
 
128
186
  arc, triplet = analyzer - 1, cassette - 1 # naming from ICD 01 v6 indexing of triplets
129
187
 
130
188
  diameter = f'2 * {radius}' if isinstance(radius, str) else 2 * radius
131
189
 
132
- pars['detector_number'] = [[pixel_fun(nj, arc, triplet, tube, position) for position in range(nj)] for tube in
133
- range(ni)]
134
- pars['data'] = ev44_event_data_group(bifrost_source_20230704(arc, triplet), 'SimulatedEvents')
190
+ detector_number = [[pixel_fun(nj, arc, triplet, tube, position) for position in range(nj)] for tube in range(ni)]
191
+
192
+ pars['detector_number'] = np.array(detector_number).astype('int32')
193
+ pars['data'] = ev44_event_data_group(bifrost_source_20230704(arc, triplet), BIFROST_DETECTOR_TOPIC)
135
194
  pars['x_pixel_offset'] = NXfield(Di, units='m')
136
195
  pars['y_pixel_offset'] = NXfield(Dj, units='m')
137
196
  pars['x_pixel_size'] = NXfield(diameter, units='m')
138
197
  pars['y_pixel_size'] = NXfield(height / nj, units='m')
139
198
  pars['diameter'] = NXfield(diameter, units='m')
140
- pars['type'] = f'{ni} He3 tubes in series' if self.nx_parameter('wires_in_series') else f'{ni} He3 tubes'
199
+ pars['type'] = f'{ni} He3 tubes in series' if self.nx_parameter('wires_in_series', True) else f'{ni} He3 tubes'
141
200
 
142
201
  # use NXcylindrical_geometry to define the detectors, which requires:
143
202
  # vertices - (i, 3) -- points relative to the detector position defining each cylinder in the detector
@@ -151,13 +210,65 @@ def detector_tubes_offsets_and_one_cylinder(self):
151
210
  # detector_number: (k,) -- maps the cylinders in cylinder by index with a detector id
152
211
  #
153
212
  # We're allowed to specify a single cylinder then le the x/y/z_offset position that pixel repeatedly:
154
- dy = (dj[1] - dj[0]) / 2
155
- vertices = NXfield([[0, -dy, 0], [radius, -dy, 0], [0, dy, 0]], units='m')
213
+ vertices = NXfield([[0, -half_pixel, 0], [radius, -half_pixel, 0], [0, half_pixel, 0]], units='m')
156
214
  cylinders = [[0, 1, 2]]
157
215
  geometry = NXcylindrical_geometry(vertices=vertices, cylinders=cylinders)
158
216
 
159
217
  return NXdetector(**pars, geometry=geometry)
160
218
 
219
+ #
220
+ # def detector_tubes_only_cylinder(self):
221
+ # """This results in only the first cylinder being plotted by Nexus constructor"""
222
+ # import numpy as np
223
+ # from nexusformat.nexus import NXdetector, NXfield, NXcylindrical_geometry
224
+ # from .utils import ev44_event_data_group
225
+ # # parameters for NXdetector, to be filled-in
226
+ # pars = {}
227
+ # pixel_fun, wre = bifrost_pixel_regex_20230911()
228
+ #
229
+ # if wre.match(str(self.obj.when)):
230
+ # m = wre.match(str(self.obj.when))
231
+ # cassette = int(m.group('cassette'))
232
+ # analyzer = int(m.group('analyzer'))
233
+ # else:
234
+ # cassette = 1
235
+ # analyzer = 1
236
+ # # cassette in (1, 9), analyzer in (1, 5):
237
+ #
238
+ # # i is the 'slow' direction, j is the 'fast' direction
239
+ # # --> i between tubes, j along tubes
240
+ # ni = self.nx_parameter('N') # corresponds to 'width' and McStas 'x' axis
241
+ # nj = self.nx_parameter('no') # corresponds to 'height' and McStas 'y' axis
242
+ # width, height, radius = [self.nx_parameter(n) for n in ('width', 'height', 'radius')]
243
+ # halfi = (width - 2 * radius) / 2
244
+ # di = np.linspace(-halfi, halfi, ni)
245
+ # dj = np.linspace(-height / 2, height / 2, nj+1)
246
+ #
247
+ # # use NXcylindrical_geometry to define the detectors, which requires:
248
+ # # vertices - (i, 3) -- points relative to the detector position defining each cylinder in the detector
249
+ # # cylinders - (j, 3) -- indexes of the vertices, to define a cylinder by its face-center, face-edge, and
250
+ # # opposite face center:
251
+ # # |---------------|---------------|
252
+ # # | | |
253
+ # # 0 + + 2 + 4
254
+ # # | | |
255
+ # # |---------------|---------------|
256
+ # # 1 3 5
257
+ # # detector_number: (k,) -- maps the cylinders in cylinder by index with a detector id
258
+ #
259
+ # arc, triplet = analyzer - 1, cassette - 1 # naming from ICD 01 v6 indexing of triplets
260
+ #
261
+ # vertices = NXfield([v for x in di for y in dj for v in [[x, y, 0], [x, y, radius]]], units='m')
262
+ # cylinders = [[k, k+1, k+2] for k in [tube * 2 * (nj + 1) + 2 * j for tube in range(ni) for j in range(nj)]]
263
+ # detector_number = [pixel_fun(nj, arc, triplet, tube, j) for tube in range(ni) for j in range(nj)]
264
+ #
265
+ # pars['data'] = ev44_event_data_group(bifrost_source_20230704(arc, triplet), BIFROST_DETECTOR_TOPIC)
266
+ # pars['type'] = f'{ni} He3 tubes in series' if self.nx_parameter('wires_in_series') else f'{ni} He3 tubes'
267
+ #
268
+ # geometry = NXcylindrical_geometry(vertices=vertices, cylinders=cylinders, detector_number=detector_number)
269
+ #
270
+ # return NXdetector(**pars, geometry=geometry)
271
+
161
272
 
162
273
  def detector_tubes_only_cylinder(self):
163
274
  """This results in only the first cylinder being plotted by Nexus constructor"""
@@ -183,8 +294,9 @@ def detector_tubes_only_cylinder(self):
183
294
  nj = self.nx_parameter('no') # corresponds to 'height' and McStas 'y' axis
184
295
  width, height, radius = [self.nx_parameter(n) for n in ('width', 'height', 'radius')]
185
296
  halfi = (width - 2 * radius) / 2
297
+ # signs of di and dj verified by examining plots of detector positions
186
298
  di = np.linspace(-halfi, halfi, ni)
187
- dj = np.linspace(-height / 2, height / 2, nj+1)
299
+ dj = -np.linspace(-height / 2, height / 2, nj+1)
188
300
 
189
301
  # use NXcylindrical_geometry to define the detectors, which requires:
190
302
  # vertices - (i, 3) -- points relative to the detector position defining each cylinder in the detector
@@ -201,17 +313,36 @@ def detector_tubes_only_cylinder(self):
201
313
  arc, triplet = analyzer - 1, cassette - 1 # naming from ICD 01 v6 indexing of triplets
202
314
 
203
315
  vertices = NXfield([v for x in di for y in dj for v in [[x, y, 0], [x, y, radius]]], units='m')
204
- cylinders = [[k, k+1, k+2] for k in [tube * 2 * (nj + 1) + 2 * j for tube in range(ni) for j in range(nj)]]
205
- detector_number = [pixel_fun(nj, arc, triplet, tube, j) for tube in range(ni) for j in range(nj)]
316
+ cylinders = np.array([[k, k+1, k+2] for k in [tube * 2 * (nj + 1) + 2 * j for tube in range(ni) for j in range(nj)]]).astype('int32')
317
+ detector_number = np.array([pixel_fun(nj, arc, triplet, tube, j) for tube in range(ni) for j in range(nj)]).astype('int32')
206
318
 
207
- pars['data'] = ev44_event_data_group(bifrost_source_20230704(arc, triplet), 'SimulatedEvents')
208
- pars['type'] = f'{ni} He3 tubes in series' if self.nx_parameter('wires_in_series') else f'{ni} He3 tubes'
319
+ pars['data'] = ev44_event_data_group(bifrost_source_20230704(arc, triplet), BIFROST_DETECTOR_TOPIC)
320
+ pars['type'] = f'{ni} He3 tubes in series' if self.nx_parameter('wires_in_series', True) else f'{ni} He3 tubes'
209
321
 
210
322
  geometry = NXcylindrical_geometry(vertices=vertices, cylinders=cylinders, detector_number=detector_number)
211
323
 
324
+ for md in self.obj.metadata:
325
+ if md.mimetype == 'application/json' and md.name == 'nexus_structure_stream_data':
326
+ from nexusformat.nexus import NXdata
327
+ from moreniius.utils import NotNXdict
328
+ from json import loads
329
+ stream = loads(md.value)
330
+ name = stream.get('module', 'metadata')
331
+ pars[name] = NXdata(data=NotNXdict(stream))
332
+
212
333
  return NXdetector(**pars, geometry=geometry)
213
334
 
214
335
 
336
+ def bifrost_detector_collector(self):
337
+ # nx_obj = detector_tubes_only_cylinder(self) # each pixel is a cylinder
338
+ nx_obj = detector_tubes_offsets_and_one_cylinder(self) # all pixels share one cylinder
339
+ # stash the object parts
340
+ pos, rot = self.obj.orientation.position_parts(), self.obj.orientation.rotation_parts()
341
+ BIFROST_DETECTOR_MODULES[str(self.obj.when)] = pos, rot, nx_obj.geometry
342
+ # and return it
343
+ return nx_obj
344
+
345
+
215
346
  # def histogram_monitor(obj):
216
347
  # from nexusformat.nexus import NXmonitor
217
348
  # from .nxoff import NXoff
@@ -222,16 +353,17 @@ def detector_tubes_only_cylinder(self):
222
353
  #
223
354
  # # parameters to be filled-in
224
355
  # pars = {}
225
- # # pars['data'] = ev44_event_data_group(bifrost_source_20230704(arc, triplet), 'SimulatedEvents')
356
+ # # pars['data'] = ev44_event_data_group(bifrost_source_20230704(arc, triplet), BIFROST_DETECTOR_TOPIC)
226
357
  # # pars['type'] = f'{ni} He3 tubes in series' if self.nx_parameter('wires_in_series') else f'{ni} He3 tubes'
227
358
  # pars['geometry'] = geometry.to_nexus()
228
359
  # return NXmonitor(**pars)
229
360
 
230
361
 
231
362
  # Patch-in the new methods
232
- register_translator('Readout', readout_translator)
363
+ # register_translator('Readout', readout_translator)
233
364
  register_translator('Monochromator_Rowland', monochromator_rowland_translator)
234
- register_translator('Detector_tubes', detector_tubes_only_cylinder)
365
+ register_translator('Detector_tubes', bifrost_detector_collector)
366
+ register_translator('Detector_time_tubes', bifrost_detector_collector)
235
367
  register_translator('Frame_monitor', monitor_translator)
236
368
 
237
369
  log.debug('moreniius.mccode.NXInstance translators extended')
moreniius/mccode/comp.py CHANGED
@@ -33,6 +33,7 @@ of the NXInstance class; then it can be used 'transparently' without needing to
33
33
  """
34
34
  from zenlog import log
35
35
  from mccode_antlr.common import Expr
36
+ from moreniius.utils import resolve_parameter_links
36
37
 
37
38
 
38
39
  def slit_translator(nxinstance):
@@ -55,7 +56,8 @@ def slit_translator(nxinstance):
55
56
  log.warn(f'{nxinstance.obj.name} has a non-constant x or y zero, which requires special handling for NeXus')
56
57
  elif abs(x_zero) or abs(y_zero):
57
58
  log.warn(f'{nxinstance.obj.name} should be translated by [{x_zero}, {y_zero}, 0] via eniius_data METADATA')
58
- return nxinstance.make_nx(NXslit, x_gap=x_gap, y_gap=y_gap)
59
+ params = resolve_parameter_links(dict(x_gap=x_gap, y_gap=y_gap))
60
+ return nxinstance.make_nx(NXslit, **params)
59
61
 
60
62
 
61
63
  def guide_translator(nxinstance):
@@ -64,18 +66,18 @@ def guide_translator(nxinstance):
64
66
  off_pars = {k: nxinstance.nx_parameter(k) for k in ('l', 'w1', 'h1', 'w2', 'h2')}
65
67
  for k in ('w', 'h'):
66
68
  off_pars[f'{k}2'] = off_pars[f'{k}1'] if off_pars[f'{k}2'] == 0 else off_pars[f'{k}2']
67
- m_value = nxinstance.parameter('m')
69
+ guide_pars = {'m_value': nxinstance.parameter('m')}
68
70
  geometry = NXoff.from_wedge(**off_pars).to_nexus()
69
- return nxinstance.make_nx(NXguide, m_value=m_value, geometry=geometry)
71
+ return nxinstance.make_nx(NXguide, OFF_GEOMETRY=geometry, **resolve_parameter_links(guide_pars))
70
72
 
71
73
 
72
74
  def collimator_linear_translator(nxinstance):
73
75
  from nexusformat.nexus import NXcollimator
74
76
  from moreniius.nxoff import NXoff
75
77
  pars = {k: nxinstance.nx_parameter(v) for k, v in (('l', 'length'), ('w1', 'xwidth'), ('h1', 'yheight'))}
76
- return nxinstance.make_nx(NXcollimator, divergence_x=nxinstance.parameter('divergence'),
77
- divergence_y=nxinstance.parameter('divergenceV'),
78
- geometry=NXoff.from_wedge(**pars).to_nexus())
78
+ col_pars = dict(divergence_x=nxinstance.parameter('divergence'), divergence_y=nxinstance.parameter('divergenceV'))
79
+ return nxinstance.make_nx(NXcollimator, OFF_GEOMETRY=NXoff.from_wedge(**pars).to_nexus(),
80
+ **resolve_parameter_links(col_pars))
79
81
 
80
82
 
81
83
  def diskchopper_translator(nxinstance):
@@ -90,7 +92,7 @@ def diskchopper_translator(nxinstance):
90
92
  nslit, delta = mpars['nslit'], mpars['theta_0'] / 2.0
91
93
  slit_edges = [y * 360.0 / nslit + x for y in range(int(nslit)) for x in (-delta, delta)]
92
94
  nx_slit_edges = [nxinstance.expr2nx(se) for se in slit_edges]
93
- return nxinstance.make_nx(NXdisk_chopper, slit_edges=NXfield(nx_slit_edges, units='degrees'), **pars)
95
+ return nxinstance.make_nx(NXdisk_chopper, slit_edges=NXfield(nx_slit_edges, units='degrees'), **resolve_parameter_links(pars))
94
96
 
95
97
 
96
98
  def elliptic_guide_gravity_translator(nxinstance):
@@ -116,6 +118,8 @@ def elliptic_guide_gravity_translator(nxinstance):
116
118
  z = x * p['l']
117
119
  vertices.extend([[-w, -h, z], [-w, h, z], [w, h, z], [w, -h, z]])
118
120
 
121
+ # These are only the guide faces (that is, the inner faces of the sides of the guide housing)
122
+ # The entry and exit are not guide faces and therefore are NOT represented here!
119
123
  for i in range(n):
120
124
  j0, j1, j2, j3, j4, j5, j6, j7 = [4 * i + k for k in range(8)]
121
125
  faces.extend([[j0, j1, j5, j4], [j1, j2, j6, j5], [j2, j3, j7, j6], [j3, j0, j4, j7]])
@@ -123,22 +127,22 @@ def elliptic_guide_gravity_translator(nxinstance):
123
127
  nx_vertices = [[nxinstance.expr2nx(expr) for expr in vector] for vector in vertices]
124
128
  nx_faces = [[nxinstance.expr2nx(expr) for expr in face] for face in faces]
125
129
 
126
- return NXguide(geometry=NXoff(nx_vertices, nx_faces).to_nexus())
130
+ return NXguide(OFF_GEOMETRY=NXoff(nx_vertices, nx_faces).to_nexus())
127
131
 
128
132
 
129
133
  def monitor_translator(nxinstance):
130
- from nexusformat.nexus import NXmonitor
134
+ from nexusformat.nexus import NXmonitor, NXdata
131
135
  from moreniius.nxoff import NXoff
132
136
  from moreniius.utils import NotNXdict
133
137
  from json import loads
134
138
  width = nxinstance.nx_parameter('xwidth')
135
139
  height = nxinstance.nx_parameter('yheight')
136
140
  geometry = NXoff.from_wedge(l=0.005, w1=width, h1=height)
137
- nx_monitor = NXmonitor(geometry=geometry.to_nexus())
141
+ nx_monitor = NXmonitor(OFF_GEOMETRY=geometry.to_nexus())
138
142
  if len(nxinstance.obj.metadata):
139
143
  # look for mimetype 'application/json' and check if it is NeXus Structure data stream:
140
144
  for md in nxinstance.obj.metadata:
141
145
  if md.mimetype == 'application/json' and md.name == 'nexus_structure_stream_data':
142
- nx_monitor['data'] = NotNXdict(loads(md.value))
146
+ nx_monitor['data'] = NXdata(data=NotNXdict(loads(md.value)))
143
147
 
144
148
  return nx_monitor
@@ -69,8 +69,9 @@ class NXInstance:
69
69
  transforms: dict[str, NXfield]
70
70
  only_nx: bool
71
71
  nx: Union[None, dict, NXfield] = None
72
+ dump_mcstas: bool = False
72
73
 
73
- def parameter(self, name):
74
+ def parameter(self, name, default=None):
74
75
  """
75
76
  Pull out a named instance parameter -- if it's value is not a constant, attempt to evaluate it
76
77
  using the Instr declare and initialize sections
@@ -78,7 +79,7 @@ class NXInstance:
78
79
  par = self.obj.get_parameter(name)
79
80
  if par is None:
80
81
  log.warn(f'It appears that {self.obj.type.name} does not define the parameter {name}')
81
- return None
82
+ return default
82
83
 
83
84
  expr = par.value
84
85
  # log.info(f'get parameter {name} which is {par} and expr {repr(expr)}')
@@ -99,9 +100,9 @@ class NXInstance:
99
100
  def expr2nx(self, expr: Expr):
100
101
  return self.instr.expr2nx(expr)
101
102
 
102
- def nx_parameter(self, name):
103
+ def nx_parameter(self, name, default=None):
103
104
  """Retrieve the named instance parameter and convert to a NeXus compatible value"""
104
- return self.expr2nx(self.parameter(name))
105
+ return self.expr2nx(self.parameter(name, default))
105
106
 
106
107
  def make_nx(self, nx_class, *args, **kwargs):
107
108
  return self.instr.make_nx(nx_class, *args, **kwargs)
@@ -111,7 +112,8 @@ class NXInstance:
111
112
  from nexusformat.nexus import NXtransformations
112
113
  from moreniius.utils import outer_transform_dependency, mccode_component_eniius_data
113
114
  self.nx = getattr(self, self.obj.type.name, self.default_translation)()
114
- self.nx['mcstas'] = dumps({'instance': str(self.obj), 'order': self.index})
115
+ if self.dump_mcstas:
116
+ self.nx['mcstas'] = dumps({'instance': str(self.obj), 'order': self.index})
115
117
  if self.transforms:
116
118
  self.nx['transformations'] = NXtransformations(**self.transforms)
117
119
  most_dependent = outer_transform_dependency(self.nx['transformations'])
moreniius/mccode/instr.py CHANGED
@@ -78,4 +78,12 @@ class NXInstr:
78
78
  def make_nx(self, nx_class, *args, **kwargs):
79
79
  nx_args = [self.expr2nx(expr) for expr in args]
80
80
  nx_kwargs = {name: self.expr2nx(expr) for name, expr in kwargs.items()}
81
+ # logged parameters are sometimes requested as NXfield objects, but should be links to the real NXlog
82
+ if nx_class == NXfield and len(nx_args) == 1 and isinstance(nx_args[0], NXcollection) and \
83
+ 'expression' in nx_args[0]:
84
+ not_expr = [x for x in nx_args[0] if x != 'expression']
85
+ if len(not_expr) == 1:
86
+ return nx_args[0][not_expr[0]]
87
+ else:
88
+ raise RuntimeError('Not sure what I should do here')
81
89
  return nx_class(*nx_args, **nx_kwargs)
@@ -57,7 +57,10 @@ class NXMcCode:
57
57
  from nexusformat.nexus import NXinstrument
58
58
  nx = NXinstrument() # this is a NeXus class
59
59
  nx['mcstas'] = self.nx_instr.to_nx()
60
- for name in self.indexes:
61
- nx[name] = self.component(name, only_nx=only_nx).nx
60
+ # hack the McCode component index into the name of the NeXus group
61
+ width = len(str(max(self.indexes.values())))
62
+ for name, index in self.indexes.items():
63
+ nx_name = f'{index:0{width}d}_{name}'
64
+ nx[nx_name] = self.component(name, only_nx=only_nx).nx
62
65
 
63
66
  return nx
moreniius/moreniius.py CHANGED
@@ -17,7 +17,7 @@ class MorEniius:
17
17
  ):
18
18
  from nexusformat.nexus import NXfield
19
19
  from .mccode import NXMcCode, NXInstr
20
- nxlog_root = nxlog_root or ''
20
+ nxlog_root = nxlog_root or '/entry/parameters'
21
21
  nx_mccode = NXMcCode(NXInstr(instr, nxlog_root=nxlog_root), origin_name=origin)
22
22
  nxs_obj = nx_mccode.instrument(only_nx=only_nx)
23
23
  nxs_obj['name'] = NXfield(value=instr.name)
@@ -34,7 +34,7 @@ def convert():
34
34
  import argparse
35
35
  parser = argparse.ArgumentParser(description='Convert an Instr (HDF5) or .instr (text) file to an equivalent NeXus Structure JSON string')
36
36
  parser.add_argument('filename', type=str, help='the file to convert')
37
- parser.add_argument('--format', type=str, default='json', help='the output format, either json or pickle')
37
+ parser.add_argument('--format', type=str, default='json', help='the output format, currently only json')
38
38
  args = parser.parse_args()
39
39
  instr = load_instr(args.filename)
40
40
  structure = to_nexus_structure(instr)
moreniius/utils.py CHANGED
@@ -161,6 +161,9 @@ def link_specifier(name: str, source: str) -> NotNXdict:
161
161
  source: str
162
162
  the target of the link, the location in the eventual NeXus file where the source of data resides
163
163
 
164
+ Reference:
165
+ https://gitlab.esss.lu.se/ecdc/ess-dmsc/kafka-to-nexus/-/blob/main/documentation/commands.md?ref_type=heads#links
166
+
164
167
  Examples:
165
168
  To produce the link in the following NeXus JSON structure, one would use this function
166
169
  {
@@ -189,3 +192,19 @@ def link_specifier(name: str, source: str) -> NotNXdict:
189
192
  >>> link_specifier('detector0_event_data', '/entry/instrument/detector_panel_0/event_data')
190
193
  """
191
194
  return ess_flatbuffer_specifier('link', {'name': name, 'source': source})
195
+
196
+
197
+ def resolve_parameter_links(instance_parameters: dict):
198
+ """Component instances have NeXus base class equivalents that require specifying any number of parameters.
199
+ Sometimes the McCode parameters are instrument-parameters, which may only be specified at runtime.
200
+ These component parameters are detectable and should already be replaced by NXfield objects holding NotNXdict
201
+ objects, in turn specifying the file-writer link module that points to their real NXlog dataset.
202
+ If the link is singular, it should have its name replaced by the NeXus class parameter so that NeXus programs
203
+ recognize it correctly.
204
+ """
205
+ from nexusformat.nexus import NXfield
206
+ for name, par in instance_parameters.items():
207
+ if isinstance(par, NXfield) and isinstance(par.nxdata, NotNXdict) and 'link' == par.nxdata.value['module']:
208
+ par.nxdata.value['config']['name'] = name
209
+
210
+ return instance_parameters
@@ -1,14 +1,14 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: moreniius
3
- Version: 0.1.10
3
+ Version: 0.2.1
4
4
  Author-email: Gregory Tucker <gregory.tucker@ess.eu>
5
5
  Classifier: License :: OSI Approved :: BSD License
6
6
  Classifier: Development Status :: 2 - Pre-Alpha
7
7
  Description-Content-Type: text/markdown
8
8
  Requires-Dist: zenlog ==1.1
9
9
  Requires-Dist: platformdirs ==3.11
10
- Requires-Dist: mccode-antlr[hdf5] ==0.5.3
11
- Requires-Dist: nexusformat ==1.0.2
10
+ Requires-Dist: mccode-antlr[hdf5] ==0.7.0
11
+ Requires-Dist: nexusformat ==1.0.6
12
12
  Requires-Dist: importlib-metadata ; python_version < "3.8"
13
13
 
14
14
  # moreniius
@@ -0,0 +1,18 @@
1
+ moreniius/__init__.py,sha256=33SUBkXWhH5rog5oaGJr1Kwqjhwz97w4E0Da7rArYi4,154
2
+ moreniius/additions.py,sha256=6Hhhc4LDUsnBj27Iil-EFFzQm1xd2M45hfcVQYEjxiI,17615
3
+ moreniius/moreniius.py,sha256=cU3CrfMC1kOnHO77yq5sZfDqRuA38G5kA3RUXFNGP2U,1455
4
+ moreniius/nexus_structure.py,sha256=i9CxYwJl4eKP9IIYR80MI3f54Yh0RPDFviZaulE5IOc,1709
5
+ moreniius/nxoff.py,sha256=WHp9wYNn_4Hcx8Nzi9rpX1p8_iwI-AdgTQouSAEG8N4,3288
6
+ moreniius/utils.py,sha256=3REIM3nJ3L0PVpIWqu0Kh-znK1ggNNYTCjXatoaAA34,8075
7
+ moreniius/writer.py,sha256=DOwzpDqoXiDXdtV-hRZVtF5lNBBoYy0UO5_bq89d1lc,6325
8
+ moreniius/mccode/__init__.py,sha256=1QiZdh90G3gp_WlVpdJB_ZGauoW0GJEQ13Nelaqa5JE,151
9
+ moreniius/mccode/comp.py,sha256=uR1L5nLfYPHhMKd3XnDbqf5xhkfwfPLRnttREc3jqBg,7382
10
+ moreniius/mccode/instance.py,sha256=4nqJ3ne6yXCEvsa3FIKUcGDYP_z7cAr46JhakDTB6qs,8055
11
+ moreniius/mccode/instr.py,sha256=3BdfYzZnHhCokFQIwp7XUEy9nt6CclqAWunHXfGW228,4363
12
+ moreniius/mccode/mccode.py,sha256=6NEXovuG-6itzlPgPklNOiZQ-MlldKF20p4TxV8n4BA,3228
13
+ moreniius/mccode/orientation.py,sha256=_jyTtabo3ZHujuXead0elZzcCck_v8UwX5tIMp4dXwI,3199
14
+ moreniius-0.2.1.dist-info/METADATA,sha256=IDriQtt4mIeEtG6mTYHH-i860X52N4IGuaO2Y04kGX0,625
15
+ moreniius-0.2.1.dist-info/WHEEL,sha256=UvcQYKBHoFqaQd6LKyqHw9fxEolWLQnlzP0h_LgJAfI,91
16
+ moreniius-0.2.1.dist-info/entry_points.txt,sha256=Ga3k4P4fyBt5_dJ03Oapic2Qlgqv9jufQGdxWiz_j2A,63
17
+ moreniius-0.2.1.dist-info/top_level.txt,sha256=RzMo23UfVhgQeuOYeS5P9I0qVbxx4Gbe6Roc29Mr02c,10
18
+ moreniius-0.2.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.42.0)
2
+ Generator: setuptools (74.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,18 +0,0 @@
1
- moreniius/__init__.py,sha256=33SUBkXWhH5rog5oaGJr1Kwqjhwz97w4E0Da7rArYi4,154
2
- moreniius/additions.py,sha256=maJXirPwtcETy5nE99dUVlngRZQdq7ynXOwb8MoOPvs,10507
3
- moreniius/moreniius.py,sha256=tT5JGdhnR19tZ8VPnjNLjcJAQDoapGdfDHvARvAvNTE,1438
4
- moreniius/nexus_structure.py,sha256=62QckSfg8m8PN0FMN5JNdOotunZzOmbj2rujIr0lUIo,1711
5
- moreniius/nxoff.py,sha256=WHp9wYNn_4Hcx8Nzi9rpX1p8_iwI-AdgTQouSAEG8N4,3288
6
- moreniius/utils.py,sha256=mI8SQ0JTqWKKC0RTmiD6z5O0R4t79riACVrMxjx9at0,7010
7
- moreniius/writer.py,sha256=DOwzpDqoXiDXdtV-hRZVtF5lNBBoYy0UO5_bq89d1lc,6325
8
- moreniius/mccode/__init__.py,sha256=1QiZdh90G3gp_WlVpdJB_ZGauoW0GJEQ13Nelaqa5JE,151
9
- moreniius/mccode/comp.py,sha256=7lev6_5P83YHQP9uat5x75m1XuoIRulxUXAg_RG5094,6955
10
- moreniius/mccode/instance.py,sha256=4jlVliEcGgG81SAJPffThXGKo-8wsbziq6jKbr0fjiQ,7952
11
- moreniius/mccode/instr.py,sha256=wixWLvOWPOq4pcwVL9olo1-tteJaPkUDFR9GRZLTtrM,3872
12
- moreniius/mccode/mccode.py,sha256=KQopQNiOi18bP_MQEYkAAd2AIhIFiVUx3gqVl0peWk8,3032
13
- moreniius/mccode/orientation.py,sha256=_jyTtabo3ZHujuXead0elZzcCck_v8UwX5tIMp4dXwI,3199
14
- moreniius-0.1.10.dist-info/METADATA,sha256=zjeEO883fEH9vALPsdC-9I6sJiRdEFJ6wFnW0tNnb1A,626
15
- moreniius-0.1.10.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
16
- moreniius-0.1.10.dist-info/entry_points.txt,sha256=Ga3k4P4fyBt5_dJ03Oapic2Qlgqv9jufQGdxWiz_j2A,63
17
- moreniius-0.1.10.dist-info/top_level.txt,sha256=RzMo23UfVhgQeuOYeS5P9I0qVbxx4Gbe6Roc29Mr02c,10
18
- moreniius-0.1.10.dist-info/RECORD,,