Compare commits

..

18 Commits

Author SHA1 Message Date
9b5038d32a Try again
All checks were successful
Build Packages / build:rpm (rocky8_nocuda) (push) Has been skipped
Build Packages / build:rpm (rocky9_nocuda) (push) Has been skipped
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Has been skipped
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Has been skipped
Build Packages / build:rpm (rocky8_sls9) (push) Has been skipped
Build Packages / build:rpm (rocky8) (push) Has been skipped
Build Packages / build:rpm (rocky9) (push) Has been skipped
Build Packages / build:rpm (ubuntu2204) (push) Has been skipped
Build Packages / build:rpm (ubuntu2404) (push) Has been skipped
Build Packages / Unit tests (push) Has been skipped
Build Packages / Generate python client (push) Successful in 19s
Build Packages / Build documentation (push) Successful in 41s
Build Packages / Create release (push) Successful in 2m4s
2025-09-30 14:34:44 +02:00
27edace546 Try again
Some checks failed
Build Packages / build:rpm (rocky8_nocuda) (push) Has been skipped
Build Packages / build:rpm (rocky9_nocuda) (push) Has been skipped
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Has been skipped
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Has been skipped
Build Packages / build:rpm (rocky8_sls9) (push) Has been skipped
Build Packages / build:rpm (rocky8) (push) Has been skipped
Build Packages / build:rpm (rocky9) (push) Has been skipped
Build Packages / build:rpm (ubuntu2204) (push) Has been skipped
Build Packages / build:rpm (ubuntu2404) (push) Has been skipped
Build Packages / Unit tests (push) Has been skipped
Build Packages / Generate python client (push) Successful in 20s
Build Packages / Create release (push) Failing after 29s
Build Packages / Build documentation (push) Successful in 41s
2025-09-30 14:33:17 +02:00
e9d6d87fc6 Print release upload url
Some checks failed
Build Packages / build:rpm (rocky8_nocuda) (push) Has been skipped
Build Packages / build:rpm (rocky9_nocuda) (push) Has been skipped
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Has been skipped
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Has been skipped
Build Packages / build:rpm (rocky8_sls9) (push) Has been skipped
Build Packages / build:rpm (rocky8) (push) Has been skipped
Build Packages / build:rpm (rocky9) (push) Has been skipped
Build Packages / build:rpm (ubuntu2204) (push) Has been skipped
Build Packages / build:rpm (ubuntu2404) (push) Has been skipped
Build Packages / Unit tests (push) Has been skipped
Build Packages / Generate python client (push) Successful in 20s
Build Packages / Create release (push) Failing after 28s
Build Packages / Build documentation (push) Successful in 41s
2025-09-30 14:30:56 +02:00
17c1d5b9e9 SpotFindingSettings: Integration is off be default
Some checks failed
Build Packages / build:rpm (rocky8_nocuda) (push) Has been skipped
Build Packages / build:rpm (rocky9_nocuda) (push) Has been skipped
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Has been skipped
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Has been skipped
Build Packages / build:rpm (rocky8_sls9) (push) Has been skipped
Build Packages / build:rpm (rocky8) (push) Has been skipped
Build Packages / build:rpm (rocky9) (push) Has been skipped
Build Packages / build:rpm (ubuntu2204) (push) Has been skipped
Build Packages / build:rpm (ubuntu2404) (push) Has been skipped
Build Packages / Unit tests (push) Has been skipped
Build Packages / Generate python client (push) Successful in 20s
Build Packages / Create release (push) Failing after 29s
Build Packages / Build documentation (push) Successful in 41s
2025-09-30 14:29:02 +02:00
de9ed017a3 Retry release upload 2025-09-30 14:28:19 +02:00
4d8afe4799 Gitea: Try create release again
Some checks failed
Build Packages / build:rpm (rocky8_nocuda) (push) Has been skipped
Build Packages / build:rpm (rocky9_nocuda) (push) Has been skipped
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Has been skipped
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Has been skipped
Build Packages / build:rpm (rocky8_sls9) (push) Has been skipped
Build Packages / build:rpm (rocky8) (push) Has been skipped
Build Packages / build:rpm (rocky9) (push) Has been skipped
Build Packages / build:rpm (ubuntu2204) (push) Has been skipped
Build Packages / build:rpm (ubuntu2404) (push) Has been skipped
Build Packages / Unit tests (push) Has been skipped
Build Packages / Generate python client (push) Successful in 20s
Build Packages / Create release (push) Failing after 28s
Build Packages / Build documentation (push) Successful in 41s
2025-09-30 14:25:07 +02:00
4fc4e13aa1 Gitea: Try create release again
Some checks failed
Build Packages / build:rpm (rocky9_nocuda) (push) Has been skipped
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Has been skipped
Build Packages / build:rpm (rocky8_nocuda) (push) Has been skipped
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Has been skipped
Build Packages / build:rpm (rocky8_sls9) (push) Has been skipped
Build Packages / build:rpm (rocky8) (push) Has been skipped
Build Packages / build:rpm (rocky9) (push) Has been skipped
Build Packages / build:rpm (ubuntu2204) (push) Has been skipped
Build Packages / build:rpm (ubuntu2404) (push) Has been skipped
Build Packages / Unit tests (push) Has been skipped
Build Packages / Generate python client (push) Successful in 18s
Build Packages / Create release (push) Failing after 36s
Build Packages / Build documentation (push) Successful in 40s
2025-09-30 14:22:34 +02:00
49cf508b78 Gitea: Try create release again
Some checks failed
Build Packages / build:rpm (rocky8_nocuda) (push) Has been skipped
Build Packages / build:rpm (rocky9_nocuda) (push) Has been skipped
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Has been skipped
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Has been skipped
Build Packages / build:rpm (rocky8_sls9) (push) Has been skipped
Build Packages / build:rpm (rocky8) (push) Has been skipped
Build Packages / build:rpm (rocky9) (push) Has been skipped
Build Packages / build:rpm (ubuntu2204) (push) Has been skipped
Build Packages / build:rpm (ubuntu2404) (push) Has been skipped
Build Packages / Unit tests (push) Has been skipped
Build Packages / Generate python client (push) Successful in 19s
Build Packages / Build documentation (push) Successful in 41s
Build Packages / Create release (push) Failing after 1m10s
2025-09-30 14:19:17 +02:00
bc95be30e9 Gitea: Try create release again
Some checks failed
Build Packages / build:rpm (rocky8_nocuda) (push) Has been skipped
Build Packages / build:rpm (rocky9_nocuda) (push) Has been skipped
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Has been skipped
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Has been skipped
Build Packages / build:rpm (rocky8_sls9) (push) Has been skipped
Build Packages / build:rpm (rocky8) (push) Has been skipped
Build Packages / build:rpm (rocky9) (push) Has been skipped
Build Packages / build:rpm (ubuntu2204) (push) Has been skipped
Build Packages / build:rpm (ubuntu2404) (push) Has been skipped
Build Packages / Unit tests (push) Has been skipped
Build Packages / Generate python client (push) Successful in 17s
Build Packages / Build documentation (push) Successful in 40s
Build Packages / Create release (push) Failing after 2m45s
2025-09-30 14:13:08 +02:00
32b6f1c8a7 Gitea: Try create release
Some checks failed
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 12m25s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 12m41s
Build Packages / Generate python client (push) Successful in 51s
Build Packages / Create release (push) Failing after 1m4s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 15m4s
Build Packages / build:rpm (rocky8) (push) Successful in 15m8s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 15m36s
Build Packages / Build documentation (push) Successful in 1m3s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 16m40s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 19m48s
Build Packages / build:rpm (rocky9) (push) Successful in 20m32s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 9m45s
Build Packages / Unit tests (push) Has been cancelled
2025-09-30 13:40:29 +02:00
8b6da064c1 VERSION: 1.0.0.rc-87 2025-09-30 13:35:02 +02:00
7cb2033cc7 SpotAnalyze: Estimate resolution based on visible non-ice ring spots 2025-09-30 13:29:45 +02:00
13ed6f280e Docker: Update images (add LFS and Python requests on Rocky) 2025-09-30 13:02:50 +02:00
b15e35b897 CI: Gitea will handle uploading to PyPi 2025-09-30 13:00:10 +02:00
0e8a44cc4f Update .gitattributes
Some checks failed
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 30m16s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 47m7s
Build Packages / Generate python client (push) Failing after 0s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 47m4s
Build Packages / Build documentation (push) Failing after 0s
Build Packages / Unit tests (push) Failing after 0s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 47m30s
Build Packages / build:rpm (rocky8) (push) Successful in 47m33s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 47m42s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 48m3s
Build Packages / build:rpm (rocky9) (push) Successful in 48m21s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 19m48s
2025-09-30 12:31:53 +02:00
6aacbcbe4b Use .gz files (4x compression)
Some checks failed
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Has started running
Build Packages / build:rpm (ubuntu2404) (push) Has been cancelled
Build Packages / Generate python client (push) Has been cancelled
Build Packages / Build documentation (push) Has been cancelled
Build Packages / build:rpm (rocky8_nocuda) (push) Has been cancelled
Build Packages / Unit tests (push) Has been cancelled
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Has been cancelled
Build Packages / build:rpm (rocky8) (push) Has been cancelled
Build Packages / build:rpm (rocky9_nocuda) (push) Has been cancelled
Build Packages / build:rpm (ubuntu2204) (push) Has been cancelled
Build Packages / build:rpm (rocky8_sls9) (push) Has been cancelled
Build Packages / build:rpm (rocky9) (push) Has been cancelled
2025-09-30 12:29:41 +02:00
f47239efa0 Add MCS files with Git LFS
Some checks failed
Build Packages / build:rpm (ubuntu2404) (push) Has been cancelled
Build Packages / Generate python client (push) Has been cancelled
Build Packages / Build documentation (push) Has been cancelled
Build Packages / Unit tests (push) Has been cancelled
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Has been cancelled
Build Packages / build:rpm (rocky8) (push) Has been cancelled
Build Packages / build:rpm (ubuntu2204) (push) Has been cancelled
Build Packages / build:rpm (rocky8_sls9) (push) Has been cancelled
Build Packages / build:rpm (rocky9_nocuda) (push) Has been cancelled
Build Packages / build:rpm (rocky8_nocuda) (push) Has been cancelled
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Has been cancelled
Build Packages / build:rpm (rocky9) (push) Has been cancelled
2025-09-30 12:25:17 +02:00
2419894e83 Enable Git LFS for FPGA images 2025-09-30 12:24:14 +02:00
25 changed files with 332 additions and 276 deletions

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
*.mcs filter=lfs diff=lfs merge=lfs -text
*.mcs.gz filter=lfs diff=lfs merge=lfs -text

