atomscale 0.8.0__tar.gz → 0.8.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.
Files changed (113) hide show
  1. {atomscale-0.8.0 → atomscale-0.8.2}/.github/workflows/release.yml +10 -6
  2. {atomscale-0.8.0 → atomscale-0.8.2}/.github/workflows/testing.yml +21 -22
  3. {atomscale-0.8.0 → atomscale-0.8.2}/.pre-commit-config.yaml +15 -14
  4. {atomscale-0.8.0/src/atomscale.egg-info → atomscale-0.8.2}/PKG-INFO +4 -3
  5. {atomscale-0.8.0 → atomscale-0.8.2}/pyproject.toml +11 -29
  6. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale/core/client.py +3 -3
  7. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale/results/__init__.py +3 -1
  8. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale/results/rheed_image.py +1 -3
  9. atomscale-0.8.2/src/atomscale/results/similarity_trajectory.py +34 -0
  10. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale/timeseries/__init__.py +2 -0
  11. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale/timeseries/align.py +24 -24
  12. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale/timeseries/registry.py +2 -0
  13. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale/timeseries/rheed.py +6 -1
  14. atomscale-0.8.2/src/atomscale/timeseries/similarity.py +113 -0
  15. {atomscale-0.8.0 → atomscale-0.8.2/src/atomscale.egg-info}/PKG-INFO +4 -3
  16. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale.egg-info/SOURCES.txt +3 -0
  17. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale.egg-info/requires.txt +3 -2
  18. {atomscale-0.8.0 → atomscale-0.8.2}/tests/conftest.py +2 -0
  19. {atomscale-0.8.0 → atomscale-0.8.2}/tests/test_rheed_video.py +5 -1
  20. atomscale-0.8.2/tests/test_similarity_trajectory.py +97 -0
  21. {atomscale-0.8.0 → atomscale-0.8.2}/.github/workflows/upgrade_dependencies.yml +0 -0
  22. {atomscale-0.8.0 → atomscale-0.8.2}/.gitignore +0 -0
  23. {atomscale-0.8.0 → atomscale-0.8.2}/CHANGELOG.md +0 -0
  24. {atomscale-0.8.0 → atomscale-0.8.2}/LICENSE +0 -0
  25. {atomscale-0.8.0 → atomscale-0.8.2}/MANIFEST.in +0 -0
  26. {atomscale-0.8.0 → atomscale-0.8.2}/README.md +0 -0
  27. {atomscale-0.8.0 → atomscale-0.8.2}/atomicds-shim-dist/pyproject.toml +0 -0
  28. {atomscale-0.8.0 → atomscale-0.8.2}/docs/Makefile +0 -0
  29. {atomscale-0.8.0 → atomscale-0.8.2}/docs/_templates/custom-class-template.rst +0 -0
  30. {atomscale-0.8.0 → atomscale-0.8.2}/docs/_templates/custom-module-template.rst +0 -0
  31. {atomscale-0.8.0 → atomscale-0.8.2}/docs/conf.py +0 -0
  32. {atomscale-0.8.0 → atomscale-0.8.2}/docs/guides/index.rst +0 -0
  33. {atomscale-0.8.0 → atomscale-0.8.2}/docs/guides/inspect-results.rst +0 -0
  34. {atomscale-0.8.0 → atomscale-0.8.2}/docs/guides/poll-timeseries.rst +0 -0
  35. {atomscale-0.8.0 → atomscale-0.8.2}/docs/guides/quickstart.rst +0 -0
  36. {atomscale-0.8.0 → atomscale-0.8.2}/docs/guides/search-data.rst +0 -0
  37. {atomscale-0.8.0 → atomscale-0.8.2}/docs/guides/stream-rheed.rst +0 -0
  38. {atomscale-0.8.0 → atomscale-0.8.2}/docs/guides/upload-data.rst +0 -0
  39. {atomscale-0.8.0 → atomscale-0.8.2}/docs/index.rst +0 -0
  40. {atomscale-0.8.0 → atomscale-0.8.2}/docs/make.bat +0 -0
  41. {atomscale-0.8.0 → atomscale-0.8.2}/docs/modules.rst +0 -0
  42. {atomscale-0.8.0 → atomscale-0.8.2}/examples/general_use.ipynb +0 -0
  43. {atomscale-0.8.0 → atomscale-0.8.2}/examples/rheed_streaming.ipynb +0 -0
  44. {atomscale-0.8.0 → atomscale-0.8.2}/examples/timeseries_polling.ipynb +0 -0
  45. {atomscale-0.8.0 → atomscale-0.8.2}/examples/vxwse2-placeholder/task1_films.ipynb +0 -0
  46. {atomscale-0.8.0 → atomscale-0.8.2}/examples/vxwse2-placeholder/task1_sapphire.ipynb +0 -0
  47. {atomscale-0.8.0 → atomscale-0.8.2}/examples/vxwse2-placeholder/task2_composition.ipynb +0 -0
  48. {atomscale-0.8.0 → atomscale-0.8.2}/requirements/requirements-macos-latest_py3.10.txt +0 -0
  49. {atomscale-0.8.0 → atomscale-0.8.2}/requirements/requirements-macos-latest_py3.10_extras.txt +0 -0
  50. {atomscale-0.8.0 → atomscale-0.8.2}/requirements/requirements-macos-latest_py3.11.txt +0 -0
  51. {atomscale-0.8.0 → atomscale-0.8.2}/requirements/requirements-macos-latest_py3.11_extras.txt +0 -0
  52. {atomscale-0.8.0 → atomscale-0.8.2}/requirements/requirements-macos-latest_py3.12.txt +0 -0
  53. {atomscale-0.8.0 → atomscale-0.8.2}/requirements/requirements-macos-latest_py3.12_extras.txt +0 -0
  54. {atomscale-0.8.0 → atomscale-0.8.2}/requirements/requirements-macos-latest_py3.9.txt +0 -0
  55. {atomscale-0.8.0 → atomscale-0.8.2}/requirements/requirements-macos-latest_py3.9_extras.txt +0 -0
  56. {atomscale-0.8.0 → atomscale-0.8.2}/requirements/requirements-ubuntu-latest_py3.10.txt +0 -0
  57. {atomscale-0.8.0 → atomscale-0.8.2}/requirements/requirements-ubuntu-latest_py3.10_extras.txt +0 -0
  58. {atomscale-0.8.0 → atomscale-0.8.2}/requirements/requirements-ubuntu-latest_py3.11.txt +0 -0
  59. {atomscale-0.8.0 → atomscale-0.8.2}/requirements/requirements-ubuntu-latest_py3.11_extras.txt +0 -0
  60. {atomscale-0.8.0 → atomscale-0.8.2}/requirements/requirements-ubuntu-latest_py3.12.txt +0 -0
  61. {atomscale-0.8.0 → atomscale-0.8.2}/requirements/requirements-ubuntu-latest_py3.12_extras.txt +0 -0
  62. {atomscale-0.8.0 → atomscale-0.8.2}/requirements/requirements-ubuntu-latest_py3.9.txt +0 -0
  63. {atomscale-0.8.0 → atomscale-0.8.2}/requirements/requirements-ubuntu-latest_py3.9_extras.txt +0 -0
  64. {atomscale-0.8.0 → atomscale-0.8.2}/requirements/requirements-windows-latest_py3.10.txt +0 -0
  65. {atomscale-0.8.0 → atomscale-0.8.2}/requirements/requirements-windows-latest_py3.10_extras.txt +0 -0
  66. {atomscale-0.8.0 → atomscale-0.8.2}/requirements/requirements-windows-latest_py3.11.txt +0 -0
  67. {atomscale-0.8.0 → atomscale-0.8.2}/requirements/requirements-windows-latest_py3.11_extras.txt +0 -0
  68. {atomscale-0.8.0 → atomscale-0.8.2}/requirements/requirements-windows-latest_py3.12.txt +0 -0
  69. {atomscale-0.8.0 → atomscale-0.8.2}/requirements/requirements-windows-latest_py3.12_extras.txt +0 -0
  70. {atomscale-0.8.0 → atomscale-0.8.2}/requirements/requirements-windows-latest_py3.9.txt +0 -0
  71. {atomscale-0.8.0 → atomscale-0.8.2}/requirements/requirements-windows-latest_py3.9_extras.txt +0 -0
  72. {atomscale-0.8.0 → atomscale-0.8.2}/setup.cfg +0 -0
  73. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomicds/__init__.py +0 -0
  74. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale/__init__.py +0 -0
  75. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale/client.py +0 -0
  76. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale/core/__init__.py +0 -0
  77. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale/core/files.py +0 -0
  78. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale/core/utils.py +0 -0
  79. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale/results/group.py +0 -0
  80. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale/results/metrology.py +0 -0
  81. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale/results/optical.py +0 -0
  82. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale/results/photoluminescence.py +0 -0
  83. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale/results/raman.py +0 -0
  84. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale/results/rheed_video.py +0 -0
  85. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale/results/unknown.py +0 -0
  86. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale/results/xps.py +0 -0
  87. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale/streaming/Cargo.lock +0 -0
  88. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale/streaming/Cargo.toml +0 -0
  89. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale/streaming/__init__.py +0 -0
  90. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale/streaming/rheed_stream.pyi +0 -0
  91. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale/streaming/src/initialize.rs +0 -0
  92. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale/streaming/src/lib.rs +0 -0
  93. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale/streaming/src/upload.rs +0 -0
  94. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale/streaming/src/utils.rs +0 -0
  95. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale/timeseries/metrology.py +0 -0
  96. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale/timeseries/optical.py +0 -0
  97. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale/timeseries/polling.py +0 -0
  98. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale/timeseries/provider.py +0 -0
  99. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale/timeseries/sample.py +0 -0
  100. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale.egg-info/dependency_links.txt +0 -0
  101. {atomscale-0.8.0 → atomscale-0.8.2}/src/atomscale.egg-info/top_level.txt +0 -0
  102. {atomscale-0.8.0 → atomscale-0.8.2}/tests/__init__.py +0 -0
  103. {atomscale-0.8.0 → atomscale-0.8.2}/tests/data/test_rheed.mp4 +0 -0
  104. {atomscale-0.8.0 → atomscale-0.8.2}/tests/test_atomicds_alias.py +0 -0
  105. {atomscale-0.8.0 → atomscale-0.8.2}/tests/test_client.py +0 -0
  106. {atomscale-0.8.0 → atomscale-0.8.2}/tests/test_core.py +0 -0
  107. {atomscale-0.8.0 → atomscale-0.8.2}/tests/test_metrology.py +0 -0
  108. {atomscale-0.8.0 → atomscale-0.8.2}/tests/test_optical.py +0 -0
  109. {atomscale-0.8.0 → atomscale-0.8.2}/tests/test_photoluminescence.py +0 -0
  110. {atomscale-0.8.0 → atomscale-0.8.2}/tests/test_polling.py +0 -0
  111. {atomscale-0.8.0 → atomscale-0.8.2}/tests/test_raman.py +0 -0
  112. {atomscale-0.8.0 → atomscale-0.8.2}/tests/test_rheed_image.py +0 -0
  113. {atomscale-0.8.0 → atomscale-0.8.2}/tests/test_xps.py +0 -0
