essreduce 25.11.0__tar.gz → 25.11.2__tar.gz
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.
- {essreduce-25.11.0 → essreduce-25.11.2}/PKG-INFO +1 -1
- {essreduce-25.11.0 → essreduce-25.11.2}/requirements/base.txt +1 -1
- {essreduce-25.11.0 → essreduce-25.11.2}/requirements/basetest.txt +1 -1
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/nexus/__init__.py +2 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/nexus/_nexus_loader.py +26 -1
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/nexus/workflow.py +59 -17
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/time_of_flight/__init__.py +2 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/time_of_flight/eto_to_tof.py +99 -8
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/time_of_flight/fakes.py +1 -1
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/time_of_flight/lut.py +1 -1
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/time_of_flight/types.py +12 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/essreduce.egg-info/PKG-INFO +1 -1
- {essreduce-25.11.0 → essreduce-25.11.2}/tests/conftest.py +2 -2
- {essreduce-25.11.0 → essreduce-25.11.2}/tests/nexus/workflow_test.py +76 -2
- {essreduce-25.11.0 → essreduce-25.11.2}/tests/time_of_flight/unwrap_test.py +66 -1
- {essreduce-25.11.0 → essreduce-25.11.2}/.copier-answers.ess.yml +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/.copier-answers.yml +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/.github/ISSUE_TEMPLATE/high-level-requirement.yml +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/.github/dependabot.yml +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/.github/workflows/ci.yml +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/.github/workflows/docs.yml +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/.github/workflows/nightly_at_main.yml +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/.github/workflows/nightly_at_main_lower_bound.yml +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/.github/workflows/nightly_at_release.yml +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/.github/workflows/python-version-ci +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/.github/workflows/release.yml +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/.github/workflows/test.yml +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/.github/workflows/unpinned.yml +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/.github/workflows/weekly_windows_macos.yml +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/.gitignore +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/.pre-commit-config.yaml +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/.python-version +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/CODE_OF_CONDUCT.md +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/CONTRIBUTING.md +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/LICENSE +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/MANIFEST.in +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/README.md +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/docs/_static/anaconda-icon.js +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/docs/_static/favicon.svg +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/docs/_static/logo-dark.svg +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/docs/_static/logo.svg +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/docs/_templates/class-template.rst +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/docs/_templates/doc_version.html +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/docs/_templates/module-template.rst +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/docs/about/index.md +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/docs/api-reference/index.md +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/docs/conf.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/docs/developer/coding-conventions.md +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/docs/developer/dependency-management.md +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/docs/developer/getting-started.md +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/docs/developer/gui.ipynb +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/docs/developer/index.md +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/docs/index.md +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/docs/user-guide/index.md +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/docs/user-guide/installation.md +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/docs/user-guide/reduction-workflow-guidelines.md +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/docs/user-guide/tof/dream.ipynb +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/docs/user-guide/tof/frame-unwrapping.ipynb +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/docs/user-guide/tof/index.md +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/docs/user-guide/tof/wfm.ipynb +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/docs/user-guide/widget.md +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/pyproject.toml +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/requirements/base.in +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/requirements/basetest.in +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/requirements/ci.in +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/requirements/ci.txt +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/requirements/dev.in +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/requirements/dev.txt +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/requirements/docs.in +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/requirements/docs.txt +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/requirements/make_base.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/requirements/mypy.in +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/requirements/mypy.txt +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/requirements/nightly.in +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/requirements/nightly.txt +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/requirements/static.in +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/requirements/static.txt +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/requirements/test.in +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/requirements/test.txt +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/requirements/wheels.in +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/requirements/wheels.txt +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/resources/logo.svg +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/setup.cfg +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/__init__.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/data/__init__.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/data/_registry.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/live/__init__.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/live/raw.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/live/roi.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/live/workflow.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/logging.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/nexus/json_generator.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/nexus/json_nexus.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/nexus/types.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/parameter.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/py.typed +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/scripts/grow_nexus.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/streaming.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/time_of_flight/interpolator_numba.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/time_of_flight/interpolator_scipy.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/time_of_flight/resample.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/time_of_flight/workflow.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/ui.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/uncertainty.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/widgets/__init__.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/widgets/_base.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/widgets/_binedges_widget.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/widgets/_bounds_widget.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/widgets/_config.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/widgets/_filename_widget.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/widgets/_linspace_widget.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/widgets/_optional_widget.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/widgets/_spinner.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/widgets/_string_widget.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/widgets/_switchable_widget.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/widgets/_vector_widget.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/ess/reduce/workflow.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/essreduce.egg-info/SOURCES.txt +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/essreduce.egg-info/dependency_links.txt +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/essreduce.egg-info/entry_points.txt +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/essreduce.egg-info/requires.txt +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/src/essreduce.egg-info/top_level.txt +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/tests/accumulators_test.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/tests/live/raw_test.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/tests/live/roi_test.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/tests/nexus/json_generator_test.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/tests/nexus/json_nexus_examples/array_dataset.json +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/tests/nexus/json_nexus_examples/dataset.json +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/tests/nexus/json_nexus_examples/detector.json +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/tests/nexus/json_nexus_examples/entry.json +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/tests/nexus/json_nexus_examples/event_data.json +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/tests/nexus/json_nexus_examples/instrument.json +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/tests/nexus/json_nexus_examples/log.json +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/tests/nexus/json_nexus_test.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/tests/nexus/nexus_loader_test.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/tests/package_test.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/tests/scripts/test_grow_nexus.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/tests/streaming_test.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/tests/time_of_flight/interpolator_test.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/tests/time_of_flight/lut_test.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/tests/time_of_flight/resample_tests.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/tests/time_of_flight/wfm_test.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/tests/time_of_flight/workflow_test.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/tests/uncertainty_test.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/tests/widget_test.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/tools/shrink_nexus.py +0 -0
- {essreduce-25.11.0 → essreduce-25.11.2}/tox.ini +0 -0
|
@@ -20,6 +20,7 @@ from ._nexus_loader import (
|
|
|
20
20
|
load_all_components,
|
|
21
21
|
load_component,
|
|
22
22
|
load_data,
|
|
23
|
+
load_from_path,
|
|
23
24
|
open_component_group,
|
|
24
25
|
open_nexus_file,
|
|
25
26
|
)
|
|
@@ -33,6 +34,7 @@ __all__ = [
|
|
|
33
34
|
'load_all_components',
|
|
34
35
|
'load_component',
|
|
35
36
|
'load_data',
|
|
37
|
+
'load_from_path',
|
|
36
38
|
'open_component_group',
|
|
37
39
|
'open_nexus_file',
|
|
38
40
|
'types',
|
|
@@ -8,7 +8,7 @@ from collections.abc import Generator, Mapping
|
|
|
8
8
|
from contextlib import AbstractContextManager, contextmanager, nullcontext
|
|
9
9
|
from dataclasses import dataclass
|
|
10
10
|
from math import prod
|
|
11
|
-
from typing import TypeVar, cast
|
|
11
|
+
from typing import Any, TypeVar, cast
|
|
12
12
|
|
|
13
13
|
import scipp as sc
|
|
14
14
|
import scippnexus as snx
|
|
@@ -42,6 +42,31 @@ class NoLockingIfNeededType:
|
|
|
42
42
|
NoLockingIfNeeded = NoLockingIfNeededType()
|
|
43
43
|
|
|
44
44
|
|
|
45
|
+
def load_from_path(
|
|
46
|
+
location: NeXusLocationSpec,
|
|
47
|
+
definitions: Mapping | NoNewDefinitionsType = NoNewDefinitions,
|
|
48
|
+
) -> Any:
|
|
49
|
+
"""Load a field or group from a NeXus file given its location.
|
|
50
|
+
|
|
51
|
+
Parameters
|
|
52
|
+
----------
|
|
53
|
+
location:
|
|
54
|
+
Location of the field within the NeXus file (filename, entry name, selection).
|
|
55
|
+
definitions:
|
|
56
|
+
Application definitions to use for the file.
|
|
57
|
+
|
|
58
|
+
Returns
|
|
59
|
+
-------
|
|
60
|
+
:
|
|
61
|
+
The loaded field (as a variable, data array, or raw python object) or group
|
|
62
|
+
(as a data group).
|
|
63
|
+
"""
|
|
64
|
+
with open_nexus_file(location.filename, definitions=definitions) as f:
|
|
65
|
+
entry = _unique_child_group(f, snx.NXentry, location.entry_name)
|
|
66
|
+
item = entry[location.component_name]
|
|
67
|
+
return item[location.selection]
|
|
68
|
+
|
|
69
|
+
|
|
45
70
|
def load_component(
|
|
46
71
|
location: NeXusLocationSpec,
|
|
47
72
|
*,
|
|
@@ -385,7 +385,11 @@ def get_calibrated_detector(
|
|
|
385
385
|
# If the NXdetector in the file is not 1-D, we want to match the order of dims.
|
|
386
386
|
# zip_pixel_offsets otherwise yields a vector with dimensions in the order given
|
|
387
387
|
# by the x/y/z offsets.
|
|
388
|
-
offsets = snx.zip_pixel_offsets(da.coords)
|
|
388
|
+
offsets = snx.zip_pixel_offsets(da.coords)
|
|
389
|
+
# Get the dims in the order of the detector data array, but filter out dims that
|
|
390
|
+
# don't exist in the offsets (e.g. the detector data may have a 'time' dimension).
|
|
391
|
+
dims = [dim for dim in da.dims if dim in offsets.dims]
|
|
392
|
+
offsets = offsets.transpose(dims).copy()
|
|
389
393
|
# We use the unit of the offsets as this is likely what the user expects.
|
|
390
394
|
if transform.value.unit is not None and transform.value.unit != '':
|
|
391
395
|
transform_value = transform.value.to(unit=offsets.unit)
|
|
@@ -399,7 +403,7 @@ def get_calibrated_detector(
|
|
|
399
403
|
|
|
400
404
|
def assemble_detector_data(
|
|
401
405
|
detector: EmptyDetector[RunType],
|
|
402
|
-
|
|
406
|
+
neutron_data: NeXusData[snx.NXdetector, RunType],
|
|
403
407
|
) -> RawDetector[RunType]:
|
|
404
408
|
"""
|
|
405
409
|
Assemble a detector data array with event data.
|
|
@@ -410,14 +414,15 @@ def assemble_detector_data(
|
|
|
410
414
|
----------
|
|
411
415
|
detector:
|
|
412
416
|
Calibrated detector data array.
|
|
413
|
-
|
|
414
|
-
|
|
417
|
+
neutron_data:
|
|
418
|
+
Neutron data array (events or histogram).
|
|
415
419
|
"""
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
420
|
+
if neutron_data.bins is not None:
|
|
421
|
+
neutron_data = nexus.group_event_data(
|
|
422
|
+
event_data=neutron_data, detector_number=detector.coords['detector_number']
|
|
423
|
+
)
|
|
419
424
|
return RawDetector[RunType](
|
|
420
|
-
_add_variances(
|
|
425
|
+
_add_variances(neutron_data)
|
|
421
426
|
.assign_coords(detector.coords)
|
|
422
427
|
.assign_masks(detector.masks)
|
|
423
428
|
)
|
|
@@ -504,6 +509,19 @@ def _drop(
|
|
|
504
509
|
}
|
|
505
510
|
|
|
506
511
|
|
|
512
|
+
class _EmptyField:
|
|
513
|
+
"""Empty field that can replace a missing detector_number in NXdetector."""
|
|
514
|
+
|
|
515
|
+
def __init__(self, sizes: dict[str, int]):
|
|
516
|
+
self.attrs = {}
|
|
517
|
+
self.sizes = sizes.copy()
|
|
518
|
+
self.dims = tuple(sizes.keys())
|
|
519
|
+
self.shape = tuple(sizes.values())
|
|
520
|
+
|
|
521
|
+
def __getitem__(self, key: Any) -> sc.Variable:
|
|
522
|
+
return sc.zeros(dims=self.dims, shape=self.shape, unit=None, dtype='int32')
|
|
523
|
+
|
|
524
|
+
|
|
507
525
|
class _StrippedDetector(snx.NXdetector):
|
|
508
526
|
"""Detector definition without large geometry or event data for ScippNexus.
|
|
509
527
|
|
|
@@ -513,8 +531,36 @@ class _StrippedDetector(snx.NXdetector):
|
|
|
513
531
|
def __init__(
|
|
514
532
|
self, attrs: dict[str, Any], children: dict[str, snx.Field | snx.Group]
|
|
515
533
|
):
|
|
516
|
-
|
|
517
|
-
|
|
534
|
+
if 'detector_number' in children:
|
|
535
|
+
data = children['detector_number']
|
|
536
|
+
else:
|
|
537
|
+
# We get the 'data' sizes before the NXdata is dropped
|
|
538
|
+
if 'data' not in children:
|
|
539
|
+
raise KeyError(
|
|
540
|
+
"StrippedDetector: Cannot determine shape of the detector. "
|
|
541
|
+
"No 'detector_number' was found, and the 'data' entry is missing."
|
|
542
|
+
)
|
|
543
|
+
if 'value' not in children['data']:
|
|
544
|
+
raise KeyError(
|
|
545
|
+
"StrippedDetector: Cannot determine shape of the detector. "
|
|
546
|
+
"The 'data' entry has no 'value'."
|
|
547
|
+
)
|
|
548
|
+
# We drop any time-related dimension from the data sizes, as they are not
|
|
549
|
+
# relevant for the detector geometry/shape.
|
|
550
|
+
data = _EmptyField(
|
|
551
|
+
sizes={
|
|
552
|
+
dim: size
|
|
553
|
+
for dim, size in children['data']['value'].sizes.items()
|
|
554
|
+
if dim not in ('time', 'frame_time')
|
|
555
|
+
}
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
children = _drop(
|
|
559
|
+
children, (snx.NXoff_geometry, snx.NXevent_data, snx.NXdata, snx.NXlog)
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
children['data'] = data
|
|
563
|
+
|
|
518
564
|
super().__init__(attrs=attrs, children=children)
|
|
519
565
|
|
|
520
566
|
|
|
@@ -528,7 +574,7 @@ class _DummyField:
|
|
|
528
574
|
self.shape = (0,)
|
|
529
575
|
|
|
530
576
|
def __getitem__(self, key: Any) -> sc.Variable:
|
|
531
|
-
return sc.
|
|
577
|
+
return sc.zeros(dims=self.dims, shape=self.shape, unit=None, dtype='int32')
|
|
532
578
|
|
|
533
579
|
|
|
534
580
|
class _StrippedMonitor(snx.NXmonitor):
|
|
@@ -645,16 +691,12 @@ def LoadMonitorWorkflow(
|
|
|
645
691
|
|
|
646
692
|
|
|
647
693
|
def LoadDetectorWorkflow(
|
|
648
|
-
*,
|
|
649
|
-
run_types: Iterable[sciline.typing.Key],
|
|
650
|
-
monitor_types: Iterable[sciline.typing.Key],
|
|
694
|
+
*, run_types: Iterable[sciline.typing.Key]
|
|
651
695
|
) -> sciline.Pipeline:
|
|
652
696
|
"""Generic workflow for loading detector data from a NeXus file."""
|
|
653
697
|
wf = sciline.Pipeline(
|
|
654
698
|
(*_common_providers, *_detector_providers),
|
|
655
|
-
constraints=_gather_constraints(
|
|
656
|
-
run_types=run_types, monitor_types=monitor_types
|
|
657
|
-
),
|
|
699
|
+
constraints=_gather_constraints(run_types=run_types, monitor_types=[]),
|
|
658
700
|
)
|
|
659
701
|
wf[DetectorBankSizes] = DetectorBankSizes({})
|
|
660
702
|
wf[PreopenNeXusFile] = PreopenNeXusFile(False)
|
|
@@ -28,6 +28,7 @@ from .types import (
|
|
|
28
28
|
PulseStrideOffset,
|
|
29
29
|
TimeOfFlightLookupTable,
|
|
30
30
|
TimeOfFlightLookupTableFilename,
|
|
31
|
+
ToaDetector,
|
|
31
32
|
TofDetector,
|
|
32
33
|
TofMonitor,
|
|
33
34
|
)
|
|
@@ -51,6 +52,7 @@ __all__ = [
|
|
|
51
52
|
"TimeOfFlightLookupTable",
|
|
52
53
|
"TimeOfFlightLookupTableFilename",
|
|
53
54
|
"TimeResolution",
|
|
55
|
+
"ToaDetector",
|
|
54
56
|
"TofDetector",
|
|
55
57
|
"TofLookupTableWorkflow",
|
|
56
58
|
"TofMonitor",
|
|
@@ -36,6 +36,7 @@ from .types import (
|
|
|
36
36
|
MonitorLtotal,
|
|
37
37
|
PulseStrideOffset,
|
|
38
38
|
TimeOfFlightLookupTable,
|
|
39
|
+
ToaDetector,
|
|
39
40
|
TofDetector,
|
|
40
41
|
TofMonitor,
|
|
41
42
|
)
|
|
@@ -196,12 +197,32 @@ def _guess_pulse_stride_offset(
|
|
|
196
197
|
return sorted(tofs, key=lambda x: sc.isnan(tofs[x]).sum())[0]
|
|
197
198
|
|
|
198
199
|
|
|
199
|
-
def
|
|
200
|
+
def _prepare_tof_interpolation_inputs(
|
|
200
201
|
da: sc.DataArray,
|
|
201
202
|
lookup: sc.DataArray,
|
|
202
203
|
ltotal: sc.Variable,
|
|
203
|
-
pulse_stride_offset: int,
|
|
204
|
-
) ->
|
|
204
|
+
pulse_stride_offset: int | None,
|
|
205
|
+
) -> dict:
|
|
206
|
+
"""
|
|
207
|
+
Prepare the inputs required for the time-of-flight interpolation.
|
|
208
|
+
This function is used when computing the time-of-flight for event data, and for
|
|
209
|
+
computing the time-of-arrival for event data (as they both require guessing the
|
|
210
|
+
pulse_stride_offset if not provided).
|
|
211
|
+
|
|
212
|
+
Parameters
|
|
213
|
+
----------
|
|
214
|
+
da:
|
|
215
|
+
Data array with event data.
|
|
216
|
+
lookup:
|
|
217
|
+
Lookup table giving time-of-flight as a function of distance and time of
|
|
218
|
+
arrival.
|
|
219
|
+
ltotal:
|
|
220
|
+
Total length of the flight path from the source to the detector.
|
|
221
|
+
pulse_stride_offset:
|
|
222
|
+
When pulse-skipping, the offset of the first pulse in the stride. This is
|
|
223
|
+
typically zero but can be a small integer < pulse_stride.
|
|
224
|
+
If None, a guess is made.
|
|
225
|
+
"""
|
|
205
226
|
etos = da.bins.coords["event_time_offset"].to(dtype=float, copy=False)
|
|
206
227
|
eto_unit = elem_unit(etos)
|
|
207
228
|
|
|
@@ -259,12 +280,34 @@ def _time_of_flight_data_events(
|
|
|
259
280
|
pulse_index += pulse_stride_offset
|
|
260
281
|
pulse_index %= pulse_stride
|
|
261
282
|
|
|
262
|
-
|
|
263
|
-
|
|
283
|
+
return {
|
|
284
|
+
"eto": etos,
|
|
285
|
+
"pulse_index": pulse_index,
|
|
286
|
+
"pulse_period": pulse_period,
|
|
287
|
+
"interp": interp,
|
|
288
|
+
"ltotal": ltotal,
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _time_of_flight_data_events(
|
|
293
|
+
da: sc.DataArray,
|
|
294
|
+
lookup: sc.DataArray,
|
|
295
|
+
ltotal: sc.Variable,
|
|
296
|
+
pulse_stride_offset: int | None,
|
|
297
|
+
) -> sc.DataArray:
|
|
298
|
+
inputs = _prepare_tof_interpolation_inputs(
|
|
299
|
+
da=da,
|
|
300
|
+
lookup=lookup,
|
|
264
301
|
ltotal=ltotal,
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
302
|
+
pulse_stride_offset=pulse_stride_offset,
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
# Compute time-of-flight for all neutrons using the interpolator
|
|
306
|
+
tofs = inputs["interp"](
|
|
307
|
+
ltotal=inputs["ltotal"],
|
|
308
|
+
event_time_offset=inputs["eto"],
|
|
309
|
+
pulse_index=inputs["pulse_index"],
|
|
310
|
+
pulse_period=inputs["pulse_period"],
|
|
268
311
|
)
|
|
269
312
|
|
|
270
313
|
parts = da.bins.constituents
|
|
@@ -416,6 +459,53 @@ def monitor_time_of_flight_data(
|
|
|
416
459
|
)
|
|
417
460
|
|
|
418
461
|
|
|
462
|
+
def detector_time_of_arrival_data(
|
|
463
|
+
detector_data: RawDetector[RunType],
|
|
464
|
+
lookup: TimeOfFlightLookupTable,
|
|
465
|
+
ltotal: DetectorLtotal[RunType],
|
|
466
|
+
pulse_stride_offset: PulseStrideOffset,
|
|
467
|
+
) -> ToaDetector[RunType]:
|
|
468
|
+
"""
|
|
469
|
+
Convert the time-of-flight data to time-of-arrival data using a lookup table.
|
|
470
|
+
The output data will have a time-of-arrival coordinate.
|
|
471
|
+
The time-of-arrival is the time since the neutron was emitted from the source.
|
|
472
|
+
It is basically equal to event_time_offset + pulse_index * pulse_period.
|
|
473
|
+
|
|
474
|
+
Parameters
|
|
475
|
+
----------
|
|
476
|
+
da:
|
|
477
|
+
Raw detector data loaded from a NeXus file, e.g., NXdetector containing
|
|
478
|
+
NXevent_data.
|
|
479
|
+
lookup:
|
|
480
|
+
Lookup table giving time-of-flight as a function of distance and time of
|
|
481
|
+
arrival.
|
|
482
|
+
ltotal:
|
|
483
|
+
Total length of the flight path from the source to the detector.
|
|
484
|
+
pulse_stride_offset:
|
|
485
|
+
When pulse-skipping, the offset of the first pulse in the stride. This is
|
|
486
|
+
typically zero but can be a small integer < pulse_stride.
|
|
487
|
+
"""
|
|
488
|
+
if detector_data.bins is None:
|
|
489
|
+
raise NotImplementedError(
|
|
490
|
+
"Computing time-of-arrival in histogram mode is not implemented yet."
|
|
491
|
+
)
|
|
492
|
+
inputs = _prepare_tof_interpolation_inputs(
|
|
493
|
+
da=detector_data,
|
|
494
|
+
lookup=lookup,
|
|
495
|
+
ltotal=ltotal,
|
|
496
|
+
pulse_stride_offset=pulse_stride_offset,
|
|
497
|
+
)
|
|
498
|
+
parts = detector_data.bins.constituents
|
|
499
|
+
parts["data"] = inputs["eto"]
|
|
500
|
+
# The pulse index is None if pulse_stride == 1 (i.e., no pulse skipping)
|
|
501
|
+
if inputs["pulse_index"] is not None:
|
|
502
|
+
parts["data"] = parts["data"] + inputs["pulse_index"] * inputs["pulse_period"]
|
|
503
|
+
result = detector_data.bins.assign_coords(
|
|
504
|
+
toa=sc.bins(**parts, validate_indices=False)
|
|
505
|
+
)
|
|
506
|
+
return result
|
|
507
|
+
|
|
508
|
+
|
|
419
509
|
def providers() -> tuple[Callable]:
|
|
420
510
|
"""
|
|
421
511
|
Providers of the time-of-flight workflow.
|
|
@@ -425,4 +515,5 @@ def providers() -> tuple[Callable]:
|
|
|
425
515
|
monitor_time_of_flight_data,
|
|
426
516
|
detector_ltotal_from_straight_line_approximation,
|
|
427
517
|
monitor_ltotal_from_straight_line_approximation,
|
|
518
|
+
detector_time_of_arrival_data,
|
|
428
519
|
)
|
|
@@ -420,7 +420,7 @@ def simulate_chopper_cascade_using_tof(
|
|
|
420
420
|
else tof.Clockwise,
|
|
421
421
|
open=ch.slit_begin,
|
|
422
422
|
close=ch.slit_end,
|
|
423
|
-
phase=
|
|
423
|
+
phase=ch.phase if ch.frequency.value > 0.0 else -ch.phase,
|
|
424
424
|
distance=sc.norm(
|
|
425
425
|
ch.axle_position - source_position.to(unit=ch.axle_position.unit)
|
|
426
426
|
),
|
|
@@ -37,5 +37,17 @@ class TofDetector(sl.Scope[RunType, sc.DataArray], sc.DataArray):
|
|
|
37
37
|
"""Detector data with time-of-flight coordinate."""
|
|
38
38
|
|
|
39
39
|
|
|
40
|
+
class ToaDetector(sl.Scope[RunType, sc.DataArray], sc.DataArray):
|
|
41
|
+
"""Detector data with time-of-arrival coordinate.
|
|
42
|
+
|
|
43
|
+
When the pulse stride is 1 (i.e., no pulse skipping), the time-of-arrival is the
|
|
44
|
+
same as the event_time_offset. When pulse skipping is used, the time-of-arrival is
|
|
45
|
+
the event_time_offset + pulse_offset * pulse_period.
|
|
46
|
+
This means that the time-of-arrival is basically the event_time_offset wrapped
|
|
47
|
+
over the frame period instead of the pulse period
|
|
48
|
+
(where frame_period = pulse_stride * pulse_period).
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
|
|
40
52
|
class TofMonitor(sl.Scope[RunType, MonitorType, sc.DataArray], sc.DataArray):
|
|
41
53
|
"""Monitor data with time-of-flight coordinate."""
|
|
@@ -63,9 +63,9 @@ def tbl_registry() -> Registry:
|
|
|
63
63
|
return make_registry(
|
|
64
64
|
'ess/tbl',
|
|
65
65
|
files={
|
|
66
|
-
"857127_00000112_small.hdf": "md5:
|
|
66
|
+
"857127_00000112_small.hdf": "md5:0db89493b859dbb2f7354c3711ed7fbd",
|
|
67
67
|
},
|
|
68
|
-
version='
|
|
68
|
+
version='2',
|
|
69
69
|
)
|
|
70
70
|
|
|
71
71
|
|
|
@@ -4,15 +4,17 @@ from datetime import UTC, datetime
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
6
|
import pytest
|
|
7
|
+
import sciline as sl
|
|
7
8
|
import scipp as sc
|
|
8
9
|
import scippnexus as snx
|
|
9
10
|
from scipp.testing import assert_identical
|
|
10
11
|
|
|
11
|
-
from ess.reduce.nexus import compute_component_position, workflow
|
|
12
|
+
from ess.reduce.nexus import compute_component_position, load_from_path, workflow
|
|
12
13
|
from ess.reduce.nexus.types import (
|
|
13
14
|
BackgroundRun,
|
|
14
15
|
Beamline,
|
|
15
16
|
EmptyBeamRun,
|
|
17
|
+
EmptyDetector,
|
|
16
18
|
Filename,
|
|
17
19
|
FrameMonitor0,
|
|
18
20
|
FrameMonitor1,
|
|
@@ -20,6 +22,8 @@ from ess.reduce.nexus.types import (
|
|
|
20
22
|
Measurement,
|
|
21
23
|
MonitorType,
|
|
22
24
|
NeXusComponentLocationSpec,
|
|
25
|
+
NeXusFileSpec,
|
|
26
|
+
NeXusLocationSpec,
|
|
23
27
|
NeXusName,
|
|
24
28
|
NeXusTransformation,
|
|
25
29
|
PreopenNeXusFile,
|
|
@@ -576,7 +580,7 @@ def test_load_histogram_monitor_workflow(dream_coda_test_file: Path) -> None:
|
|
|
576
580
|
|
|
577
581
|
|
|
578
582
|
def test_load_detector_workflow(loki_tutorial_sample_run_60250: Path) -> None:
|
|
579
|
-
wf = LoadDetectorWorkflow(run_types=[SampleRun]
|
|
583
|
+
wf = LoadDetectorWorkflow(run_types=[SampleRun])
|
|
580
584
|
wf[Filename[SampleRun]] = loki_tutorial_sample_run_60250
|
|
581
585
|
wf[NeXusName[snx.NXdetector]] = 'larmor_detector'
|
|
582
586
|
da = wf.compute(RawDetector[SampleRun])
|
|
@@ -585,6 +589,31 @@ def test_load_detector_workflow(loki_tutorial_sample_run_60250: Path) -> None:
|
|
|
585
589
|
assert da.dims == ('detector_number',)
|
|
586
590
|
|
|
587
591
|
|
|
592
|
+
def test_load_histogram_detector_workflow(tbl_commissioning_orca_file: Path) -> None:
|
|
593
|
+
wf = LoadDetectorWorkflow(run_types=[SampleRun])
|
|
594
|
+
wf[Filename[SampleRun]] = tbl_commissioning_orca_file
|
|
595
|
+
wf[NeXusName[snx.NXdetector]] = 'orca_detector'
|
|
596
|
+
da = wf.compute(RawDetector[SampleRun])
|
|
597
|
+
assert 'position' in da.coords
|
|
598
|
+
assert da.bins is None
|
|
599
|
+
assert 'time' in da.dims
|
|
600
|
+
assert da.ndim == 3
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
def test_load_empty_histogram_detector_workflow(
|
|
604
|
+
tbl_commissioning_orca_file: Path,
|
|
605
|
+
) -> None:
|
|
606
|
+
wf = LoadDetectorWorkflow(run_types=[SampleRun])
|
|
607
|
+
wf[Filename[SampleRun]] = tbl_commissioning_orca_file
|
|
608
|
+
wf[NeXusName[snx.NXdetector]] = 'orca_detector'
|
|
609
|
+
da = wf.compute(EmptyDetector[SampleRun])
|
|
610
|
+
assert 'position' in da.coords
|
|
611
|
+
assert da.bins is None
|
|
612
|
+
# The empty detector has no time dimension, only the dimensions of the geometry
|
|
613
|
+
assert 'time' not in da.dims
|
|
614
|
+
assert da.ndim == 2
|
|
615
|
+
|
|
616
|
+
|
|
588
617
|
@pytest.mark.parametrize('preopen', [True, False])
|
|
589
618
|
def test_generic_nexus_workflow(
|
|
590
619
|
preopen: bool, loki_tutorial_sample_run_60250: Path
|
|
@@ -750,3 +779,48 @@ def assert_not_contains_type_arg(node: object, excluded: set[type]) -> None:
|
|
|
750
779
|
assert not any(
|
|
751
780
|
arg in excluded for arg in getattr(node, "__args__", ())
|
|
752
781
|
), f"Node {node} contains one of {excluded!r}"
|
|
782
|
+
|
|
783
|
+
|
|
784
|
+
def test_generic_nexus_workflow_load_custom_field_user_affiliation(
|
|
785
|
+
loki_tutorial_sample_run_60250: Path,
|
|
786
|
+
) -> None:
|
|
787
|
+
class UserAffiliation(sl.Scope[RunType, str], str):
|
|
788
|
+
"""User affiliation."""
|
|
789
|
+
|
|
790
|
+
def load_user_affiliation(
|
|
791
|
+
file: NeXusFileSpec[RunType], path: NeXusName[UserAffiliation[RunType]]
|
|
792
|
+
) -> UserAffiliation[RunType]:
|
|
793
|
+
return UserAffiliation[RunType](
|
|
794
|
+
load_from_path(NeXusLocationSpec(filename=file.value, component_name=path))
|
|
795
|
+
)
|
|
796
|
+
|
|
797
|
+
wf = GenericNeXusWorkflow(run_types=[SampleRun], monitor_types=[])
|
|
798
|
+
wf.insert(load_user_affiliation)
|
|
799
|
+
wf[Filename[SampleRun]] = loki_tutorial_sample_run_60250
|
|
800
|
+
# Path is relative to the top-level '/entry'
|
|
801
|
+
wf[NeXusName[UserAffiliation[SampleRun]]] = 'user_0/affiliation'
|
|
802
|
+
affiliation = wf.compute(UserAffiliation[SampleRun])
|
|
803
|
+
assert affiliation == 'ESS'
|
|
804
|
+
|
|
805
|
+
|
|
806
|
+
def test_generic_nexus_workflow_load_custom_group_user(
|
|
807
|
+
loki_tutorial_sample_run_60250: Path,
|
|
808
|
+
) -> None:
|
|
809
|
+
class UserInfo(sl.Scope[RunType, str], str):
|
|
810
|
+
"""User info."""
|
|
811
|
+
|
|
812
|
+
def load_user_info(
|
|
813
|
+
file: NeXusFileSpec[RunType], path: NeXusName[UserInfo[RunType]]
|
|
814
|
+
) -> UserInfo[RunType]:
|
|
815
|
+
return UserInfo[RunType](
|
|
816
|
+
load_from_path(NeXusLocationSpec(filename=file.value, component_name=path))
|
|
817
|
+
)
|
|
818
|
+
|
|
819
|
+
wf = GenericNeXusWorkflow(run_types=[SampleRun], monitor_types=[])
|
|
820
|
+
wf.insert(load_user_info)
|
|
821
|
+
wf[Filename[SampleRun]] = loki_tutorial_sample_run_60250
|
|
822
|
+
# Path is relative to the top-level '/entry'
|
|
823
|
+
wf[NeXusName[UserInfo]] = 'user_0'
|
|
824
|
+
user_info = wf.compute(UserInfo[SampleRun])
|
|
825
|
+
assert user_info['affiliation'] == 'ESS'
|
|
826
|
+
assert user_info['name'] == 'John Doe'
|
|
@@ -9,7 +9,12 @@ from scippneutron.conversion.graph.tof import elastic as elastic_graph
|
|
|
9
9
|
|
|
10
10
|
from ess.reduce import time_of_flight
|
|
11
11
|
from ess.reduce.nexus.types import AnyRun, RawDetector, SampleRun
|
|
12
|
-
from ess.reduce.time_of_flight import
|
|
12
|
+
from ess.reduce.time_of_flight import (
|
|
13
|
+
GenericTofWorkflow,
|
|
14
|
+
PulsePeriod,
|
|
15
|
+
TofLookupTableWorkflow,
|
|
16
|
+
fakes,
|
|
17
|
+
)
|
|
13
18
|
|
|
14
19
|
sl = pytest.importorskip("sciline")
|
|
15
20
|
|
|
@@ -441,3 +446,63 @@ def test_unwrap_int(dtype, lut_workflow_psc_choppers) -> None:
|
|
|
441
446
|
_validate_result_events(
|
|
442
447
|
tofs=tofs, ref=ref, percentile=100, diff_threshold=0.02, rtol=0.05
|
|
443
448
|
)
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def test_compute_toa():
|
|
452
|
+
distance = sc.scalar(80.0, unit="m")
|
|
453
|
+
choppers = fakes.psc_choppers()
|
|
454
|
+
|
|
455
|
+
lut_wf = make_lut_workflow(
|
|
456
|
+
choppers=choppers, neutrons=500_000, seed=1234, pulse_stride=1
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
pl, _ = _make_workflow_event_mode(
|
|
460
|
+
distance=distance,
|
|
461
|
+
choppers=choppers,
|
|
462
|
+
lut_workflow=lut_wf,
|
|
463
|
+
seed=2,
|
|
464
|
+
pulse_stride_offset=0,
|
|
465
|
+
error_threshold=0.1,
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
toas = pl.compute(time_of_flight.ToaDetector[SampleRun])
|
|
469
|
+
|
|
470
|
+
assert "toa" in toas.bins.coords
|
|
471
|
+
raw = pl.compute(RawDetector[SampleRun])
|
|
472
|
+
assert sc.allclose(toas.bins.coords["toa"], raw.bins.coords["event_time_offset"])
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def test_compute_toa_pulse_skipping():
|
|
476
|
+
distance = sc.scalar(100.0, unit="m")
|
|
477
|
+
choppers = fakes.pulse_skipping_choppers()
|
|
478
|
+
|
|
479
|
+
lut_wf = make_lut_workflow(
|
|
480
|
+
choppers=choppers, neutrons=500_000, seed=1234, pulse_stride=2
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
pl, _ = _make_workflow_event_mode(
|
|
484
|
+
distance=distance,
|
|
485
|
+
choppers=choppers,
|
|
486
|
+
lut_workflow=lut_wf,
|
|
487
|
+
seed=2,
|
|
488
|
+
pulse_stride_offset=1,
|
|
489
|
+
error_threshold=0.1,
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
raw = pl.compute(RawDetector[SampleRun])
|
|
493
|
+
|
|
494
|
+
toas = pl.compute(time_of_flight.ToaDetector[SampleRun])
|
|
495
|
+
|
|
496
|
+
assert "toa" in toas.bins.coords
|
|
497
|
+
pulse_period = lut_wf.compute(PulsePeriod)
|
|
498
|
+
hist = toas.bins.concat().hist(
|
|
499
|
+
toa=sc.array(
|
|
500
|
+
dims=["toa"],
|
|
501
|
+
values=[0, pulse_period.value, pulse_period.value * 2],
|
|
502
|
+
unit=pulse_period.unit,
|
|
503
|
+
).to(unit=toas.bins.coords["toa"].unit)
|
|
504
|
+
)
|
|
505
|
+
# There should be counts in both bins
|
|
506
|
+
n = raw.sum().value
|
|
507
|
+
assert hist.data[0].value > n / 5
|
|
508
|
+
assert hist.data[1].value > n / 5
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|