View File

@@ -12,6 +12,7 @@ on:
jobs:
build-rpm:
name: build:rpm (${{ matrix.distro }})
if: github.ref_type == 'tag'
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
@@ -20,48 +21,30 @@ jobs:
- runner: jfjoch_rocky8
distro: rocky8
cmake_flags: -DJFJOCH_USE_CUDA=ON
pkg_glob: "*.rpm"
upload_url: https://gitea.psi.ch/api/packages/mx/rpm/centos/el8/slsdet8-cuda12/upload
- runner: jfjoch_rocky8
distro: rocky8_sls9
cmake_flags: -DJFJOCH_USE_CUDA=ON -DSLS9=ON
pkg_glob: "*.rpm"
upload_url: https://gitea.psi.ch/api/packages/mx/rpm/centos/el8/slsdet9-cuda12/upload
- runner: jfjoch_rocky8
distro: rocky8_nocuda
cmake_flags: -DJFJOCH_USE_CUDA=OFF
pkg_glob: "*.rpm"
upload_url: https://gitea.psi.ch/api/packages/mx/rpm/centos/el8/slsdet8-nocuda/upload
- runner: jfjoch_rocky9
distro: rocky9
cmake_flags: -DJFJOCH_USE_CUDA=ON
pkg_glob: "*.rpm"
upload_url: https://gitea.psi.ch/api/packages/mx/rpm/centos/el9/slsdet8-cuda13/upload
- runner: jfjoch_rocky9
distro: rocky9_nocuda
cmake_flags: -DJFJOCH_USE_CUDA=OFF
pkg_glob: "*.rpm"
upload_url: https://gitea.psi.ch/api/packages/mx/rpm/centos/el9/slsdet8-nocuda/upload
- runner: jfjoch_ubuntu2204
distro: ubuntu2204
cmake_flags: -DJFJOCH_USE_CUDA=ON
pkg_glob: "*.deb"
upload_url: https://gitea.psi.ch/api/packages/mx/debian/pool/jammy/cuda13/upload
- runner: jfjoch_ubuntu2204
distro: ubuntu2204_nocuda
cmake_flags: -DJFJOCH_USE_CUDA=OFF
pkg_glob: "*.deb"
upload_url: https://gitea.psi.ch/api/packages/mx/debian/pool/jammy/nocuda/upload
- runner: jfjoch_ubuntu2404
distro: ubuntu2404
cmake_flags: -DJFJOCH_USE_CUDA=ON
pkg_glob: "*.deb"
upload_url: https://gitea.psi.ch/api/packages/mx/debian/pool/noble/cuda13/upload
- runner: jfjoch_ubuntu2404
distro: ubuntu2404_nocuda
cmake_flags: -DJFJOCH_USE_CUDA=OFF
pkg_glob: "*.deb"
upload_url: https://gitea.psi.ch/api/packages/mx/debian/pool/noble/nocuda/upload
steps:
- uses: actions/checkout@v4
- name: Build packages
@@ -75,21 +58,74 @@ jobs:
- name: Upload packages
if: github.ref_type == 'tag'
shell: bash
env:
TOKEN: ${{ secrets.PIP_REPOSITORY_API_TOKEN }}
run: |
set -euo pipefail
cd build
shopt -s nullglob
files=(${{ matrix.pkg_glob }})
if [ ${#files[@]} -eq 0 ]; then
echo "No package files found for pattern: ${{ matrix.pkg_glob }}"
exit 1
if [ "${{ matrix.distro }}" = "rocky8" ]; then
for file in *.rpm; do
if [ -f "$file" ]; then
curl --user __token__:${{ secrets.PIP_REPOSITORY_API_TOKEN }} \
--upload-file "$file" \
https://gitea.psi.ch/api/packages/mx/rpm/centos/el8/slsdet8-cuda12/upload
fi
done
elif [ "${{ matrix.distro }}" = "rocky8_nocuda" ]; then
for file in *.rpm; do
if [ -f "$file" ]; then
curl --user __token__:${{ secrets.PIP_REPOSITORY_API_TOKEN }} \
--upload-file "$file" \
https://gitea.psi.ch/api/packages/mx/rpm/centos/el8/slsdet8-nocuda/upload
fi
done
elif [ "${{ matrix.distro }}" = "rocky8_sls9" ]; then
for file in *.rpm; do
if [ -f "$file" ]; then
curl --user __token__:${{ secrets.PIP_REPOSITORY_API_TOKEN }} \
--upload-file "$file" \
https://gitea.psi.ch/api/packages/mx/rpm/centos/el8/slsdet9-cuda12/upload
fi
done
elif [ "${{ matrix.distro }}" = "rocky9_nocuda" ]; then
for file in *.rpm; do
if [ -f "$file" ]; then
curl --user __token__:${{ secrets.PIP_REPOSITORY_API_TOKEN }} \
--upload-file "$file" \
https://gitea.psi.ch/api/packages/mx/rpm/centos/el9/slsdet8-nocuda/upload
fi
done
elif [ "${{ matrix.distro }}" = "ubuntu2204_nocuda" ]; then
for file in *.deb; do
if [ -f "$file" ]; then
curl --user __token__:${{ secrets.PIP_REPOSITORY_API_TOKEN }} \
--upload-file "$file" \
https://gitea.psi.ch/api/packages/mx/debian/pool/jammy/nocuda/upload
fi
done
elif [ "${{ matrix.distro }}" = "ubuntu2204" ]; then
for file in *.deb; do
if [ -f "$file" ]; then
curl --user __token__:${{ secrets.PIP_REPOSITORY_API_TOKEN }} \
--upload-file "$file" \
https://gitea.psi.ch/api/packages/mx/debian/pool/jammy/cuda13/upload
fi
done
elif [ "${{ matrix.distro }}" = "ubuntu2404" ]; then
for file in *.deb; do
if [ -f "$file" ]; then
curl --user __token__:${{ secrets.PIP_REPOSITORY_API_TOKEN }} \
--upload-file "$file" \
https://gitea.psi.ch/api/packages/mx/debian/pool/noble/cuda13/upload
fi
done
fi
elif [ "${{ matrix.distro }}" = "ubuntu2404_nocuda" ]; then
for file in *.deb; do
if [ -f "$file" ]; then
curl --user __token__:${{ secrets.PIP_REPOSITORY_API_TOKEN }} \
--upload-file "$file" \
https://gitea.psi.ch/api/packages/mx/debian/pool/noble/nocuda/upload
fi
done
fi
for file in "${files[@]}"; do
echo "Uploading $file -> ${{ matrix.upload_url }}"
curl --fail --user __token__:"$TOKEN" --upload-file "$file" "${{ matrix.upload_url }}"
done
python-client:
name: Generate python client
runs-on: jfjoch_rocky8
@@ -103,7 +139,30 @@ jobs:
run: |
twine upload -u __token__ -p ${{ secrets.CI_PYPI_TOKEN }} --skip-existing dist/*
twine upload -u __token__ -p ${{ secrets.PIP_REPOSITORY_API_TOKEN }} --repository-url https://gitea.psi.ch/api/packages/mx/pypi dist/*
create-release:
name: Create release
runs-on: jfjoch_rocky8
# if: github.ref_type == 'tag'
steps:
- uses: actions/checkout@v4
with:
persist-credentials: 'true' # Optional; should be the default
- name: Configure auth and fetch LFS
shell: bash
env:
GITEA_TOKEN: ${{ secrets.PIP_REPOSITORY_API_TOKEN }}
run: |
git lfs install --local
AUTH=$(git config --local http.${{ github.server_url }}/.extraheader)
git config --local --unset http.${{ github.server_url }}/.extraheader
git config --local http.${{ github.server_url }}/${{ github.repository }}.git/info/lfs/objects/batch.extraheader "$AUTH"
git lfs pull
- name: Create release
env:
GITEA_TOKEN: ${{ secrets.PIP_REPOSITORY_API_TOKEN }}
shell: bash
run: |
python3 gitea_create_release.py
documentation:
name: Build documentation
runs-on: jfjoch_rocky8
@@ -133,6 +192,7 @@ jobs:
unit-tests:
name: Unit tests
runs-on: jfjoch_rocky8
if: github.ref_type == 'tag'
container:
image: gitea.psi.ch/leonarski_f/jfjoch_rocky8:2509
options: --gpus all

View File

@@ -133,11 +133,9 @@ struct DataMessage {
std::optional<int64_t> original_number;
std::vector<Reflection> reflections;
std::vector<float> integration_B_logI;
std::vector<float> integration_B_one_over_d;
std::vector<float> integration_logI;
std::vector<float> integration_one_over_d;
std::vector<float> integration_Isigma;
std::vector<float> integration_Isigma_one_over_d;
std::optional<float> beam_corr_x;
std::optional<float> beam_corr_y;

View File

@@ -14,6 +14,7 @@ RUN dnf -y update && \
dnf-plugins-core \
epel-release \
git \
git-lfs \
which \
ca-certificates \
wget \
@@ -60,12 +61,15 @@ RUN dnf -y update && \
python3-pip \
python3-setuptools \
python3-wheel \
python3-requests \
twine \
openblas \
openblas-devel \
freetype-devel && \
dnf clean all && rm -rf /var/cache/dnf
RUN git lfs install
RUN set -eux; \
cd /tmp; \
curl -LO https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz; \

View File

@@ -15,6 +15,7 @@ RUN dnf -y update && \
dnf-plugins-core \
epel-release \
git \
git-lfs \
which \
ca-certificates \
wget \
@@ -32,6 +33,7 @@ RUN dnf -y update && \
redhat-rpm-config \
ninja-build \
python3 \
python3-requests \
perl \
pkgconf-pkg-config \
libxcb-devel \
@@ -61,6 +63,8 @@ RUN dnf -y update && \
freetype-devel && \
dnf clean all && rm -rf /var/cache/dnf
RUN git lfs install
RUN set -eux; \
cd /tmp; \
curl -LO https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz; \

View File

@@ -1,11 +1,4 @@
# Changelog
## 1.0.0.rc.87
This is an UNSTABLE release.
* jfjoch_viewer: Display more image metadata (angle / exposure time)
* jfjoch_viewer: Improve I/sigma and B-factor plots
* jfjoch_broker: Estimate resolution based on visible spots
## 1.0.0.rc.86
This is an UNSTABLE release.

Binary file not shown.

Binary file not shown.

122
gitea_create_release.py Normal file
View File

@@ -0,0 +1,122 @@
#!/usr/bin/env python3
import os
import sys
from typing import Optional
try:
import requests
except ModuleNotFoundError:
print("ERROR: Python 'requests' package is required. Please install it (e.g., pip install requests).")
sys.exit(1)
gitea_api_url = "https://gitea.psi.ch/api/v1/"
repo_owner = "mx"
repo_name = "jungfraujoch"
gitea_token = os.getenv('GITEA_TOKEN')
if gitea_token is None:
print("ERROR: Required environment variables not set")
sys.exit(1)
def read_version():
try:
with open('VERSION', 'r') as f:
return f.readline().strip()
except FileNotFoundError:
print("ERROR: VERSION file not found")
sys.exit(1)
def create_release(version):
headers = {
'Authorization': f'token {gitea_token}',
'Content-Type': 'application/json',
}
data = {
'tag_name': version,
'name': f'Release {version}',
'draft': False,
'prerelease': '-rc.' in version or '-alpha.' in version or '-beta.' in version
}
url = f'{gitea_api_url}/repos/{repo_owner}/{repo_name}/releases'
response = requests.post(url, headers=headers, json=data)
if response.status_code != 201:
print(f"ERROR: Failed to create release. Status code: {response.status_code}")
print(response.text)
sys.exit(1)
release = response.json()
print(f"Successfully created release {version}")
return release
def get_release_by_tag(version: str):
"""
Fetch release information by tag name.
Returns the release object (dict) or exits on error.
"""
headers = {
'Authorization': f'token {gitea_token}',
'Content-Type': 'application/json',
}
url = f'{gitea_api_url}/repos/{repo_owner}/{repo_name}/releases/tags/{version}'
resp = requests.get(url, headers=headers)
if resp.status_code != 200:
print(f"ERROR: Failed to fetch release {version}. Status code: {resp.status_code}")
print(resp.text)
sys.exit(1)
return resp.json()
def upload_file_to_release(version: str, file_path: str, name: Optional[str] = None, label: Optional[str] = None):
"""
Upload a file as an asset to the release identified by the given version tag.
:param version: Tag name of the release (e.g., '1.2.3' or '1.2.3-rc.1')
:param file_path: Local file path to upload
:param name: Optional asset name to display in release (defaults to filename)
:param label: Optional asset label (display name)
"""
if not os.path.isfile(file_path):
print(f"ERROR: File not found: {file_path}")
sys.exit(1)
# Ensure the release exists and get its ID
release = get_release_by_tag(version)
release_id = release.get('id')
if release_id is None:
print("ERROR: Release ID not found in response")
sys.exit(1)
headers = {
'Authorization': f'token {gitea_token}',
}
asset_name = name if name else os.path.basename(file_path)
data = {'name': asset_name}
if label:
data['label'] = label
url = f'{gitea_api_url}/repos/{repo_owner}/{repo_name}/releases/{release_id}/assets'
with open(file_path, 'rb') as f:
files = {
'attachment': (asset_name, f, 'application/octet-stream'),
}
resp = requests.post(url, headers=headers, data=data, files=files)
if resp.status_code not in (201, 200):
print(f"ERROR: Failed to upload asset. Status code: {resp.status_code}")
print(resp.text)
sys.exit(1)
asset = resp.json()
browser_download_url = asset.get('browser_download_url') or asset.get('browser_url') or asset.get('url')
print(f"Uploaded asset '{asset_name}' to release {version}: {browser_download_url}")
if __name__ == '__main__':
version = read_version()
release = create_release(version)
upload_file_to_release(version, 'fpga/images/jfjoch_fpga_pcie_8x10g.mcs.gz', 'jfjoch_fpga_pcie_8x10g.mcs.gz', '8x10G FPGA image')
upload_file_to_release(version, 'fpga/images/jfjoch_fpga_pcie_100g.mcs.gz', 'jfjoch_fpga_pcie_100g.mcs.gz', '100G FPGA image')

View File

@@ -8,7 +8,7 @@
#include "spot_finding/StrongPixelSet.h"
#include "bragg_integration/BraggIntegrate2D.h"
#include "indexing/AnalyzeIndexing.h"
#include "bragg_integration/CalcISigma.h"
void SpotAnalyze(const DiffractionExperiment &experiment,
const SpotFindingSettings &spot_finding_settings,
@@ -86,13 +86,16 @@ void SpotAnalyze(const DiffractionExperiment &experiment,
ewald_dist_cutoff, output.number);
constexpr size_t kMaxReflections = 10000;
if (res.size() > kMaxReflections) {
output.reflections.assign(res.begin(), res.begin() + kMaxReflections);
if (res.reflections.size() > kMaxReflections) {
output.reflections.assign(res.reflections.begin(),
res.reflections.begin() + kMaxReflections);
} else
output.reflections = res;
output.reflections = res.reflections;
CalcISigma(output);
CalcWilsonBFactor(output);
output.b_factor = res.b_factor;
output.integration_Isigma = res.Isigma;
output.integration_logI = res.logI;
output.integration_one_over_d = res.one_over_d;
}
}
}

View File

@@ -4,10 +4,13 @@
#include "BraggIntegrate2D.h"
#include "BraggPrediction.h"
#include "BraggIntegrationStats.h"
template<class T>
bool IntegrateReflection(Reflection &r, const T *image, size_t xpixel, size_t ypixel,
int64_t special_value, int64_t saturation,
float r_3, float r_1_sq, float r_2_sq, float r_3_sq) {
r.I = 0;
r.bkg = 0;
@@ -65,23 +68,24 @@ bool IntegrateReflection(Reflection &r, const T *image, size_t xpixel, size_t yp
template<class T>
std::vector<Reflection> IntegrateInternal(const DiffractionExperiment &experiment,
const CompressedImage &image,
const CrystalLattice &latt,
float dist_from_ewald_sphere,
int64_t special_value,
int64_t saturation,
int64_t image_number) {
QuickIntegrateResult IntegrateInternal(const DiffractionExperiment &experiment,
const CompressedImage &image,
const CrystalLattice &latt,
float dist_from_ewald_sphere,
int64_t special_value,
int64_t saturation,
int64_t image_number) {
auto settings = experiment.GetBraggIntegrationSettings();
auto geom = experiment.GetDiffractionGeometry();
auto start = std::chrono::high_resolution_clock::now();
std::vector<uint8_t> buffer;
auto ptr = reinterpret_cast<const T *>(image.GetUncompressedPtr(buffer));
auto ptr = reinterpret_cast<const T*>(image.GetUncompressedPtr(buffer));
auto refl = CalcBraggPredictions(experiment, latt, settings.GetDMinLimit_A(), dist_from_ewald_sphere);
std::vector<Reflection> ret;
QuickIntegrateResult ret;
BraggIntegrationStats stats(6.0, settings.GetDMinLimit_A(), 20); // Wilson statistics is only linear in certain resolution range
float r_3 = settings.GetR3();
float r_1_sq = settings.GetR1() * settings.GetR1();
float r_2_sq = settings.GetR2() * settings.GetR2();
@@ -90,52 +94,51 @@ std::vector<Reflection> IntegrateInternal(const DiffractionExperiment &experimen
for (const auto &[x, r_in]: refl) {
Reflection r = r_in;
if (IntegrateReflection(r, ptr, image.GetWidth(), image.GetHeight(), special_value, saturation,
r_3, r_1_sq, r_2_sq, r_3_sq)) {
r_3, r_1_sq, r_2_sq, r_3_sq)) {
if (experiment.GetPolarizationFactor()) {
float pol = geom.CalcAzIntPolarizationCorr(r.predicted_x, r.predicted_y,
experiment.GetPolarizationFactor().value());
float pol = geom.CalcAzIntPolarizationCorr(r.predicted_x, r.predicted_y, experiment.GetPolarizationFactor().value());
r.I /= pol;
r.bkg /= pol;
r.sigma /= pol;
}
r.image_number = static_cast<int>(image_number);
ret.push_back(r);
ret.reflections.push_back(r);
stats.AddReflection(r);
}
}
ret.b_factor = stats.BFactor();
ret.logI = stats.GetWilsonPlot();
ret.one_over_d = stats.GetWilsonPlotLegend();
ret.Isigma = stats.GetISigmaPlot();
return ret;
}
std::vector<Reflection> BraggIntegrate2D(const DiffractionExperiment &experiment,
const CompressedImage &image,
const CrystalLattice &latt,
float dist_from_ewald_sphere,
int64_t image_number) {
QuickIntegrateResult BraggIntegrate2D(const DiffractionExperiment &experiment,
const CompressedImage &image,
const CrystalLattice &latt,
float dist_from_ewald_sphere,
int64_t image_number) {
if (image.GetCompressedSize() == 0)
return {};
switch (image.GetMode()) {
case CompressedImageMode::Int8:
return IntegrateInternal<int8_t>(experiment, image, latt, dist_from_ewald_sphere, INT8_MIN, INT8_MAX,
image_number);
return IntegrateInternal<int8_t>(experiment, image, latt, dist_from_ewald_sphere, INT8_MIN, INT8_MAX, image_number);
case CompressedImageMode::Int16:
return IntegrateInternal<int16_t>(experiment, image, latt, dist_from_ewald_sphere, INT16_MIN, INT16_MAX,
image_number);
return IntegrateInternal<int16_t>(experiment, image, latt, dist_from_ewald_sphere, INT16_MIN, INT16_MAX, image_number);
case CompressedImageMode::Int32:
return IntegrateInternal<int32_t>(experiment, image, latt, dist_from_ewald_sphere, INT32_MIN, INT32_MAX,
image_number);
return IntegrateInternal<int32_t>(experiment, image, latt, dist_from_ewald_sphere, INT32_MIN, INT32_MAX, image_number);
case CompressedImageMode::Uint8:
return IntegrateInternal<uint8_t>(experiment, image, latt, dist_from_ewald_sphere, UINT8_MAX, UINT8_MAX,
image_number);
return IntegrateInternal<uint8_t>(experiment, image, latt, dist_from_ewald_sphere, UINT8_MAX, UINT8_MAX, image_number);
case CompressedImageMode::Uint16:
return IntegrateInternal<uint16_t>(experiment, image, latt, dist_from_ewald_sphere, UINT16_MAX, UINT16_MAX,
image_number);
return IntegrateInternal<uint16_t>(experiment, image, latt, dist_from_ewald_sphere, UINT16_MAX, UINT16_MAX, image_number);
case CompressedImageMode::Uint32:
return IntegrateInternal<uint16_t>(experiment, image, latt, dist_from_ewald_sphere, UINT32_MAX, UINT32_MAX,
image_number);
return IntegrateInternal<uint16_t>(experiment, image, latt, dist_from_ewald_sphere, UINT32_MAX, UINT32_MAX, image_number);
default:
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
"Image mode not supported");
}
}

View File

@@ -9,10 +9,18 @@
#include "../../common/CrystalLattice.h"
#include "../../common/Reflection.h"
std::vector<Reflection> BraggIntegrate2D(const DiffractionExperiment &experiment,
const CompressedImage &image,
const CrystalLattice &latt,
float dist_from_ewald_sphere,
int64_t image_number);
struct QuickIntegrateResult {
std::vector<Reflection> reflections;
std::optional<float> b_factor;
std::vector<float> logI;
std::vector<float> one_over_d;
std::vector<float> Isigma;
};
QuickIntegrateResult BraggIntegrate2D(const DiffractionExperiment &experiment,
const CompressedImage &image,
const CrystalLattice &latt,
float dist_from_ewald_sphere,
int64_t image_number);
#endif //JFJOCH_BRAGGINTEGRATE2D_H

View File

@@ -39,12 +39,12 @@ std::optional<float> BraggIntegrationStats::BFactor() {
+ (static_cast<float>(i) + 0.5) * (one_over_dmin - one_over_dmax) / static_cast<float>(nshells);
wilson_plot_legend.push_back(one_over_d);
if (count[i] == 0 )
plot_Isigma.push_back(0);
plot_Isigma.push_back(NAN);
else
plot_Isigma.push_back(I_sigma[i] / count[i]);
if (count[i] == 0 || I[i] < 0.0 || I_sigma[i] < 0.2f * count[i])
wilson_plot.push_back(0);
wilson_plot.push_back(NAN);
else
wilson_plot.push_back(log(I[i] / count[i]));
}
@@ -60,7 +60,7 @@ std::vector<float> BraggIntegrationStats::GetWilsonPlot() {
return wilson_plot;
}
std::vector<float> BraggIntegrationStats::GetOneOverDPlot() {
std::vector<float> BraggIntegrationStats::GetWilsonPlotLegend() {
return wilson_plot_legend;
}

View File

@@ -23,11 +23,11 @@ class BraggIntegrationStats {
std::vector<float> wilson_plot_legend;
std::vector<float> plot_Isigma;
public:
explicit BraggIntegrationStats(float d_max_A, float d_min_A, int32_t nshells);
explicit BraggIntegrationStats(float d_max_A = 6.0, float d_min_A = 1.5, int32_t nshells = 20);
void AddReflection(const Reflection &r);
std::optional<float> BFactor();
std::vector<float> GetWilsonPlot();
std::vector<float> GetOneOverDPlot();
std::vector<float> GetWilsonPlotLegend();
std::vector<float> GetISigmaPlot();
};

View File

@@ -6,8 +6,6 @@ ADD_LIBRARY(JFJochBraggIntegration STATIC
BraggIntegrationStats.cpp
BraggIntegrationStats.h
Regression.h
CalcISigma.cpp
CalcISigma.h
)
TARGET_LINK_LIBRARIES(JFJochBraggIntegration JFJochCommon)

View File

@@ -1,32 +0,0 @@
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include "CalcISigma.h"
#include "BraggIntegrationStats.h"
void CalcISigma(DataMessage &msg) {
if (msg.reflections.empty())
return;
BraggIntegrationStats stats(50.0, 1.5, 20);
for (const auto &r: msg.reflections)
stats.AddReflection(r);
stats.BFactor(); // need to do it to generate plots...
msg.integration_Isigma = stats.GetISigmaPlot();
msg.integration_Isigma_one_over_d = stats.GetOneOverDPlot();
}
void CalcWilsonBFactor(DataMessage &msg) {
if (msg.reflections.empty())
return;
BraggIntegrationStats stats(6.0, 3.0, 10); // Wilson statistics is only linear in certain resolution range
for (const auto &r: msg.reflections)
stats.AddReflection(r);
msg.b_factor = stats.BFactor();
msg.integration_B_logI = stats.GetWilsonPlot();
msg.integration_B_one_over_d = stats.GetOneOverDPlot();
}

View File

@@ -1,12 +0,0 @@
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#ifndef JFJOCH_CALCISIGMA_H
#define JFJOCH_CALCISIGMA_H
#include "../../common/JFJochMessages.h"
void CalcISigma(DataMessage &msg);
void CalcWilsonBFactor(DataMessage &msg);
#endif //JFJOCH_CALCISIGMA_H

View File

@@ -19,7 +19,7 @@ struct SpotFindingSettings {
float ice_ring_width_Q_recipA = 0.02;
bool indexing = true;
bool quick_integration = true;
bool quick_integration = false;
};
#endif //JUNGFRAUJOCH_SPOTFINDINGSETTINGS_H

View File

@@ -4,7 +4,6 @@
#include "JFJochHDF5Reader.h"
#include "spdlog/fmt/fmt.h"
#include "../image_analysis/bragg_integration/BraggIntegrationStats.h"
#include "../image_analysis/bragg_integration/CalcISigma.h"
std::vector<hsize_t> GetDimension(HDF5Object& object, const std::string& path) {
const auto dim = object.GetDimension(path);
@@ -543,6 +542,8 @@ bool JFJochHDF5Reader::LoadImage_i(std::shared_ptr<JFJochReaderDataset> &dataset
|| h.size() != predicted_x.size() || h.size() != predicted_y.size()
|| h.size() != int_sum.size() || h.size() != int_err.size() || h.size() != bkg.size())
throw JFJochException(JFJochExceptionCategory::HDF5, "Wrong size of reflections dataset");
BraggIntegrationSettings settings;
BraggIntegrationStats stats(6.0, settings.GetDMinLimit_A(), 10); // Wilson statistics is only linear in certain resolution range
for (size_t i = 0; i < h.size(); i++) {
Reflection r{
@@ -556,10 +557,13 @@ bool JFJochHDF5Reader::LoadImage_i(std::shared_ptr<JFJochReaderDataset> &dataset
.bkg = bkg.at(i),
.sigma = int_err.at(i)
};
stats.AddReflection(r);
message.reflections.emplace_back(r);
}
CalcISigma(message);
CalcWilsonBFactor(message);
message.integration_logI = stats.GetWilsonPlot();
message.integration_one_over_d = stats.GetWilsonPlotLegend();
message.integration_Isigma = stats.GetISigmaPlot();
}
return true;

View File

@@ -8,7 +8,6 @@
#include "../frame_serialize/CBORStream2Deserializer.h"
#include "../broker/gen/model/Image_buffer_status.h"
#include "../broker/gen/model/Plots.h"
#include "../image_analysis/bragg_integration/CalcISigma.h"
void JFJochHttpReader::Close_i() {
addr = "";
@@ -156,10 +155,6 @@ bool JFJochHttpReader::LoadImage_i(std::shared_ptr<JFJochReaderDataset> &dataset
return false;
message = *msg->data_message;
CalcISigma(message);
CalcWilsonBFactor(message);
return true;
} catch (std::exception &e) {
return false;

View File

@@ -3,7 +3,6 @@
#include "JFJochViewerImageStatistics.h"
#include <QFormLayout>
#include <QRegularExpression>
static QString mkSourceInstrumentText(const DiffractionExperiment& exp) {
QString src, inst;
@@ -62,11 +61,11 @@ JFJochViewerImageStatistics::JFJochViewerImageStatistics(QWidget *parent) : QWid
sample_name = new QLabel(this);
layout->addRow("Sample:", sample_name);
exposure_time = new QLabel(this);
layout->addRow(new QLabel("Exposure Time:"), exposure_time);
error_pixels = new QLabel(this);
layout->addRow(new QLabel("Error pixels:"), error_pixels);
rotation_angle = new QLabel(this);
layout->addRow(new QLabel("Image angle:"), rotation_angle);
sat_pixels = new QLabel(this);
layout->addRow(new QLabel("Sat. pixels:"), sat_pixels);
valid_values = new QLabel(this);
layout->addRow(new QLabel("Valid values:"), valid_values);
@@ -100,17 +99,6 @@ JFJochViewerImageStatistics::JFJochViewerImageStatistics(QWidget *parent) : QWid
}
QString TrimZeros(double number, int precision) {
auto s = QString::number(number, 'f', precision);
s.remove(QRegularExpression("\\.?0+$"));
if (s.isEmpty()) s = "0";
return s;
}
QString FormatTime(std::chrono::microseconds time) {
return TrimZeros(time.count()/1e6, 6);
}
void JFJochViewerImageStatistics::loadImage(std::shared_ptr<const JFJochReaderImage> image) {
if (!image) {
source_name->setText("");
@@ -119,12 +107,9 @@ void JFJochViewerImageStatistics::loadImage(std::shared_ptr<const JFJochReaderIm
dataset_name->setToolTip("");
detector_name->setText("");
detector_name->setToolTip("");
exposure_time->setText("");
exposure_time->setToolTip("");
rotation_angle->setText("");
rotation_angle->setToolTip("");
error_pixels->setText("");
sat_pixels->setText("");
valid_values->setText("");
valid_values->setToolTip("");
spots->setText("");
bkg_estimate->setText("");
indexed->setText("");
@@ -161,24 +146,16 @@ void JFJochViewerImageStatistics::loadImage(std::shared_ptr<const JFJochReaderIm
detector_name->setText(text);
source_name->setText(mkSourceInstrumentText(exp));
source_name->setToolTip(mkSourceInstrumentTooltip(exp));
source_name->setToolTip(mkSampleTooltip(exp));
sample_name->setText(mkSampleText(exp));
sample_name->setToolTip(mkSampleTooltip(exp));
if (exp.GetGoniometer()) {
rotation_angle->setText(QString("<b>%1°</b>")
.arg(TrimZeros(exp.GetGoniometer()->GetIncrement_deg(), 3)));
rotation_angle->setToolTip(QString("Start angle: <b>%1°</b><br/>This image: <b>%2°</b>")
.arg(TrimZeros(exp.GetGoniometer()->GetStart_deg(), 3))
.arg(TrimZeros(exp.GetGoniometer()->GetAngle_deg(image->ImageData().number), 3))
);
} else {
rotation_angle->setText(QString("-"));
rotation_angle->setToolTip("");
}
exposure_time->setText(QString("<b>%1</b> s").arg(FormatTime(exp.GetImageTime())));
exposure_time->setToolTip(QString("Count time: <b>%1</b> s<br/>").arg(FormatTime(exp.GetImageCountTime())));
text = QString("<b>%1</b>").arg(image->ErrorPixels().size());
error_pixels->setText(text);
text = QString("<b>%1</b>").arg(image->SaturatedPixels().size());
sat_pixels->setText(text);
text = QString("<b>%1</b>").arg(image->ImageData().spots.size());
spots->setText(text);
@@ -193,8 +170,6 @@ void JFJochViewerImageStatistics::loadImage(std::shared_ptr<const JFJochReaderIm
.arg(image->ValidPixels().begin()->first)
.arg(image->ValidPixels().rbegin()->first);
valid_values->setText(text);
valid_values->setToolTip(QString("Error pixels: <b>%1</b><br/>Saturated pixels: <b>%2</b>")
.arg(image->ErrorPixels().size()).arg(image->SaturatedPixels().size()));
if (!image->Dataset().bkg_estimate.empty()) {
text = QString("<b>%1</b>").arg(image->ImageData().bkg_estimate.value_or(0));

View File

@@ -15,8 +15,8 @@ class JFJochViewerImageStatistics : public QWidget {
QLabel *sample_name;
QLabel *dataset_name;
QLabel *detector_name;
QLabel *exposure_time;
QLabel *rotation_angle;
QLabel *error_pixels;
QLabel *sat_pixels;
QLabel *spots;
QLabel *valid_values;
QLabel *indexed;

View File

@@ -24,8 +24,8 @@ JFJochViewerSidePanelChart::JFJochViewerSidePanelChart(QWidget *parent) : QWidge
azint_image = new JFJochAzIntImageView(this);
stack = new QStackedWidget(this);
stack->addWidget(azint_plot); // index 0
stack->addWidget(azint_image); // index 1
stack->addWidget(azint_plot); // index 0
stack->addWidget(azint_image); // index 1
layout->addWidget(stack);
setLayout(layout);
@@ -44,8 +44,7 @@ void JFJochViewerSidePanelChart::comboBoxSelected(int val) {
}
void JFJochViewerSidePanelChart::redrawPlot() {
std::vector<float> x, y;
bool one_over_x = false;
std::vector<float> x,y;
if (image) {
auto index = combo_box->currentIndex();
switch (combo_box->itemData(index).toInt()) {
@@ -54,14 +53,12 @@ void JFJochViewerSidePanelChart::redrawPlot() {
y = image->GetAzInt1D();
break;
case 1:
x = image->ImageData().integration_B_one_over_d;
y = image->ImageData().integration_B_logI;
one_over_x = true;
x = image->ImageData().integration_one_over_d;
y = image->ImageData().integration_logI;
break;
case 2:
x = image->ImageData().integration_Isigma_one_over_d;
x = image->ImageData().integration_one_over_d;
y = image->ImageData().integration_Isigma;
one_over_x = true;
break;
case 4:
x = image->Dataset().experiment.GetFluorescenceSpectrum().GetEnergy_eV();
@@ -86,7 +83,7 @@ void JFJochViewerSidePanelChart::redrawPlot() {
}
}
if (!x.empty() && !y.empty()) {
azint_plot->UpdateData(x, y, one_over_x);
azint_plot->UpdateData(x,y);
} else {
azint_plot->ClearData();
}
@@ -95,4 +92,4 @@ void JFJochViewerSidePanelChart::redrawPlot() {
void JFJochViewerSidePanelChart::loadImage(std::shared_ptr<const JFJochReaderImage> in_image) {
image = in_image;
redrawPlot();
}
}

View File

@@ -6,7 +6,6 @@
#include <QMenu>
#include <QClipboard>
#include <QApplication>
#include <QCategoryAxis>
JFJochSimpleChartView::JFJochSimpleChartView(QWidget *parent)
: QChartView(new QChart(), parent) {
@@ -16,58 +15,16 @@ JFJochSimpleChartView::JFJochSimpleChartView(QWidget *parent)
//setRubberBand(QChartView::RubberBand::HorizontalRubberBand);
}
void JFJochSimpleChartView::UpdateData(const std::vector<float> &in_x, const std::vector<float> &in_y, bool one_over_x) {
x = in_x; y = in_y;
void JFJochSimpleChartView::UpdateData(const std::vector<float> &in_x, const std::vector<float> &in_y) {
x = in_x;
y = in_y;
chart()->removeAllSeries();
// Remove all axes to avoid duplicates
for (auto ax : chart()->axes()) chart()->removeAxis(ax);
if (x.empty() || x.size() != y.size()) return;
auto* series = new QLineSeries(this);
for (size_t i = 0; i < x.size(); ++i) series->append(x[i], y[i]);
chart()->addSeries(series);
auto* axY = new QValueAxis();
chart()->addAxis(axY, Qt::AlignLeft);
series->attachAxis(axY);
// Build X range
auto [minXIt, maxXIt] = std::minmax_element(x.begin(), x.end());
const double xmin = *minXIt, xmax = *maxXIt;
if (one_over_x) {
// Hidden X value axis on top for grid/range
auto* axXTop = new QValueAxis();
axXTop->setRange(xmin, xmax);
axXTop->setTickCount(5);
axXTop->setLabelsVisible(false);
chart()->addAxis(axXTop, Qt::AlignTop);
series->attachAxis(axXTop);
// Visible category axis at bottom for 1/x labels
auto* axXcat = new QCategoryAxis();
axXcat->setLabelsPosition(QCategoryAxis::AxisLabelsPositionOnValue);
axXcat->setGridLineVisible(false);
axXcat->setMinorGridLineVisible(false);
const int tickCount = axXTop->tickCount();
const double step = (tickCount > 1) ? (xmax - xmin) / (tickCount - 1) : 0.0;
for (int i = 0; i < tickCount; ++i) {
const double xv = xmin + i * step;
const QString lab = (std::abs(xv) < 1e-12)
? QStringLiteral("")
: QString::number(1.0 / xv, 'g', 4);
axXcat->append(lab, xv);
}
chart()->addAxis(axXcat, Qt::AlignBottom);
series->attachAxis(axXcat);
} else {
// Normal value axis at bottom
auto* axX = new QValueAxis();
axX->setRange(xmin, xmax);
chart()->addAxis(axX, Qt::AlignBottom);
series->attachAxis(axX);
if (!x.empty() && x.size() == y.size()) {
series = new QLineSeries(this);
for (int i = 0; i < x.size(); i++)
series->append(x[i], in_y[i]);
chart()->addSeries(series);
chart()->createDefaultAxes();
}
}
@@ -94,31 +51,3 @@ void JFJochSimpleChartView::contextMenuEvent(QContextMenuEvent *event) {
cb->setText(out);
}
}
void JFJochSimpleChartView::applyInverseXLabels(QValueAxis* axX, QLineSeries* s) {
axX->setTickCount(5); // adjust as needed
axX->setLabelsVisible(false); // hide default numeric labels
auto* axXcat = new QCategoryAxis();
axXcat->setLabelsPosition(QCategoryAxis::AxisLabelsPositionOnValue);
axXcat->setGridLineVisible(false);
axXcat->setMinorGridLineVisible(false);
const int tickCount = axX->tickCount();
const double min = axX->min();
const double max = axX->max();
const double step = (tickCount > 1) ? (max - min) / (tickCount - 1) : 0.0;
for (int i = 0; i < tickCount; ++i) {
const double xv = min + i * step;
if (std::abs(xv) < 1e-12) {
axXcat->append("", xv);
} else {
const double inv = 1.0 / xv;
axXcat->append(QString::number(inv, 'g', 4), xv);
}
}
chart()->addAxis(axXcat, Qt::AlignBottom);
s->attachAxis(axXcat);
}

View File

@@ -11,16 +11,15 @@
class JFJochSimpleChartView : public QChartView {
Q_OBJECT
QLineSeries *series = nullptr;
std::vector<float> x, y;
void applyInverseXLabels(QValueAxis* axX, QLineSeries* s);
void Plot();
protected:
void contextMenuEvent(QContextMenuEvent *event) override;
public:
JFJochSimpleChartView(QWidget *parent = nullptr);
void UpdateData(const std::vector<float> &in_x, const std::vector<float> &in_y, bool one_over_x);
void UpdateData(const std::vector<float> &in_x, const std::vector<float> &in_y);
void ClearData();
};