Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
528 commits
Select commit Hold shift + click to select a range
b8fb654
Remove python interpreter header.
jamescrake-merani Mar 4, 2025
74b896c
Remove this comment as I don't think its true.
jamescrake-merani Mar 4, 2025
10455fe
Skeleton implementation for ordinate and abscissae
jamescrake-merani Mar 4, 2025
8366cd8
These are the wrong way round.
jamescrake-merani Mar 5, 2025
198c0a3
Match statements are causing problems; use if.
jamescrake-merani Mar 5, 2025
042eb29
Implement abscissae for 2D data.
jamescrake-merani Mar 5, 2025
94fcc04
Wrote a test for 1d data.
jamescrake-merani Mar 5, 2025
2589732
Use the value property.
jamescrake-merani Mar 5, 2025
7769f90
2D test.
jamescrake-merani Mar 5, 2025
f569126
Bring guess into sasdata.
Feb 12, 2025
6b9f873
Bring default logic from SasView here.
Feb 12, 2025
29213a6
Make the default value a param.
Feb 12, 2025
6b732f8
Function for making the guesses.
Feb 12, 2025
a426c19
Forgot return statement.
Feb 12, 2025
4b3fb72
Column unit can be none.
Feb 12, 2025
4fb53e4
If there are extra columns than expected, ignore.
Feb 12, 2025
b855026
Wrote a test for the ASCII reader.
Feb 12, 2025
847b966
Need to get the full filename.
Feb 12, 2025
d55c377
Don't use filename to get filename :P
Feb 12, 2025
d0baf0a
Property that doesn't include ignore columns.
Feb 12, 2025
b742d2c
It seems I'm a bit sleepy today.
Feb 12, 2025
6cfd4e3
Use the new property.
Feb 12, 2025
330a725
Find can accept more locations in the future.
Feb 13, 2025
e66ff5c
Missing import.
Feb 13, 2025
f09c7fe
Second test.
Feb 13, 2025
b2077bb
Uncertainties should have the same units.
Feb 17, 2025
053d494
Add slow cubic interpolation
rprospero Feb 3, 2025
dd2ad3c
Fix first for loop
rprospero Feb 4, 2025
0729c39
Remove second for loop from conversion_matrix calculation
rprospero Feb 4, 2025
85120f7
Parameterize tests over interpolation orders
rprospero Feb 17, 2025
84760ee
Create test option for displaying diagnostic plots
rprospero Feb 17, 2025
ca16ca1
More interpolation test into main test directory
rprospero Feb 17, 2025
dec5669
Temporarily add a magnetic category.
jamescrake-merani Mar 4, 2025
3a82a27
Fixed typo.
jamescrake-merani Mar 5, 2025
db12067
Don't compare against the quantity.
jamescrake-merani Mar 5, 2025
c8abd6d
Need to convert to list before array.
jamescrake-merani Mar 5, 2025
444a05f
Apply all twice.
jamescrake-merani Mar 5, 2025
9355316
Use brackets properly.
jamescrake-merani Mar 5, 2025
5c1e0ce
Add tests for dataset
rprospero Mar 4, 2025
e29eaa5
Handle collimations directly
rprospero Mar 5, 2025
1836411
Start properly descending node tree
rprospero Mar 5, 2025
8335fc7
Make aperture part of collimation
rprospero Mar 5, 2025
f92655b
Move to dataclasses
rprospero Mar 17, 2025
f542286
Parse source
rprospero Mar 17, 2025
0c6c4d2
Parse source in instrument
rprospero Mar 17, 2025
bb6edb4
Start fixing Detector setup
rprospero Mar 17, 2025
4815c98
Get Detector name and wavelength properly
rprospero Mar 17, 2025
fd9f15a
Better unit parsing
rprospero Mar 17, 2025
f18ebcb
All the detector parts
rprospero Mar 17, 2025
11dabb5
Fix up aperture
rprospero Mar 17, 2025
4b9d963
Instrument is a data class
rprospero Mar 17, 2025
71c09ef
Keyword only dataclasses
rprospero Mar 17, 2025
32cf210
Make the Instrument metadata optional
rprospero Mar 17, 2025
20aed4e
Parse sample data
rprospero Mar 17, 2025
d83bd3c
Refactor our conditional parsing code
rprospero Mar 17, 2025
f42113a
Process Metadata as a dataclass
rprospero Mar 17, 2025
f5f753d
Simplify radiation handling
rprospero Mar 17, 2025
6e25baa
Include high leve metadata in metadata
rprospero Mar 17, 2025
e78db91
Metadata is a dataclass
rprospero Mar 17, 2025
213dbc2
Remove unused values
rprospero Mar 17, 2025
6d457e0
Properly load and test multiple entries
rprospero Mar 17, 2025
8836bba
Minor cleanup
rprospero Mar 17, 2025
b2e4641
Flag node types for parses
rprospero Mar 17, 2025
ccadd77
Fixup after rebase
rprospero Mar 17, 2025
b5be36a
Clean up import lint
rprospero Mar 17, 2025
aa33dca
More lint
rprospero Mar 17, 2025
a243435
More handling of the change to dict
rprospero Mar 17, 2025
ffb6e05
All metadata needs to be optional to accomodate other file formats
rprospero Mar 19, 2025
74afe49
Fix up types
rprospero Mar 19, 2025
71f4a61
Collimation is optional
rprospero Mar 19, 2025
a652ab6
Fix imports
rprospero Mar 19, 2025
08b68e5
Replace optional with proper sum type
rprospero Mar 19, 2025
392b4ff
Remove unused imports
rprospero Mar 19, 2025
614c1e2
Fix parsing of unit full names
rprospero Mar 19, 2025
2778bbc
Remove testing bit
rprospero Mar 19, 2025
41f9b8b
Don't edit the file with a giant "Don't edit this file" header
rprospero Mar 19, 2025
6115f73
Handle null title and sample in summary
rprospero Apr 2, 2025
87d2c30
Start parsing XML
rprospero Apr 2, 2025
7dbfac7
Create _load_text helper
rprospero Apr 2, 2025
6d198d6
Parse process
rprospero Apr 2, 2025
cee1619
Refactor with helper functions
rprospero Apr 2, 2025
637e8a1
Fully parse xml metadata
rprospero Apr 2, 2025
d673b3c
Parse IData in XML
rprospero Apr 2, 2025
2e91c4f
Remove extraneous line in hdf reader
rprospero Apr 2, 2025
1c058d2
Trial multiple files
rprospero Apr 2, 2025
d292b34
Fix aperture load
rprospero Apr 2, 2025
94e9c68
Support multiple cansas versions
rprospero Apr 2, 2025
ec59240
More IData column types
rprospero Apr 4, 2025
f955710
Start adding official tests for xml file loading
rprospero Apr 4, 2025
7264c1d
Add singular alias to degrees unit
rprospero Apr 4, 2025
bcb803a
Better work at handling comments
rprospero Apr 4, 2025
a422741
Refactor text parsing to better handle comments
rprospero Apr 4, 2025
058e827
More tests of xml
rprospero Apr 4, 2025
f53d66b
Mark weird units, but continue parsing
rprospero Apr 4, 2025
60d4bfb
Add last xml test
rprospero Apr 4, 2025
a4190d7
Fix parsing of BeamSize
rprospero Apr 4, 2025
068cc09
Add micron unit alias
rprospero Apr 8, 2025
f7dab12
Handle singular unit names in parsing
rprospero Apr 8, 2025
5c7cb72
Always print summary in same order
rprospero Apr 8, 2025
bf7244e
Reinstate summary output when xml loader is used as a main module
rprospero Apr 14, 2025
5512416
Factor our finding cansas version into its own file
rprospero Apr 14, 2025
be963bb
More docstrings
rprospero Apr 14, 2025
5b3daf4
Fix typo in comment
rprospero Apr 14, 2025
7c32f83
Fix type hints
rprospero Apr 14, 2025
0da9553
Provide default name for unnamed data sets
rprospero Apr 14, 2025
e0db3b8
Better default names for SasData entries
rprospero Apr 14, 2025
a606e9c
Process can have multiple terms, which may be quantites
rprospero Apr 14, 2025
1e726cb
Fix hdf process term parsing
rprospero Apr 14, 2025
0abcf01
Correctly parse process notes
rprospero Apr 14, 2025
a2207fd
Add type hint to load_data filename
rprospero Apr 14, 2025
74cb534
More concise source parsing
rprospero Apr 24, 2025
d3931d0
Whitespace fix
rprospero Apr 24, 2025
01e7d95
Typehint on load_data
rprospero Apr 24, 2025
a7206de
Fix up Source parser
rprospero Apr 24, 2025
ab78e8c
Add raw data to xml metadata
rprospero Apr 15, 2025
f67ca5a
Add raw handling to reader
rprospero Apr 15, 2025
4fb6948
Enable raw data filter
rprospero Apr 15, 2025
bc8415a
Start testing data filter
rprospero Apr 15, 2025
8592769
Run through code formatter
rprospero Apr 15, 2025
07e753a
Simplify metadata filtering
rprospero Apr 24, 2025
dbf1afb
Raise KeyError instead of ValueError
rprospero May 7, 2025
0e9e219
Fix up ascii reader tests
rprospero May 8, 2025
e21d9ab
Update creation of SasData in trend
rprospero May 8, 2025
2ce3491
Properly compare named units with unnamed units
rprospero May 8, 2025
4cb0e45
Enforce loading test reference files in UTF-8
rprospero May 8, 2025
0716618
Skeleton framework for SESANS data
rprospero May 6, 2025
6e6e7ba
Start parsing sesans header
rprospero May 6, 2025
db6457f
Start parsing SESANS sample metadata
rprospero May 6, 2025
fd8311f
Include SESANS angle metadata
rprospero May 6, 2025
2c76846
Multiple SESANS files in reader test
rprospero May 6, 2025
577d1d9
Parse actual data from SES files
rprospero May 6, 2025
ffa2fb8
Add Raw SESANS Node Data
rprospero May 7, 2025
3803d0d
Update XML test references to include apertures
rprospero May 7, 2025
9d56eb1
SESANS metadata as a process, not an aperture
rprospero May 7, 2025
ad94f77
Fixup lint
rprospero May 7, 2025
c3f28f7
Make changes suggested in PR review
rprospero May 19, 2025
367967b
Fix simple typos from rebase
rprospero Jun 3, 2025
4d4252c
Add equality testing for quantities
rprospero Jun 10, 2025
a8644a8
More tests for quantities
rprospero Jun 10, 2025
08e2fb9
Fix meshmerge calculation
rprospero Jun 17, 2025
d5e3a8b
Ruff format.
jamescrake-merani Jun 23, 2025
7f2b413
This comment was repetitive.
jamescrake-merani Jun 23, 2025
de8e4e6
Ruff format.
jamescrake-merani Jun 24, 2025
5e12205
Function to guess the dataset type.
jamescrake-merani Jun 24, 2025
cc305f9
Function for loading a file with default params.
jamescrake-merani Jun 24, 2025
61c62b0
Ruff format.
jamescrake-merani Jun 24, 2025
af8e198
Remove imports ruff is complaining aren't used.
jamescrake-merani Jun 24, 2025
357f986
Added a test to make sure 2d data gets read right.
jamescrake-merani Jun 24, 2025
e907221
Makes sure the dataset type gets guessed.
jamescrake-merani Jun 24, 2025
57f1e68
Use basename for sasdata ascii name.
jamescrake-merani Jun 25, 2025
a15298e
Be consistent with how basename is called.
jamescrake-merani Jul 4, 2025
5d11621
Move comment to avoid odd Ruff format.
jamescrake-merani Jul 4, 2025
abb2f7b
Move comment again.
jamescrake-merani Jul 4, 2025
839b39e
Fixed test.
jamescrake-merani Jul 4, 2025
a731597
Created an import metadata function.
jamescrake-merani Jul 7, 2025
4ba6657
Remove the old function.
jamescrake-merani Jul 7, 2025
a1c00c8
Use the new import function.
jamescrake-merani Jul 7, 2025
174e68f
Remove these imports.
jamescrake-merani Jul 7, 2025
279cd95
Need to use keywords for this dataclass.
jamescrake-merani Jul 7, 2025
df3b70f
Import the mumag data for testing.
jamescrake-merani Jul 7, 2025
192b43a
Added a case for mumag data.
jamescrake-merani Jul 7, 2025
a5f0aa5
Added test for loading ascii data with metadata.
jamescrake-merani Jul 7, 2025
f3b53e4
Use params filenames not just filenames.
jamescrake-merani Jul 7, 2025
3486b09
Fixed column parameters.
jamescrake-merani Jul 7, 2025
8a54ddb
Doh. Missing commas on filename list.
jamescrake-merani Jul 7, 2025
d07e432
Roll the raw metadata into the metadata object.
jamescrake-merani Jul 7, 2025
37cef0d
Need to fill in all the parameters.
jamescrake-merani Jul 7, 2025
9b82cbc
Forgot process :P
jamescrake-merani Jul 7, 2025
bbe282d
Consider both of these.
jamescrake-merani Jul 7, 2025
bda9de3
Combine both metadata so we go through all of them
jamescrake-merani Jul 7, 2025
012fe8b
Raw metadata is in lists.
jamescrake-merani Jul 7, 2025
f2dff42
I don't know what these decimals were.
jamescrake-merani Jul 7, 2025
b47297c
Start implementing ModellingRequirements
rprospero Jun 18, 2025
580c131
Start testing modelling requirements
rprospero Jun 18, 2025
a111590
Start adding better tests for ModellingRequirements
rprospero Jun 18, 2025
5732617
Flip order of parameters on compose
rprospero Jun 18, 2025
62238d6
Don't assume that Sesans includes smear
rprospero Jun 18, 2025
ccec88e
Enable left composition by null model
rprospero Jun 18, 2025
94f7de6
support right addition of NullModel
rprospero Jun 18, 2025
da83c51
Allow preprocess and postprocess steps
rprospero Jun 18, 2025
74881f5
Start performing Hankel transform for SESANS
rprospero Jun 18, 2025
cd225f8
Ignore slit smearing before SESANS
rprospero Jun 20, 2025
618409c
Pull sesans metadata from file
rprospero Jun 20, 2025
736f0c5
Add unit test for Hankel transform
rprospero Jun 20, 2025
998378b
Fix units with error calculation
rprospero Jun 20, 2025
125ca04
χ² squared based test
rprospero Jun 20, 2025
7d466cf
Code Review Suggestions
rprospero Jun 20, 2025
350eaf2
Fix up rename in compose
rprospero Jun 20, 2025
f642ef1
Fix uncertainty in SESANS parser
rprospero Jul 1, 2025
2f61308
Update SESANS units in unit_kinds
rprospero Jul 14, 2025
c1bdc32
Applies auto fixes for ruff rule F401
DrPaulSharp Jul 17, 2025
c2ad2af
Applies auto fixes for ruff rule E714
DrPaulSharp Jul 17, 2025
7bd7c32
Applies auto fixes for ruff rule F541
DrPaulSharp Jul 17, 2025
d03a440
Bumps minimum python version to 3.12
DrPaulSharp Jul 17, 2025
bcefce7
Test should fail when there's no data.
Jul 18, 2025
5690d37
Added a get default unit function.
Jul 18, 2025
f639a11
Use the new get default unit function.
Jul 18, 2025
fab834f
Pass in the unit group as well.
Jul 18, 2025
7489c95
Return value if it isn't None.
Jul 18, 2025
d92362c
Set the dataset type properly.
Jul 18, 2025
9c3e1e4
Expect 2D test to fail.
Jul 18, 2025
0f7bd91
Use Quantity in models
rprospero Jul 14, 2025
ad64bf7
Don't use a quantity for model post processing
rprospero Jul 14, 2025
c7c3d4b
Basic Pinhole model
rprospero Jul 14, 2025
52f5314
Refactor pinhole tests
rprospero Jul 15, 2025
f14c8e7
Fix radius bug in smearing
rprospero Jul 17, 2025
43269a1
More realisting δq
rprospero Jul 18, 2025
b2d93c7
Combine Smearing and Sesans tests
rprospero Jul 17, 2025
137ea54
Define model metadata in constructor
rprospero Jul 18, 2025
41ae684
Better testing range for pinhole
rprospero Jul 18, 2025
5414665
Add slit smearing
rprospero Jul 18, 2025
95ddb75
Remove old commented code
rprospero Jul 18, 2025
94ccd98
Vectorise slit calculation
rprospero Jul 21, 2025
9e97400
Check combination of slit models
rprospero Jul 21, 2025
a9f4d98
Decrease logical depth of linear_extrapolation
rprospero Jul 31, 2025
96cb964
Decrease logical depth of slit_resolution
rprospero Jul 31, 2025
9534ea1
Decrease logical depth of geometric_extrapolation
rprospero Jul 31, 2025
1afeaa6
Apply PR changes
rprospero Jul 31, 2025
623043d
Fixes ruff linting errors (#140)
DrPaulSharp Aug 1, 2025
7c26dca
Adds Pyupgrade (UP) ruleset and rules SIM118 & SIM300 to ruff linter …
DrPaulSharp Aug 6, 2025
a0ab00a
Applies fixes for isort (I) ruff ruleset (#146)
DrPaulSharp Aug 7, 2025
ab48763
Fixes whitespace errors
DrPaulSharp Aug 6, 2025
c99f173
Fix hours, days, and years
rprospero Aug 1, 2025
e27d0cc
Add command for explicitly formatting quantity units
rprospero Aug 1, 2025
43cfd12
Add test harness for serialisation
rprospero Jun 3, 2025
39e11a3
Add first real json property
rprospero Aug 1, 2025
f9419a0
Export data type
rprospero Aug 1, 2025
9b4a561
Add mask and model requirements
rprospero Aug 1, 2025
4acda72
Start exporting metadata
rprospero Aug 1, 2025
e66117d
Start adding sample details
rprospero Aug 1, 2025
948759b
Export process metadata
rprospero Aug 1, 2025
0037490
Export Instrument and Raw metadata
rprospero Aug 1, 2025
df12d24
Support exporting Quantities
rprospero Aug 1, 2025
775170e
Remove unneeded default calls
rprospero Aug 1, 2025
6ffbaa8
Start writing decoder
rprospero Aug 1, 2025
bf15104
Correctly export sample temperature
rprospero Aug 1, 2025
c37facd
Fix notes nodes type
rprospero Aug 1, 2025
431e6e8
Decode metadata from json
rprospero Aug 1, 2025
ad82625
Use proper bytes for numpy serialisation
rprospero Aug 11, 2025
0a6b213
Fix historical unit errors
rprospero Aug 1, 2025
e19f214
Parse MetaNode
rprospero Aug 1, 2025
76ace63
Use .json extension for JSON files
rprospero Aug 11, 2025
a45f18e
Establish settings for ruff and pycodestyle
rprospero Aug 12, 2025
2d9364b
Fix formatting
rprospero Aug 12, 2025
75bd590
[pre-commit.ci lite] apply automatic fixes for ruff linting errors
pre-commit-ci-lite[bot] Aug 12, 2025
dcb3fb3
Merge pull request #152 from SasView/serialise-sasdata
rprospero Aug 13, 2025
7d1cbd6
Abscissa basics
lucas-wilkins Aug 11, 2025
7af5c33
Abscissa WIP
lucas-wilkins Aug 13, 2025
78bcc6d
Fixed check
lucas-wilkins Aug 14, 2025
078cb36
[pre-commit.ci lite] apply automatic fixes for ruff linting errors
pre-commit-ci-lite[bot] Aug 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
python-version: ['3.10', '3.11', '3.12']
python-version: ['3.12']
fail-fast: false

env:
Expand Down
11 changes: 11 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import pytest


def pytest_addoption(parser):
parser.addoption(
"--show_plots", action="store_true", default=False, help="Display diagnostic plots during tests"
)

@pytest.fixture
def show_plots(request):
return request.config.getoption("--show_plots")
26 changes: 25 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ dynamic = [
]
description = "Sas Data Loader application"
readme = "README.md"
requires-python = ">=3.9"
requires-python = ">=3.12"
license = { text = "BSD-3-Clause" }
authors = [
{name = "SasView Team", email = "[email protected]"},
Expand Down Expand Up @@ -118,3 +118,27 @@ testpaths = [
norecursedirs = [
"sasdata",
]

[tool.ruff]
line-length = 120

[tool.ruff.lint]
# The ruff rules are available at: https://docs.astral.sh/ruff/rules/
select = ["E", # pycodestyle errors
"F", # pyflakes
"I", # isort
"UP", # pyupgrade
"SIM118", # Use `key in dict` instead of `key in dict.keys()`
"SIM300"] # Yoda condition detected


ignore = ["E501", # line too long (leave to formatter)
"UP008", # Use `super()` instead of `super(__class__, self)`
"UP031"] # Use format specifiers instead of percent format

[tool.ruff.lint.isort.sections]
# Group all SasView and SasModels imports into a separate section.
"sas" = ["sas", "sasmodels"]

[tool.ruff.lint.isort]
section-order = ["future", "standard-library", "third-party", "sas", "first-party", "local-folder"]
12 changes: 12 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,15 @@ lxml

# Calculation
numpy
scipy

# Unit testing
pytest
unittest-xml-reporting

# Documentation (future)
sphinx
html5lib

# Other stuff
matplotlib
126 changes: 126 additions & 0 deletions sasdata/abscissa.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
from abc import ABC, abstractmethod

import numpy as np
from exceptions import InterpretationError
from numpy._typing import ArrayLike
from quantities.quantity import Quantity
from util import is_increasing


class Abscissa(ABC):

def __init__(self, axes: list[Quantity]):
self._axes = axes
self._dimensionality = len(axes)
@property
def dimensionality(self) -> int:
""" Dimensionality of this data """
return self._dimensionality

@property
@abstractmethod
def is_grid(self) -> bool:
""" Are these coordinates using a grid representation
( as opposed to a general list representation)

is_grid = True: implies that the corresponding ordinate is n-dimensional tensor
is_grid = False: implies that the corresponding ordinate is a 1D list

If the data is one dimensional, is_grid=True

"""


@property
def axes(self) -> list[Quantity]:
""" Axes of the data:

If it's an (n1-by-n2-by-n3...) grid (is_grid=True): give the values for each axis, returning a list like
[Quantity(length n1), Quantity(length n2), Quantity(length n3) ... ]

If it is not grid data (is_grid=False), but n points on a general mesh, give one array for each dimension
[Quantity(length n), Quantity(length n), Quantity(length n) ... ]

"""

return self._axes

@staticmethod
def _determine_error_message(axis_arrays: list[np.ndarray], ordinate_shape: tuple):
""" Error message for the `.determine` function"""

shape_string = ", ".join([str(axis.shape) for axis in axis_arrays])

return f"Cannot interpret array shapes axis: [{shape_string}], ordinate: {ordinate_shape}"

@staticmethod
def determine(axis_data: list[Quantity[ArrayLike]], ordinate_data: Quantity[ArrayLike]) -> "Abscissa":
""" Get an Abscissa object that fits the combination of axes and data"""

# Different posibilites:
# 1: axes_data[i].shape == axes_data[j].shape == ordinate_data.shape
# 1a: axis_data[i] is 1D =>
# 1a-i: len(axes_data) == 1 => Grid type or Scatter type depending on sortedness
# 1a-ii: len(axes_data) != 1 => Scatter type
# 1b: axis_data[i] dimensionality matches len(axis_data) => Meshgrid type
# 1c: other => Error
# 2: (len(axes_data[0]), len(axes_data[1])... ) == ordinate_data.shape => Grid type
# 3: None of the above => Error

ordinate_shape = np.array(ordinate_data.value).shape
axis_arrays = [np.array(axis.value) for axis in axis_data]

# 1:
if all([axis.shape == ordinate_shape for axis in axis_arrays]):
# 1a:
if all([len(axis.shape)== 1 for axis in axis_arrays]):
# 1a-i:
if len(axis_arrays) == 1:
# Is it sorted
if is_increasing(axis_arrays[0]):
return GridAbscissa(axis_data)
else:
return ScatterAbscissa(axis_data)
# 1a-ii
else:
return ScatterAbscissa(axis_data)
# 1b
elif all([len(axis.shape) == len(axis_arrays) for axis in axis_arrays]):

return MeshgridAbscissa(axis_data)

else:
raise InterpretationError(Abscissa._determine_error_message(axis_arrays, ordinate_shape))

elif all([len(axis.shape)== 1 for axis in axis_arrays]) and \
tuple([axis.shape[0] for axis in axis_arrays]) == ordinate_shape:

# Require that they are sorted
if all([is_increasing(axis) for axis in axis_arrays]):

return GridAbscissa(axis_data)

else:
raise InterpretationError("Grid axes are not sorted")

else:
raise InterpretationError(Abscissa._determine_error_message(axis_arrays, ordinate_shape))
Comment on lines +57 to +107

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ New issue: Complex Method
Abscissa.determine has a cyclomatic complexity of 24, threshold = 9

Suppress

Comment on lines +57 to +107

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ New issue: Bumpy Road Ahead
Abscissa.determine has 2 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is one single, nested block per function

Suppress

Comment on lines +57 to +107

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ New issue: Deep, Nested Complexity
Abscissa.determine has a nested complexity depth of 4, threshold = 4

Suppress


class GridAbscissa(Abscissa):

@property
def is_grid(self):
return True

class MeshgridAbscissa(Abscissa):

@property
def is_grid(self):
return True

class ScatterAbscissa(Abscissa):

@property
def is_grid(self):
return False

149 changes: 149 additions & 0 deletions sasdata/ascii_reader_metadata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import re
from dataclasses import dataclass, field
from typing import TypeVar

initial_metadata = {
'source': ['name', 'radiation', 'type', 'probe_particle', 'beam_size_name', 'beam_size', 'beam_shape', 'wavelength', 'wavelength_min', 'wavelength_max', 'wavelength_spread'],
'detector': ['name', 'distance', 'offset', 'orientation', 'beam_center', 'pixel_size', 'slit_length'],
'aperture': ['name', 'type', 'size_name', 'size', 'distance'],
'collimation': ['name', 'lengths'],
'process': ['name', 'date', 'description', 'term', 'notes'],
'sample': ['name', 'sample_id', 'thickness', 'transmission', 'temperature', 'position', 'orientation', 'details'],
'transmission_spectrum': ['name', 'timestamp', 'transmission', 'transmission_deviation'],
'magnetic': ['demagnetizing_field', 'saturation_magnetization', 'applied_magnetic_field', 'counting_index'],
'other': ['title', 'run', 'definition']
}

CASING_REGEX = r'[A-Z][a-z]*'

# First item has the highest precedence.
SEPARATOR_PRECEDENCE = [
'_',
'-',
]
# If none of these characters exist in that string, use casing. See init_separator

T = TypeVar('T')

# TODO: There may be a better place for this.
pairings = {'I': 'dI', 'Q': 'dQ', 'Qx': 'dQx', 'Qy': 'dQy'}
pairing_error = {value: key for key, value in pairings.items()}
# Allows this to be bidirectional.
bidirectional_pairings = pairings | pairing_error

@dataclass
class AsciiMetadataCategory[T]:
values: dict[str, T] = field(default_factory=dict)

def default_categories() -> dict[str, AsciiMetadataCategory[str | int]]:
return {key: AsciiMetadataCategory() for key in initial_metadata}

@dataclass
class AsciiReaderMetadata:
# Key is the filename.
filename_specific_metadata: dict[str, dict[str, AsciiMetadataCategory[str]]] = field(default_factory=dict)
# True instead of str means use the casing to separate the filename.
filename_separator: dict[str, str | bool] = field(default_factory=dict)
master_metadata: dict[str, AsciiMetadataCategory[int]] = field(default_factory=default_categories)

def init_separator(self, filename: str):
separator = next(filter(lambda c: c in SEPARATOR_PRECEDENCE, filename), True)
self.filename_separator[filename] = separator

def filename_components(self, filename: str, cut_off_extension: bool = True, capture: bool = False) -> list[str]:
"""Split the filename into several components based on the current separator for that file."""
separator = self.filename_separator[filename]
# FIXME: This sort of string construction may be an issue. Might need an alternative.
base_str = '({})' if capture else '{}'
if isinstance(separator, str):
splitted = re.split(base_str.replace('{}', separator), filename)
else:
splitted = re.findall(base_str.replace('{}', CASING_REGEX), filename)
# If the last component has a file extensions, remove it.
last_component = splitted[-1]
if cut_off_extension and '.' in last_component:
pos = last_component.index('.')
last_component = last_component[:pos]
splitted[-1] = last_component
return splitted

def purge_unreachable(self, filename: str):
"""This is used when the separator has changed. If lets say we now have 2 components when there were 5 but the
3rd component was selected, this will now produce an index out of range exception. Thus we'll need to purge this
to stop exceptions from happening."""
components = self.filename_components(filename)
component_length = len(components)
# Converting to list as this mutates the dictionary as it goes through it.
for category_name, category in list(self.master_metadata.items()):
for key, value in list(category.values.items()):
if value >= component_length:
del self.master_metadata[category_name].values[key]

def all_file_metadata(self, filename: str) -> dict[str, AsciiMetadataCategory[str]]:
"""Return all of the metadata for known for the specified filename. This
will combin the master metadata specified for all files with the
metadata specific to that filename."""
file_metadata = self.filename_specific_metadata[filename]
components = self.filename_components(filename)
# The ordering here is important. If there are conflicts, the second dictionary will override the first one.
# Conflicts shouldn't really be happening anyway but if they do, we're gonna go with the master metadata taking
# precedence for now.
return_metadata: dict[str, AsciiMetadataCategory[str]] = {}
for category_name, category in (file_metadata | self.master_metadata).items():
combined_category_dict = category.values | self.master_metadata[category_name].values
new_category_dict: dict[str, str] = {}
for key, value in combined_category_dict.items():
if isinstance(value, str):
new_category_dict[key] = value
elif isinstance(value, int):
new_category_dict[key] = components[value]
else:
raise TypeError(f'Invalid value for {key} in {category_name}')
new_category = AsciiMetadataCategory(new_category_dict)
return_metadata[category_name] = new_category
return return_metadata
def get_metadata(self, category: str, value: str, filename: str, error_on_not_found=False) -> str | None:
"""Get a particular piece of metadata for the filename."""
components = self.filename_components(filename)

# We prioritise the master metadata.

# TODO: Assumes category in master_metadata exists. Is this a reasonable assumption? May need to make sure it is
# definitely in the dictionary.
if value in self.master_metadata[category].values:
index = self.master_metadata[category].values[value]
return components[index]
target_category = self.filename_specific_metadata[filename][category].values
if value in target_category:
return target_category[value]
if error_on_not_found:
raise ValueError('value does not exist in metadata.')
else:
return None

def update_metadata(self, category: str, key: str, filename: str, new_value: str | int):
"""Update the metadata for a filename. If the new_value is a string,
then this new metadata will be specific to that file. Otherwise, if
new_value is an integer, then this will represent the component of the
filename that this metadata applies to all."""
if isinstance(new_value, str):
self.filename_specific_metadata[filename][category].values[key] = new_value
# TODO: What about the master metadata? Until that's gone, that still takes precedence.
elif isinstance(new_value, int):
self.master_metadata[category].values[key] = new_value
else:
raise TypeError('Invalid type for new_value')

def clear_metadata(self, category: str, key: str, filename: str):
"""Remove any metadata recorded for a certain filename."""
category_obj = self.filename_specific_metadata[filename][category]
if key in category_obj.values:
del category_obj.values[key]
if key in self.master_metadata[category].values:
del self.master_metadata[category].values[key]

def add_file(self, new_filename: str):
"""Add a filename to the metadata, filling it with some default
categories."""
# TODO: Fix typing here. Pyright is showing errors.
self.filename_specific_metadata[new_filename] = default_categories()
3 changes: 3 additions & 0 deletions sasdata/checklist.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Things to check once everything is in place:

1) Do any centigrade fields read in incorrectly?
Loading
Loading