@@ -139,17 +139,21 @@ jobs:
139
139
  steps:
140
140
  - uses: actions/checkout@v4
141
141
 
142
- - uses: actions/setup-python@v5
142
+ - name: Set up uv
143
+ uses: astral-sh/setup-uv@v5
144
+ with:
145
+ version: "latest"
146
+
147
+ - name: Set up Python
148
+ uses: actions/setup-python@v5
143
149
  with:
144
150
  python-version: "3.11"
145
151
 
146
152
  - name: Install dependencies
147
153
  run: |
148
- python -m pip install --upgrade pip
149
- pip install -r requirements/requirements-ubuntu-latest_py3.11.txt
150
- pip install -r requirements/requirements-ubuntu-latest_py3.11_extras.txt
151
- pip install sphinx
152
- pip install -e .
154
+ uv pip install --system -r requirements/requirements-ubuntu-latest_py3.11.txt
155
+ uv pip install --system -r requirements/requirements-ubuntu-latest_py3.11_extras.txt
156
+ uv pip install --system -e .
153
157
 
154
158
  - name: Build docs
155
159
  run: sphinx-build ./docs ./docs/_build
@@ -17,30 +17,24 @@ jobs:
17
17
  steps:
18
18
  - uses: actions/checkout@v4
19
19
 
