Compare commits
2 Commits
2510-relea
...
1.0.0-rc.8
| Author | SHA1 | Date | |
|---|---|---|---|
| dc4a1ee9e6 | |||
| 99b7dc07f7 |
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -1,2 +0,0 @@
|
||||
*.mcs filter=lfs diff=lfs merge=lfs -text
|
||||
*.mcs.gz filter=lfs diff=lfs merge=lfs -text
|
||||
@@ -12,7 +12,6 @@ on:
|
||||
jobs:
|
||||
build-rpm:
|
||||
name: build:rpm (${{ matrix.distro }})
|
||||
if: github.ref_type == 'tag'
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -21,30 +20,48 @@ 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
|
||||
@@ -58,74 +75,21 @@ jobs:
|
||||
- name: Upload packages
|
||||
if: github.ref_type == 'tag'
|
||||
shell: bash
|
||||
env:
|
||||
TOKEN: ${{ secrets.PIP_REPOSITORY_API_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cd build
|
||||
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
|
||||
shopt -s nullglob
|
||||
files=(${{ matrix.pkg_glob }})
|
||||
if [ ${#files[@]} -eq 0 ]; then
|
||||
echo "No package files found for pattern: ${{ matrix.pkg_glob }}"
|
||||
exit 1
|
||||
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
|
||||
@@ -139,30 +103,7 @@ 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
|
||||
@@ -192,7 +133,6 @@ 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
|
||||
|
||||
@@ -133,9 +133,11 @@ struct DataMessage {
|
||||
std::optional<int64_t> original_number;
|
||||
std::vector<Reflection> reflections;
|
||||
|
||||
std::vector<float> integration_logI;
|
||||
std::vector<float> integration_one_over_d;
|
||||
std::vector<float> integration_B_logI;
|
||||
std::vector<float> integration_B_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;
|
||||
|
||||
@@ -14,7 +14,6 @@ RUN dnf -y update && \
|
||||
dnf-plugins-core \
|
||||
epel-release \
|
||||
git \
|
||||
git-lfs \
|
||||
which \
|
||||
ca-certificates \
|
||||
wget \
|
||||
@@ -61,15 +60,12 @@ 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; \
|
||||
|
||||
@@ -15,7 +15,6 @@ RUN dnf -y update && \
|
||||
dnf-plugins-core \
|
||||
epel-release \
|
||||
git \
|
||||
git-lfs \
|
||||
which \
|
||||
ca-certificates \
|
||||
wget \
|
||||
@@ -33,7 +32,6 @@ RUN dnf -y update && \
|
||||
redhat-rpm-config \
|
||||
ninja-build \
|
||||
python3 \
|
||||
python3-requests \
|
||||
perl \
|
||||
pkgconf-pkg-config \
|
||||
libxcb-devel \
|
||||
@@ -63,8 +61,6 @@ 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; \
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
# 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.
@@ -1,122 +0,0 @@
|
||||
#!/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')
|
||||
@@ -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,16 +86,13 @@ void SpotAnalyze(const DiffractionExperiment &experiment,
|
||||
ewald_dist_cutoff, output.number);
|
||||
|
||||
constexpr size_t kMaxReflections = 10000;
|
||||
if (res.reflections.size() > kMaxReflections) {
|
||||
output.reflections.assign(res.reflections.begin(),
|
||||
res.reflections.begin() + kMaxReflections);
|
||||
if (res.size() > kMaxReflections) {
|
||||
output.reflections.assign(res.begin(), res.begin() + kMaxReflections);
|
||||
} else
|
||||
output.reflections = res.reflections;
|
||||
output.reflections = res;
|
||||
|
||||
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;
|
||||
CalcISigma(output);
|
||||
CalcWilsonBFactor(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,10 @@
|
||||
#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;
|
||||
|
||||
@@ -68,24 +65,23 @@ bool IntegrateReflection(Reflection &r, const T *image, size_t xpixel, size_t yp
|
||||
|
||||
|
||||
template<class T>
|
||||
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) {
|
||||
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) {
|
||||
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);
|
||||
|
||||
QuickIntegrateResult ret;
|
||||
BraggIntegrationStats stats(6.0, settings.GetDMinLimit_A(), 20); // Wilson statistics is only linear in certain resolution range
|
||||
std::vector<Reflection> ret;
|
||||
float r_3 = settings.GetR3();
|
||||
float r_1_sq = settings.GetR1() * settings.GetR1();
|
||||
float r_2_sq = settings.GetR2() * settings.GetR2();
|
||||
@@ -94,51 +90,52 @@ QuickIntegrateResult IntegrateInternal(const DiffractionExperiment &experiment,
|
||||
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.reflections.push_back(r);
|
||||
stats.AddReflection(r);
|
||||
ret.push_back(r);
|
||||
}
|
||||
}
|
||||
ret.b_factor = stats.BFactor();
|
||||
ret.logI = stats.GetWilsonPlot();
|
||||
ret.one_over_d = stats.GetWilsonPlotLegend();
|
||||
ret.Isigma = stats.GetISigmaPlot();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
QuickIntegrateResult BraggIntegrate2D(const DiffractionExperiment &experiment,
|
||||
const CompressedImage &image,
|
||||
const CrystalLattice &latt,
|
||||
float dist_from_ewald_sphere,
|
||||
int64_t image_number) {
|
||||
std::vector<Reflection> 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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,18 +9,10 @@
|
||||
#include "../../common/CrystalLattice.h"
|
||||
#include "../../common/Reflection.h"
|
||||
|
||||
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);
|
||||
std::vector<Reflection> BraggIntegrate2D(const DiffractionExperiment &experiment,
|
||||
const CompressedImage &image,
|
||||
const CrystalLattice &latt,
|
||||
float dist_from_ewald_sphere,
|
||||
int64_t image_number);
|
||||
|
||||
#endif //JFJOCH_BRAGGINTEGRATE2D_H
|
||||
|
||||
@@ -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(NAN);
|
||||
plot_Isigma.push_back(0);
|
||||
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(NAN);
|
||||
wilson_plot.push_back(0);
|
||||
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::GetWilsonPlotLegend() {
|
||||
std::vector<float> BraggIntegrationStats::GetOneOverDPlot() {
|
||||
return wilson_plot_legend;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,11 +23,11 @@ class BraggIntegrationStats {
|
||||
std::vector<float> wilson_plot_legend;
|
||||
std::vector<float> plot_Isigma;
|
||||
public:
|
||||
explicit BraggIntegrationStats(float d_max_A = 6.0, float d_min_A = 1.5, int32_t nshells = 20);
|
||||
explicit BraggIntegrationStats(float d_max_A, float d_min_A, int32_t nshells);
|
||||
void AddReflection(const Reflection &r);
|
||||
std::optional<float> BFactor();
|
||||
std::vector<float> GetWilsonPlot();
|
||||
std::vector<float> GetWilsonPlotLegend();
|
||||
std::vector<float> GetOneOverDPlot();
|
||||
std::vector<float> GetISigmaPlot();
|
||||
};
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ ADD_LIBRARY(JFJochBraggIntegration STATIC
|
||||
BraggIntegrationStats.cpp
|
||||
BraggIntegrationStats.h
|
||||
Regression.h
|
||||
CalcISigma.cpp
|
||||
CalcISigma.h
|
||||
)
|
||||
|
||||
TARGET_LINK_LIBRARIES(JFJochBraggIntegration JFJochCommon)
|
||||
32
image_analysis/bragg_integration/CalcISigma.cpp
Normal file
32
image_analysis/bragg_integration/CalcISigma.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
// 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();
|
||||
}
|
||||
12
image_analysis/bragg_integration/CalcISigma.h
Normal file
12
image_analysis/bragg_integration/CalcISigma.h
Normal file
@@ -0,0 +1,12 @@
|
||||
// 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
|
||||
@@ -19,7 +19,7 @@ struct SpotFindingSettings {
|
||||
float ice_ring_width_Q_recipA = 0.02;
|
||||
|
||||
bool indexing = true;
|
||||
bool quick_integration = false;
|
||||
bool quick_integration = true;
|
||||
};
|
||||
|
||||
#endif //JUNGFRAUJOCH_SPOTFINDINGSETTINGS_H
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#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);
|
||||
@@ -542,8 +543,6 @@ 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{
|
||||
@@ -557,13 +556,10 @@ 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);
|
||||
}
|
||||
|
||||
message.integration_logI = stats.GetWilsonPlot();
|
||||
message.integration_one_over_d = stats.GetWilsonPlotLegend();
|
||||
message.integration_Isigma = stats.GetISigmaPlot();
|
||||
CalcISigma(message);
|
||||
CalcWilsonBFactor(message);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#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 = "";
|
||||
@@ -155,6 +156,10 @@ 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;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "JFJochViewerImageStatistics.h"
|
||||
#include <QFormLayout>
|
||||
#include <QRegularExpression>
|
||||
|
||||
static QString mkSourceInstrumentText(const DiffractionExperiment& exp) {
|
||||
QString src, inst;
|
||||
@@ -61,11 +62,11 @@ JFJochViewerImageStatistics::JFJochViewerImageStatistics(QWidget *parent) : QWid
|
||||
sample_name = new QLabel(this);
|
||||
layout->addRow("Sample:", sample_name);
|
||||
|
||||
error_pixels = new QLabel(this);
|
||||
layout->addRow(new QLabel("Error pixels:"), error_pixels);
|
||||
exposure_time = new QLabel(this);
|
||||
layout->addRow(new QLabel("Exposure Time:"), exposure_time);
|
||||
|
||||
sat_pixels = new QLabel(this);
|
||||
layout->addRow(new QLabel("Sat. pixels:"), sat_pixels);
|
||||
rotation_angle = new QLabel(this);
|
||||
layout->addRow(new QLabel("Image angle:"), rotation_angle);
|
||||
|
||||
valid_values = new QLabel(this);
|
||||
layout->addRow(new QLabel("Valid values:"), valid_values);
|
||||
@@ -99,6 +100,17 @@ 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("");
|
||||
@@ -107,9 +119,12 @@ void JFJochViewerImageStatistics::loadImage(std::shared_ptr<const JFJochReaderIm
|
||||
dataset_name->setToolTip("");
|
||||
detector_name->setText("");
|
||||
detector_name->setToolTip("");
|
||||
error_pixels->setText("");
|
||||
sat_pixels->setText("");
|
||||
exposure_time->setText("");
|
||||
exposure_time->setToolTip("");
|
||||
rotation_angle->setText("");
|
||||
rotation_angle->setToolTip("");
|
||||
valid_values->setText("");
|
||||
valid_values->setToolTip("");
|
||||
spots->setText("");
|
||||
bkg_estimate->setText("");
|
||||
indexed->setText("");
|
||||
@@ -146,16 +161,24 @@ void JFJochViewerImageStatistics::loadImage(std::shared_ptr<const JFJochReaderIm
|
||||
detector_name->setText(text);
|
||||
|
||||
source_name->setText(mkSourceInstrumentText(exp));
|
||||
source_name->setToolTip(mkSampleTooltip(exp));
|
||||
source_name->setToolTip(mkSourceInstrumentTooltip(exp));
|
||||
|
||||
sample_name->setText(mkSampleText(exp));
|
||||
sample_name->setToolTip(mkSampleTooltip(exp));
|
||||
|
||||
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);
|
||||
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->ImageData().spots.size());
|
||||
spots->setText(text);
|
||||
@@ -170,6 +193,8 @@ 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));
|
||||
|
||||
@@ -15,8 +15,8 @@ class JFJochViewerImageStatistics : public QWidget {
|
||||
QLabel *sample_name;
|
||||
QLabel *dataset_name;
|
||||
QLabel *detector_name;
|
||||
QLabel *error_pixels;
|
||||
QLabel *sat_pixels;
|
||||
QLabel *exposure_time;
|
||||
QLabel *rotation_angle;
|
||||
QLabel *spots;
|
||||
QLabel *valid_values;
|
||||
QLabel *indexed;
|
||||
|
||||
@@ -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,7 +44,8 @@ void JFJochViewerSidePanelChart::comboBoxSelected(int val) {
|
||||
}
|
||||
|
||||
void JFJochViewerSidePanelChart::redrawPlot() {
|
||||
std::vector<float> x,y;
|
||||
std::vector<float> x, y;
|
||||
bool one_over_x = false;
|
||||
if (image) {
|
||||
auto index = combo_box->currentIndex();
|
||||
switch (combo_box->itemData(index).toInt()) {
|
||||
@@ -53,12 +54,14 @@ void JFJochViewerSidePanelChart::redrawPlot() {
|
||||
y = image->GetAzInt1D();
|
||||
break;
|
||||
case 1:
|
||||
x = image->ImageData().integration_one_over_d;
|
||||
y = image->ImageData().integration_logI;
|
||||
x = image->ImageData().integration_B_one_over_d;
|
||||
y = image->ImageData().integration_B_logI;
|
||||
one_over_x = true;
|
||||
break;
|
||||
case 2:
|
||||
x = image->ImageData().integration_one_over_d;
|
||||
x = image->ImageData().integration_Isigma_one_over_d;
|
||||
y = image->ImageData().integration_Isigma;
|
||||
one_over_x = true;
|
||||
break;
|
||||
case 4:
|
||||
x = image->Dataset().experiment.GetFluorescenceSpectrum().GetEnergy_eV();
|
||||
@@ -83,7 +86,7 @@ void JFJochViewerSidePanelChart::redrawPlot() {
|
||||
}
|
||||
}
|
||||
if (!x.empty() && !y.empty()) {
|
||||
azint_plot->UpdateData(x,y);
|
||||
azint_plot->UpdateData(x, y, one_over_x);
|
||||
} else {
|
||||
azint_plot->ClearData();
|
||||
}
|
||||
@@ -92,4 +95,4 @@ void JFJochViewerSidePanelChart::redrawPlot() {
|
||||
void JFJochViewerSidePanelChart::loadImage(std::shared_ptr<const JFJochReaderImage> in_image) {
|
||||
image = in_image;
|
||||
redrawPlot();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <QMenu>
|
||||
#include <QClipboard>
|
||||
#include <QApplication>
|
||||
#include <QCategoryAxis>
|
||||
|
||||
JFJochSimpleChartView::JFJochSimpleChartView(QWidget *parent)
|
||||
: QChartView(new QChart(), parent) {
|
||||
@@ -15,16 +16,58 @@ JFJochSimpleChartView::JFJochSimpleChartView(QWidget *parent)
|
||||
//setRubberBand(QChartView::RubberBand::HorizontalRubberBand);
|
||||
}
|
||||
|
||||
void JFJochSimpleChartView::UpdateData(const std::vector<float> &in_x, const std::vector<float> &in_y) {
|
||||
x = in_x;
|
||||
y = in_y;
|
||||
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;
|
||||
chart()->removeAllSeries();
|
||||
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();
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,3 +94,31 @@ 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);
|
||||
}
|
||||
@@ -11,15 +11,16 @@
|
||||
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);
|
||||
void UpdateData(const std::vector<float> &in_x, const std::vector<float> &in_y, bool one_over_x);
|
||||
|
||||
void ClearData();
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user