20
+ - name: Set up uv
21
+ uses: astral-sh/setup-uv@v5
22
+ with:
23
+ version: "latest"
24
+
20
25
  - name: Set up Python
21
26
  uses: actions/setup-python@v5
22
27
  with:
23
28
  python-version: "3.11"
24
29
 
25
- - name: Install Python dependencies
26
- run: |
27
- python -m pip install --upgrade pip
28
- pip install -r requirements/requirements-ubuntu-latest_py3.11.txt
29
- pip install -r requirements/requirements-ubuntu-latest_py3.11_extras.txt
30
-
31
30
  - name: Install dependencies
32
31
  run: |
33
- pip install pre-commit
32
+ uv pip install --system -r requirements/requirements-ubuntu-latest_py3.11.txt
33
+ uv pip install --system -r requirements/requirements-ubuntu-latest_py3.11_extras.txt
34
+ uv pip install --system pre-commit ty
34
35
 
35
36
  - name: Run pre-commit
36
- run: |
37
- pre-commit run --all-files --show-diff-on-failure
38
-
39
- - name: Run mypy
40
- run: |
41
- mypy --version
42
- rm -rf .mypy_cache
43
- mypy src/atomicds
37
+ run: pre-commit run --all-files --show-diff-on-failure
44
38
 
45
39
  test:
46
40
  needs: lint
@@ -55,16 +49,20 @@ jobs:
55
49
  steps:
56
50
  - uses: actions/checkout@v4
57
51
 
52
+ - name: Set up uv
53
+ uses: astral-sh/setup-uv@v5
54
+ with:
55
+ version: "latest"
56
+
58
57
  - name: Set up Python ${{ matrix.python-version }}
59
58
  uses: actions/setup-python@v5
60
59
  with:
61
60
  python-version: ${{ matrix.python-version }}
62
61
 
63
- - name: Install Python dependencies
62
+ - name: Install dependencies
64
63
  run: |
65
- python -m pip install --upgrade pip
66
- pip install -r requirements/requirements-${{ matrix.os }}_py${{ matrix.python-version }}.txt
67
- pip install -r requirements/requirements-${{ matrix.os }}_py${{ matrix.python-version }}_extras.txt
64
+ uv pip install --system -r requirements/requirements-${{ matrix.os }}_py${{ matrix.python-version }}.txt
65
+ uv pip install --system -r requirements/requirements-${{ matrix.os }}_py${{ matrix.python-version }}_extras.txt
68
66
 
69
67
  - name: Set SSL_CERT_FILE (Linux/MacOS)
70
68
  if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest'
@@ -74,13 +72,14 @@ jobs:
74
72
  if: matrix.os == 'windows-latest'
75
73
  run: echo "SSL_CERT_FILE=$(python -m certifi)" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
76
74
 
75
+ - name: Install package
76
+ run: uv pip install --system -e .
77
+
77
78
  - name: Test with pytest
78
79
  env:
79
80
  AS_API_KEY: ${{ secrets.AS_API_KEY }}
80
81
  AS_API_ENDPOINT: "https://api.atomscale.ai"
81
- run: |
82
- pip install -e .
83
- pytest -x
82
+ run: pytest -x
84
83
 
85
84
  auto-gen-release:
86
85
  needs: test
@@ -5,19 +5,8 @@ ci:
5
5
  autoupdate_commit_msg: "chore: update pre-commit hooks"
6
6
 
7
7
  repos:
8
- - repo: https://github.com/psf/black
9
- rev: 23.9.1
10
- hooks:
11
- - id: black
12
-
13
- - repo: https://github.com/asottile/blacken-docs
14
- rev: "1.16.0"
15
- hooks:
16
- - id: blacken-docs
17
- additional_dependencies: [black>=23.7.0]
18
-
19
8
  - repo: https://github.com/pre-commit/pre-commit-hooks
20
- rev: v4.5.0
9
+ rev: v5.0.0
21
10
  hooks:
22
11
  - id: check-case-conflict
23
12
  - id: check-symlinks
@@ -28,15 +17,27 @@ repos:
28
17
  - id: trailing-whitespace
29
18
 
30
19
  - repo: https://github.com/astral-sh/ruff-pre-commit
31
- rev: v0.3.4
20
+ rev: v0.9.4
32
21
  hooks:
33
22
  - id: ruff
34
23
  args: [--fix, --show-fixes]
24
+ exclude: '\.ipynb$'
25
+ - id: ruff-format
26
+ exclude: '\.ipynb$'
35
27
 
36
28
  - repo: https://github.com/codespell-project/codespell
37
- rev: v2.2.6
29
+ rev: v2.4.1
38
30
  hooks:
39
31
  - id: codespell
40
32
  stages: [pre-commit, commit-msg]
41
33
  exclude_types: [json, bib, svg]
42
34
  args: [--ignore-words-list, "mater,fwe,te"]
35
+
36
+ - repo: local
37
+ hooks:
38
+ - id: ty
39
+ name: ty
40
+ entry: ty check src/atomicds
41
+ language: system
42
+ types: [python]
43
+ pass_filenames: false
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: atomscale
3
- Version: 0.8.0
3
+ Version: 0.8.2
4
4
  Summary: Python SDK for Atomscale.
5
5
  Author-email: Atomscale <info@atomscale.ai>
6
6
  License: GPL-3.0-only
@@ -36,10 +36,11 @@ Requires-Dist: pytest-cov; extra == "dev"
36
36
  Requires-Dist: pytest-order; extra == "dev"
37
37
  Requires-Dist: pytest-dependency; extra == "dev"
38
38
  Requires-Dist: pytest-asyncio; extra == "dev"
39
- Requires-Dist: ruff; extra == "dev"
40
- Requires-Dist: mypy; extra == "dev"
39
+ Requires-Dist: ruff>=0.9.0; extra == "dev"
40
+ Requires-Dist: ty; extra == "dev"
41
41
  Requires-Dist: types-requests; extra == "dev"
42
42
  Requires-Dist: pandas-stubs; extra == "dev"
43
+ Requires-Dist: pre-commit>=4.0.0; extra == "dev"
43
44
  Provides-Extra: docs
44
45
  Requires-Dist: sphinx>=6.2; extra == "docs"
45
46
  Requires-Dist: sphinx-autodoc-typehints>=1.24; extra == "docs"
@@ -49,10 +49,11 @@ dev = [
49
49
  "pytest-order",
50
50
  "pytest-dependency",
51
51
  "pytest-asyncio",
52
- "ruff",
53
- "mypy",
52
+ "ruff>=0.9.0",
53
+ "ty",
54
54
  "types-requests",
55
55
  "pandas-stubs",
56
+ "pre-commit>=4.0.0",
56
57
  ]
57
58
  docs = ["sphinx>=6.2", "sphinx-autodoc-typehints>=1.24", "alabaster>=0.7"]
58
59
 
@@ -73,10 +74,6 @@ path = "src/atomscale/streaming/Cargo.toml"
73
74
  binding = "PyO3"
74
75
 
75
76
 
76
- [tool.pyright]
77
- include = ["atomscale", "atomicds"]
78
- exclude = ["**/__pycache__"]
79
-
80
77
  [tool.pytest.ini_options]
81
78
  minversion = "6.0"
82
79
  addopts = ["-p no:warnings", "--import-mode=importlib"]
@@ -84,18 +81,6 @@ xfail_strict = true
84
81
  log_cli_level = "warn"
85
82
  testpaths = ["tests"]
86
83
 
87
- [tool.black]
88
- exclude = '''
89
- /(
90
- \.git
91
- | \.tox
92
- )/
93
- '''
94
-
95
- [tool.isort]
96
- profile = 'black'
97
- skip_gitignore = true
98
-
99
84
  [tool.coverage.run]
100
85
  source = ["src"]
101
86
 
@@ -137,11 +122,9 @@ lint.select = [
137
122
  lint.extend-ignore = [
138
123
  "PLR", # Design related pylint codes
139
124
  "E501", # Line too long
140
- "PT004", # Use underscore for non-returning fixture (use usefixture instead)
141
125
  "B028", # No explicit stacklevel
142
126
  "EM101", # Exception must not use a string literal
143
127
  "EM102", # Exception must not use an f-string literal
144
- "PD901", # Avoid using the generic variable name `df` for DataFrames
145
128
  ]
146
129
  lint.typing-modules = ["mypackage._compat.typing"]
147
130
  src = ["src"]
@@ -151,20 +134,19 @@ lint.unfixable = [
151
134
  ]
152
135
  exclude = []
153
136
  lint.flake8-unused-arguments.ignore-variadic-names = true
154
- extend-exclude = ["tests"]
137
+ extend-exclude = ["tests", "examples", "*.ipynb"]
155
138
  lint.pydocstyle.convention = "google"
156
139
  lint.isort.known-first-party = ["atomscale"]
157
140
 
158
141
  [tool.docformatter]
159
142
  pre-summary-newline = true
160
- black = true
161
-
162
- [tool.mypy]
163
- ignore_missing_imports = true
164
- namespace_packages = true
165
- explicit_package_bases = true
166
- no_implicit_optional = false
167
- disable_error_code = "annotation-unchecked"
143
+
144
+ [tool.ty.environment]
145
+ root = ["./src"]
146
+
147
+ [tool.ty.src]
148
+ include = ["src/atomicds"]
149
+ exclude = ["tests", "examples", "docs"]
168
150
 
169
151
  [tool.codespell]
170
152
  ignore-words-list = "fpr"
@@ -245,9 +245,9 @@ class BaseClient:
245
245
  atomscale_info = "atomscale/" + __version__
246
246
  python_info = f"Python/{sys.version.split()[0]}"
247
247
  platform_info = f"{platform.system()}/{platform.release()}"
248
- session.headers[
249
- "user-agent"
250
- ] = f"{atomscale_info} ({python_info} {platform_info})"
248
+ session.headers["user-agent"] = (
249
+ f"{atomscale_info} ({python_info} {platform_info})"
250
+ )
251
251
 
252
252
  # TODO: Add retry setting to configuration somewhere
253
253
  max_retry_num = 3
@@ -5,6 +5,7 @@ from .photoluminescence import PhotoluminescenceResult
5
5
  from .raman import RamanResult
6
6
  from .rheed_image import RHEEDImageCollection, RHEEDImageResult, _get_rheed_image_result
7
7
  from .rheed_video import RHEEDVideoResult
8
+ from .similarity_trajectory import SimilarityTrajectoryResult
8
9
  from .unknown import UnknownResult
9
10
  from .xps import XPSResult
10
11
 
@@ -18,7 +19,8 @@ __all__ = [
18
19
  "RHEEDImageResult",
19
20
  "RHEEDVideoResult",
20
21
  "RamanResult",
21
- "XPSResult",
22
+ "SimilarityTrajectoryResult",
22
23
  "UnknownResult",
24
+ "XPSResult",
23
25
  "_get_rheed_image_result",
24
26
  ]
@@ -371,9 +371,7 @@ class RHEEDImageResult(MSONable):
371
371
  x,
372
372
  new_df["mask_height"].iloc[0], # type: ignore # noqa: PGH003
373
373
  new_df["mask_width"].iloc[0], # type: ignore # noqa: PGH003
374
- )[
375
- "counts"
376
- ]
374
+ )["counts"]
377
375
 
378
376
  new_df = new_df.groupby("node_id").agg(agg_dict).reset_index(drop=True)
379
377
 
@@ -0,0 +1,34 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Sequence
4
+ from uuid import UUID
5
+
6
+ from monty.json import MSONable
7
+ from pandas import DataFrame
8
+
9
+
10
+ class SimilarityTrajectoryResult(MSONable):
11
+ def __init__(
12
+ self,
13
+ source_id: UUID | str,
14
+ workflow: str,
15
+ window_span: float,
16
+ timeseries_data: DataFrame,
17
+ source_data_ids: Sequence[UUID | str] | None = None,
18
+ ):
19
+ """Similarity trajectory result
20
+
21
+ Args:
22
+ source_id (UUID | str): Source ID for the similarity trajectory query.
23
+ workflow (str): Workflow name used for the similarity analysis.
24
+ window_span (float): Window span parameter used for the trajectory.
25
+ timeseries_data (DataFrame): Pandas DataFrame with similarity trajectory data.
26
+ source_data_ids (Sequence[UUID | str] | None): Sequence of source data IDs included in the trajectory.
27
+ """
28
+ self.source_id = source_id
29
+ self.workflow = workflow
30
+ self.window_span = window_span
31
+ self.timeseries_data = timeseries_data
32
+ self.source_data_ids: list[UUID | str] = (
33
+ list(source_data_ids) if source_data_ids else []
34
+ )
@@ -4,11 +4,13 @@ from .optical import OpticalProvider
4
4
  from .provider import TimeseriesProvider
5
5
  from .registry import get_provider
6
6
  from .rheed import RHEEDProvider
7
+ from .similarity import SimilarityTrajectoryProvider
7
8
 
8
9
  __all__ = [
9
10
  "MetrologyProvider",
10
11
  "OpticalProvider",
11
12
  "RHEEDProvider",
13
+ "SimilarityTrajectoryProvider",
12
14
  "TimeseriesProvider",
13
15
  "align_timeseries",
14
16
  "get_provider",
@@ -71,26 +71,26 @@ def _extract_timeseries(result):
71
71
  """Return (data_id, domain, df_with_timeindex) or None for non-timeseries."""
72
72
  if isinstance(result, RHEEDVideoResult):
73
73
  domain = "rheed"
74
- df = result.timeseries_data
74
+ timeseries = result.timeseries_data
75
75
  elif isinstance(result, OpticalResult):
76
76
  domain = "optical"
77
- df = result.timeseries_data
77
+ timeseries = result.timeseries_data
78
78
  elif isinstance(result, MetrologyResult):
79
79
  domain = "metrology"
80
- df = result.timeseries_data
80
+ timeseries = result.timeseries_data
81
81
  else:
82
82
  return None
83
83
 
84
- if df is None or df.empty:
84
+ if timeseries is None or timeseries.empty:
85
85
  return None
86
86
 
87
87
  # Build time index: prefer absolute epochs; fall back to upload_datetime + relative offsets.
88
88
  upload_dt = getattr(result, "upload_datetime", None)
89
89
 
90
- time_index = _infer_absolute_time(df)
90
+ time_index = _infer_absolute_time(timeseries)
91
91
  if time_index is None and upload_dt is not None:
92
92
  base = pd.to_datetime(upload_dt, utc=True, errors="coerce")
93
- rel = _infer_relative_time(df)
93
+ rel = _infer_relative_time(timeseries)
94
94
  if base is not pd.NaT and rel is not None:
95
95
  time_index = base + rel
96
96
 
@@ -101,7 +101,7 @@ def _extract_timeseries(result):
101
101
  if not valid_mask.any():
102
102
  return None
103
103
 
104
- indexed = df.loc[valid_mask].copy(deep=False)
104
+ indexed = timeseries.loc[valid_mask].copy(deep=False)
105
105
  indexed.index = pd.Index(time_index[valid_mask], name="time")
106
106
  indexed = indexed.sort_index()
107
107
 
@@ -173,11 +173,11 @@ def align_timeseries(
173
173
  if not extracted:
174
174
  continue
175
175
 
176
- data_id, domain, df = extracted
177
- df = df.copy(deep=False)
178
- df.columns = pd.MultiIndex.from_product([[data_id], [domain], df.columns])
179
- frames.append(df)
180
- indices.append(df.index)
176
+ data_id, domain, frame = extracted
177
+ frame = frame.copy(deep=False)
178
+ frame.columns = pd.MultiIndex.from_product([[data_id], [domain], frame.columns])
179
+ frames.append(frame)
180
+ indices.append(frame.index)
181
181
 
182
182
  if not frames:
183
183
  return pd.DataFrame()
@@ -211,11 +211,11 @@ def align_timeseries(
211
211
 
212
212
  # Merge compatible metrics across items: if multiple columns share (domain, metric)
213
213
  # and never conflict where they overlap, collapse into (shared, domain, metric).
214
- def _merge_compatible_metrics(df: pd.DataFrame) -> pd.DataFrame:
215
- if not isinstance(df.columns, pd.MultiIndex):
216
- return df
217
- domains = df.columns.get_level_values(1)
218
- metrics = df.columns.get_level_values(2)
214
+ def _merge_compatible_metrics(data: pd.DataFrame) -> pd.DataFrame:
215
+ if not isinstance(data.columns, pd.MultiIndex):
216
+ return data
217
+ domains = data.columns.get_level_values(1)
218
+ metrics = data.columns.get_level_values(2)
219
219
  new_cols: dict = {}
220
220
  drop_cols: list = []
221
221
 
@@ -223,16 +223,16 @@ def align_timeseries(
223
223
  for metric in metrics.unique():
224
224
  cols = [
225
225
  c
226
- for c in df.columns
226
+ for c in data.columns
227
227
  if c[1] == domain and c[2] == metric and c[0] != "shared"
228
228
  ]
229
229
  if len(cols) <= 1:
230
230
  continue
231
231
 
232
- merged = df[cols[0]]
232
+ merged = data[cols[0]]
233
233
  conflict = False
234
234
  for c in cols[1:]:
235
- other = df[c]
235
+ other = data[c]
236
236
  overlap_mask = merged.notna() & other.notna()
237
237
  if (merged[overlap_mask] != other[overlap_mask]).any():
238
238
  conflict = True
@@ -247,10 +247,10 @@ def align_timeseries(
247
247
  drop_cols.extend(cols)
248
248
 
249
249
  if new_cols:
250
- df = df.drop(columns=drop_cols)
250
+ data = data.drop(columns=drop_cols)
251
251
  for col, series in new_cols.items():
252
- df[col] = series
253
- df = df.sort_index(axis=1)
254
- return df
252
+ data[col] = series
253
+ data = data.sort_index(axis=1)
254
+ return data
255
255
 
256
256
  return _merge_compatible_metrics(aligned)
@@ -4,11 +4,13 @@ from .metrology import MetrologyProvider
4
4
  from .optical import OpticalProvider
5
5
  from .provider import TimeseriesProvider
6
6
  from .rheed import RHEEDProvider
7
+ from .similarity import SimilarityTrajectoryProvider
7
8
 
8
9
  _PROVIDER_CLASSES: dict[str, type[TimeseriesProvider]] = {
9
10
  RHEEDProvider.TYPE: RHEEDProvider,
10
11
  OpticalProvider.TYPE: OpticalProvider,
11
12
  MetrologyProvider.TYPE: MetrologyProvider,
13
+ SimilarityTrajectoryProvider.TYPE: SimilarityTrajectoryProvider,
12
14
  }
13
15
 
14
16
 
@@ -41,8 +41,13 @@ class RHEEDProvider(TimeseriesProvider[RHEEDVideoResult]):
41
41
  "first_order_fwhm_1": "First Order FWHM",
42
42
  "lattice_spacing": "Lattice Spacing",
43
43
  "tar_metric": "TAR Metric",
44
+ "composition_metric": "Composition Metric",
44
45
  }
45
- DROP_IF_ALL_NA: Sequence[str] = ["reconstruction_intensity", "tar_metric"]
46
+ DROP_IF_ALL_NA: Sequence[str] = [
47
+ "reconstruction_intensity",
48
+ "tar_metric",
49
+ "composition_metric",
50
+ ]
46
51
  INDEX_COLS: Sequence[str] = ["Angle", "Frame Number"]
47
52
 
48
53
  def fetch_raw(self, client: BaseClient, data_id: str, **kwargs) -> Any:
@@ -0,0 +1,113 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping, Sequence
4
+ from typing import Any
5
+ from uuid import UUID
6
+
7
+ from pandas import DataFrame, concat
8
+
9
+ from atomscale.core import BaseClient
10
+ from atomscale.results.similarity_trajectory import SimilarityTrajectoryResult
11
+ from atomscale.timeseries.provider import TimeseriesProvider
12
+
13
+
14
+ class SimilarityTrajectoryProvider(TimeseriesProvider[SimilarityTrajectoryResult]):
15
+ TYPE = "similarity_trajectory"
16
+
17
+ RENAME_MAP: Mapping[str, str] = {
18
+ "reference_id": "Reference ID",
19
+ "reference_item_name": "Reference Name",
20
+ "real_time_seconds": "Time",
21
+ "similarity_values": "Similarity",
22
+ "unix_times": "UNIX Timestamp",
23
+ "is_active": "Active",
24
+ "averaged_count": "Averaged Count",
25
+ }
26
+ INDEX_COLS: Sequence[str] = ["Reference ID", "Time"]
27
+
28
+ def fetch_raw(self, client: BaseClient, data_id: str, **kwargs: Any) -> Any:
29
+ """Fetch similarity trajectory data from the API.
30
+
31
+ Args:
32
+ client: The API client.
33
+ data_id: The source ID for the similarity query.
34
+ **kwargs: Must include 'workflow' (required). Optional parameters:
35
+ window_span, reference_ids, softmax_mode, reference_n_values.
36
+
37
+ Returns:
38
+ Raw API response payload.
39
+
40
+ Raises:
41
+ KeyError: If 'workflow' is not provided in kwargs.
42
+ """
43
+ workflow = kwargs.pop("workflow")
44
+ return client._get(
45
+ sub_url=f"similarity/{workflow}/{data_id}/trajectory/",
46
+ params=kwargs,
47
+ )
48
+
49
+ def to_dataframe(self, raw: Any) -> DataFrame:
50
+ if not raw:
51
+ return DataFrame(None)
52
+
53
+ trajectories = raw.get("trajectories", [])
54
+ if not trajectories:
55
+ return DataFrame(None)
56
+
57
+ frames: list[DataFrame] = []
58
+ for traj in trajectories:
59
+ ref_id = traj.get("reference_id")
60
+ ref_name = traj.get("reference_item_name")
61
+ similarity_values = traj.get("similarity_values", [])
62
+ real_time_seconds = traj.get("real_time_seconds", [])
63
+ unix_times = traj.get("unix_times", [])
64
+ is_active = traj.get("is_active")
65
+ averaged_count = traj.get("averaged_count")
66
+
67
+ if not similarity_values:
68
+ continue
69
+
70
+ # Build dataframe from columnar data
71
+ traj_df = DataFrame(
72
+ {
73
+ "reference_id": ref_id,
74
+ "reference_item_name": ref_name,
75
+ "similarity_values": similarity_values,
76
+ "real_time_seconds": real_time_seconds,
77
+ "unix_times": unix_times,
78
+ "is_active": is_active,
79
+ "averaged_count": averaged_count,
80
+ }
81
+ )
82
+ frames.append(traj_df)
83
+
84
+ if not frames:
85
+ return DataFrame(None)
86
+
87
+ df_all = concat(frames, axis=0, ignore_index=True)
88
+ df_all = df_all.rename(columns=self.RENAME_MAP)
89
+
90
+ idx_cols = [c for c in self.INDEX_COLS if c in df_all.columns]
91
+ if idx_cols:
92
+ df_all = df_all.set_index(idx_cols)
93
+
94
+ return df_all
95
+
96
+ def build_result(
97
+ self,
98
+ client: BaseClient, # noqa: ARG002
99
+ data_id: str,
100
+ data_type: str, # noqa: ARG002
101
+ ts_df: DataFrame,
102
+ *,
103
+ workflow: str = "",
104
+ window_span: float = 0.0,
105
+ source_data_ids: Sequence[UUID | str] | None = None,
106
+ ) -> SimilarityTrajectoryResult:
107
+ return SimilarityTrajectoryResult(
108
+ source_id=data_id,
109
+ workflow=workflow,
110
+ window_span=window_span,
111
+ timeseries_data=ts_df,
112
+ source_data_ids=source_data_ids,
113
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: atomscale
3
- Version: 0.8.0
3
+ Version: 0.8.2
4
4
  Summary: Python SDK for Atomscale.
5
5
  Author-email: Atomscale <info@atomscale.ai>
6
6
  License: GPL-3.0-only
@@ -36,10 +36,11 @@ Requires-Dist: pytest-cov; extra == "dev"
36
36
  Requires-Dist: pytest-order; extra == "dev"
37
37
  Requires-Dist: pytest-dependency; extra == "dev"
38
38
  Requires-Dist: pytest-asyncio; extra == "dev"
39
- Requires-Dist: ruff; extra == "dev"
40
- Requires-Dist: mypy; extra == "dev"
39
+ Requires-Dist: ruff>=0.9.0; extra == "dev"
40
+ Requires-Dist: ty; extra == "dev"
41
41
  Requires-Dist: types-requests; extra == "dev"
42
42
  Requires-Dist: pandas-stubs; extra == "dev"
43
+ Requires-Dist: pre-commit>=4.0.0; extra == "dev"
43
44
  Provides-Extra: docs
44
45
  Requires-Dist: sphinx>=6.2; extra == "docs"
45
46
  Requires-Dist: sphinx-autodoc-typehints>=1.24; extra == "docs"
@@ -73,6 +73,7 @@ src/atomscale/results/photoluminescence.py
73
73
  src/atomscale/results/raman.py
74
74
  src/atomscale/results/rheed_image.py
75
75
  src/atomscale/results/rheed_video.py
76
+ src/atomscale/results/similarity_trajectory.py
76
77
  src/atomscale/results/unknown.py
77
78
  src/atomscale/results/xps.py
78
79
  src/atomscale/streaming/Cargo.lock
@@ -92,6 +93,7 @@ src/atomscale/timeseries/provider.py
92
93
  src/atomscale/timeseries/registry.py
93
94
  src/atomscale/timeseries/rheed.py
94
95
  src/atomscale/timeseries/sample.py
96
+ src/atomscale/timeseries/similarity.py
95
97
  tests/__init__.py
96
98
  tests/conftest.py
97
99
  tests/test_atomicds_alias.py
@@ -104,5 +106,6 @@ tests/test_polling.py
104
106
  tests/test_raman.py
105
107
  tests/test_rheed_image.py
106
108
  tests/test_rheed_video.py
109
+ tests/test_similarity_trajectory.py
107
110
  tests/test_xps.py
108
111
  tests/data/test_rheed.mp4
@@ -16,10 +16,11 @@ pytest-cov
16
16
  pytest-order
17
17
  pytest-dependency
18
18
  pytest-asyncio
19
- ruff
20
- mypy
19
+ ruff>=0.9.0
20
+ ty
21
21
  types-requests
22
22
  pandas-stubs
23
+ pre-commit>=4.0.0
23
24
 
24
25
  [docs]
25
26
  sphinx>=6.2
@@ -27,3 +27,5 @@ class ResultIDs:
27
27
  metrology = ""
28
28
  photoluminescence = ""
29
29
  raman = ""
30
+ similarity_workflow = "rheed_stationary"
31
+ similarity_source_id = "bb3494b1-b5fb-4f3e-ac50-e4024f8aacf5"
@@ -22,6 +22,7 @@ def result(client: Client):
22
22
 
23
23
 
24
24
  def test_get_dataframe(result: RHEEDVideoResult):
25
+ # Core columns that should always be present
25
26
  column_names = set(
26
27
  [
27
28
  "Strain",
@@ -38,9 +39,12 @@ def test_get_dataframe(result: RHEEDVideoResult):
38
39
  "Half Order Intensity R",
39
40
  "Specular FWHM",
40
41
  "First Order FWHM",
41
- "Time",
42
42
  "UNIX Timestamp",
43
43
  "Relative Time",
44
+ # Optional columns (included if data exists)
45
+ "Time",
46
+ "TAR Metric",
47
+ "Composition Metric",
44
48
  ]
45
49
  )
46
50
 
@@ -0,0 +1,97 @@
1
+ import pytest
2
+ from pandas import DataFrame
3
+
4
+ from atomscale import Client
5
+ from atomscale.results import SimilarityTrajectoryResult
6
+ from atomscale.timeseries.similarity import SimilarityTrajectoryProvider
7
+
8
+ from .conftest import ResultIDs
9
+
10
+
11
+ @pytest.fixture
12
+ def client():
13
+ return Client()
14
+
15
+
16
+ @pytest.fixture
17
+ def provider():
18
+ return SimilarityTrajectoryProvider()
19
+
20
+
21
+ @pytest.fixture
22
+ def raw_data(client: Client, provider: SimilarityTrajectoryProvider):
23
+ if not ResultIDs.similarity_workflow or not ResultIDs.similarity_source_id:
24
+ pytest.skip("No similarity trajectory data available")
25
+
26
+ data = provider.fetch_raw(
27
+ client,
28
+ ResultIDs.similarity_source_id,
29
+ workflow=ResultIDs.similarity_workflow,
30
+ )
31
+
32
+ if not data or not data.get("trajectories"):
33
+ pytest.skip("No trajectory data returned from API")
34
+
35
+ return data
36
+
37
+
38
+ @pytest.fixture
39
+ def result(
40
+ client: Client, provider: SimilarityTrajectoryProvider, raw_data: dict
41
+ ) -> SimilarityTrajectoryResult:
42
+ df = provider.to_dataframe(raw_data)
43
+ return provider.build_result(
44
+ client=client,
45
+ data_id=ResultIDs.similarity_source_id,
46
+ data_type="similarity_trajectory",
47
+ ts_df=df,
48
+ workflow=ResultIDs.similarity_workflow,
49
+ )
50
+
51
+
52
+ def test_type_constant():
53
+ """Verify TYPE constant is set correctly."""
54
+ assert SimilarityTrajectoryProvider.TYPE == "similarity_trajectory"
55
+
56
+
57
+ def test_fetch_raw(raw_data: dict):
58
+ """Verify raw data is fetched from API."""
59
+ assert raw_data is not None
60
+ assert "trajectories" in raw_data
61
+
62
+
63
+ def test_to_dataframe(provider: SimilarityTrajectoryProvider, raw_data: dict):
64
+ """Verify dataframe conversion."""
65
+ df = provider.to_dataframe(raw_data)
66
+
67
+ assert isinstance(df, DataFrame)
68
+ assert not df.empty
69
+
70
+
71
+ def test_to_dataframe_columns(provider: SimilarityTrajectoryProvider, raw_data: dict):
72
+ """Verify column names and index."""
73
+ df = provider.to_dataframe(raw_data)
74
+
75
+ # Check index names
76
+ assert df.index.names == ["Reference ID", "Time"]
77
+
78
+ # Check expected columns exist
79
+ expected_columns = {"Similarity", "Reference Name", "UNIX Timestamp", "Active", "Averaged Count"}
80
+ assert expected_columns == set(df.columns)
81
+
82
+
83
+ def test_build_result(result: SimilarityTrajectoryResult):
84
+ """Verify result object construction."""
85
+ assert isinstance(result, SimilarityTrajectoryResult)
86
+ assert result.source_id == ResultIDs.similarity_source_id
87
+ assert result.workflow == ResultIDs.similarity_workflow
88
+ assert isinstance(result.timeseries_data, DataFrame)
89
+
90
+
91
+ def test_result_dataframe(result: SimilarityTrajectoryResult):
92
+ """Verify result contains valid timeseries data."""
93
+ df = result.timeseries_data
94
+
95
+ assert isinstance(df, DataFrame)
96
+ if df.index.names != [None]:
97
+ assert df.index.names == ["Reference ID", "Time"]
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