Skip to content

Commit 83b6c62

Browse files
committed
Major update! Pypi compatible vie 30 ene 2026 12:46:08 CET
1 parent 6c54181 commit 83b6c62

13 files changed

Lines changed: 489 additions & 530 deletions

File tree

pyproject.toml

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "rionid"
3-
version = "1.0.0"
3+
version = "8.0.0"
44
description = "Ring-stored ion IDentification: A pure Python tool for Schottky spectrum analysis."
55
authors = ["David Freire-Fernández <D.FreireFernandez@gsi.de>"]
66
license = "GPL-3.0-or-later"
@@ -9,37 +9,52 @@ homepage = "https://gsi-nuclear-astrophysics.github.io/rionid/"
99
repository = "https://github.com/GSI-Nuclear-Astrophysics/rionid"
1010
keywords = ["Physics", "Nuclear Physics", "Storage Rings", "Mass Spectrometry", "Schottky"]
1111

12+
classifiers = [
13+
"Development Status :: 5 - Production/Stable",
14+
"Intended Audience :: Science/Research",
15+
"Topic :: Scientific/Engineering :: Physics",
16+
"Programming Language :: Python :: 3.9",
17+
"Programming Language :: Python :: 3.10",
18+
"Programming Language :: Python :: 3.11",
19+
"Programming Language :: Python :: 3.12",
20+
]
21+
1222
# Recursively include everything in src/rionid
1323
packages = [
1424
{ include = "rionid", from = "src" }
1525
]
1626

17-
# Ensure mass tables inside the vendorized barion are packaged
18-
include = [
19-
"src/rionid/external/barion/*.mas20",
20-
"src/rionid/external/barion/data/*"
21-
]
27+
[tool.poetry.urls]
28+
"Bug Tracker" = "https://github.com/GSI-Nuclear-Astrophysics/rionid/issues"
29+
"Documentation" = "https://gsi-nuclear-astrophysics.github.io/rionid/"
30+
"Source Code" = "https://github.com/GSI-Nuclear-Astrophysics/rionid"
2231

2332
[tool.poetry.dependencies]
2433
python = ">=3.9,<3.13"
25-
numpy = "*"
26-
scipy = "*"
27-
PyQt5 = "*"
28-
pyqtgraph = "*"
29-
loguru = "*"
30-
toml = "*"
31-
ezodf = "*"
32-
requests = "*" # Required for Barion to download mass tables
33-
fortranformat = "*"
34+
numpy = "^2.0.2"
35+
scipy = "^1.10.1"
36+
PyQt5 = "^5.15.9"
37+
pyqtgraph = "^0.13.3"
38+
loguru = "^0.7.0"
39+
toml = "^0.10.2"
40+
ezodf = "^0.3.2"
41+
requests = "^2.31.0" # Required for Barion to download mass tables
42+
fortranformat = "^2.0.3"
43+
lxml = "^6.0.2"
44+
3445
[tool.poetry.group.dev.dependencies]
35-
pytest = "*"
36-
black = "*"
46+
pytest = "^8.0.0"
47+
black = "^25.0.0"
48+
49+
[tool.poetry.group.docs]
50+
optional = true
51+
[tool.poetry.group.docs.dependencies]
3752
# Documentation dependencies
38-
mkdocs = "*"
39-
mkdocs-material = "*"
40-
mkdocstrings = {extras = ["python"], version = "*"}
41-
mkdocs-gen-files = "*"
42-
mkdocs-literate-nav = "*"
53+
mkdocs = "^1.5.0"
54+
mkdocs-material = "^9.5.0"
55+
mkdocstrings = {extras = ["python"], version = "^0.24.0"}
56+
mkdocs-gen-files = "^0.5.0"
57+
mkdocs-literate-nav = "^0.6.0"
4358

4459
[tool.poetry.scripts]
4560
rionid = "rionid.gui.app:main"

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ loguru
66
toml
77
ezodf
88
requests
9-
fortranformat
9+
fortranformat
10+
lxml

src/rionid/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
from .core import ImportData
2-
from .gui.plot import CreatePyGUI
32
from .version import __version__

src/rionid/__main__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ def main():
9494
show=args.show,
9595
app=app
9696
)
97+
98+
if args.show and app:
99+
sys.exit(app.exec_())
97100

98101
def run_controller(data_file, particles_to_simulate, alphap, ref_ion, harmonics,
99102
brho=None, fref=None, ke=None, gam=None, correct=None,
@@ -169,8 +172,6 @@ def run_controller(data_file, particles_to_simulate, alphap, ref_ion, harmonics,
169172
if show and app:
170173
sa = CreatePyGUI(mydata.experimental_data, mydata.simulated_data_dict)
171174
sa.show()
172-
# This blocks execution until the window is closed
173-
app.exec_()
174175

175176
def display_nions(nions, yield_data, nuclei_names, simulated_data_dict, ref_ion, harmonics):
176177
"""Filters the top N ions by yield."""

src/rionid/core.py

Lines changed: 78 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@
1414
from rionid.io import (
1515
read_psdata,
1616
handle_read_tdsm_bin,
17-
handle_read_tdsm,
18-
handle_read_rsa_specan_xml,
19-
handle_spectrumnpz_data,
17+
handle_spectrumnpz_data, # probably I will just keep this option, delete the others
2018
handle_tiqnpz_data
2119
)
2220
from rionid.baseline import NONPARAMS_EST
@@ -72,11 +70,14 @@ def __init__(self, refion, alphap, filename=None, reload_data=None, circumferenc
7270
self.total_mass = dict()
7371
self.yield_data = []
7472

75-
self.ref_ion = refion
7673
self.highlight_ions = self._parse_highlight_ions(highlight_ions)
7774
self.alphap = alphap
78-
self.gammat = (1/alphap)**0.5 if alphap > 0 else 0
75+
self.gammat = 1.0 / (self.alphap ** 0.5)
76+
7977
self.ring = Ring('ESR', circumference)
78+
79+
self.ref_ion = refion.strip()
80+
self._parse_ref_ion(refion)
8081

8182
# Physics / Matching Params
8283
self.peak_threshold_pct = float(peak_threshold_pct) if peak_threshold_pct else 0.05
@@ -94,6 +95,7 @@ def __init__(self, refion, alphap, filename=None, reload_data=None, circumferenc
9495
self.match_count = 0
9596

9697
self.cache_file = self._get_cache_file_path(filename) if filename else None
98+
self.experimental_data = None
9799

98100
if filename is not None:
99101
if reload_data:
@@ -102,12 +104,66 @@ def __init__(self, refion, alphap, filename=None, reload_data=None, circumferenc
102104
else:
103105
try:
104106
self._load_experimental_data()
105-
except FileNotFoundError:
107+
except (FileNotFoundError, IOError):
106108
self._get_experimental_data(filename)
107109

108-
# Process peaks after loading
110+
# --- NEW DATA PROCESSING BLOCK ---
111+
if self.experimental_data is not None:
112+
freq, amp = self.experimental_data
113+
114+
# 1. Baseline Removal
115+
if remove_baseline:
116+
try:
117+
est = NONPARAMS_EST(amp)
118+
baseline = est.pls('BrPLS', l=psd_baseline_removed_l, ratio=1e-6)
119+
amp = amp - baseline
120+
except Exception as e:
121+
print(f"Baseline removal failed: {e}")
122+
traceback.print_exc()
123+
124+
# 2. Log-Safety (Clip negatives)
125+
# Ensure all values are > 0 for logarithmic plotting.
126+
# We use 1e-9 as a "floor" value.
127+
amp = np.maximum(amp, 1e-29)
128+
129+
# 3. Normalization
130+
# Scale so the highest peak is 1.0
131+
max_val = np.max(amp)
132+
if max_val > 0:
133+
amp = amp / max_val
134+
135+
# Update the stored data
136+
self.experimental_data = (freq, amp)
137+
# ---------------------------------
138+
139+
# Process peaks after loading and processing
109140
self.detect_peaks_and_widths()
110141

142+
def _parse_ref_ion(self, refion):
143+
# Regex to extract Mass(Digits), Element(Letters), Charge(Digits)
144+
# It handles both '98Zr+39' and '98Zr39+' inputs
145+
match = re.match(r'(\d+)([a-zA-Z]+).*?(\d+)', self.ref_ion)
146+
if match:
147+
self.ref_aa = int(match.group(1))
148+
self.ref_el = match.group(2)
149+
self.ref_charge = int(match.group(3))
150+
# Force standard format: 98Zr39+
151+
self.ref_ion = f"{self.ref_aa}{self.ref_el}{self.ref_charge}+"
152+
else:
153+
# Fallback parsing
154+
try:
155+
# Try splitting by '+' if it exists in the middle
156+
if '+' in refion and not refion.endswith('+'):
157+
parts = refion.split('+')
158+
self.ref_charge = int(parts[1])
159+
self.ref_aa = int(re.split(r'(\d+)', parts[0])[1])
160+
else:
161+
# Assume format like 98Zr39+
162+
self.ref_charge = int(re.findall(r'\d+', refion)[-1])
163+
self.ref_aa = int(re.findall(r'\d+', refion)[0])
164+
except:
165+
print(f"Warning: Could not parse reference ion '{refion}'.")
166+
111167
def _parse_highlight_ions(self, input_str):
112168
"""Parses a comma-separated string of ions into a list."""
113169
if not input_str: return []
@@ -128,15 +184,12 @@ def _get_experimental_data(self, filename):
128184
self.experimental_data = read_psdata(filename, dbm=False)
129185
elif ext in ['.bin_fre', '.bin_time', '.bin_amp']:
130186
self.experimental_data = handle_read_tdsm_bin(filename)
131-
elif ext == '.tdms':
132-
self.experimental_data = handle_read_tdsm(filename)
133-
elif ext in ['.xml', '.specan']:
134-
self.experimental_data = handle_read_rsa_specan_xml(filename)
135187
elif ext == '.npz':
136-
if 'spectrum' in base:
137-
self.experimental_data = handle_spectrumnpz_data(filename, **self.io_params)
138-
else:
139-
self.experimental_data = handle_tiqnpz_data(filename, **self.io_params)
188+
self.experimental_data = handle_spectrumnpz_data(filename, **self.io_params)
189+
#if 'spectrum' in base:
190+
# self.experimental_data = handle_spectrumnpz_data(filename, **self.io_params)
191+
#else:
192+
# self.experimental_data = handle_tiqnpz_data(filename, **self.io_params)
140193
elif ext == '.root':
141194
raise ValueError("ROOT files are not supported in this version. Please convert to NPZ/CSV.")
142195

@@ -166,7 +219,7 @@ def detect_peaks_and_widths(self):
166219
amp,
167220
height=height_thresh,
168221
distance=self.min_distance,
169-
prominence=height_thresh * 0.3,
222+
prominence=height_thresh * 0.2,
170223
width=1
171224
)
172225

@@ -257,12 +310,12 @@ def _load_experimental_data(self):
257310
else:
258311
raise FileNotFoundError("Cached data file not found. Please set reload_data to True to generate it.")
259312

260-
def _set_particles_to_simulate_from_file(self, particles_to_simulate, verbose=None):
313+
def _set_particles_to_simulate_from_file(self, particles_to_simulate):
261314
"""Parses the LISE++ output file."""
262315
self.ame = AMEData()
263316
self.ame_data = self.ame.ame_table
264317
lise = LISEreader(particles_to_simulate)
265-
self.particles_to_simulate = lise.get_info_all(verbose=verbose)
318+
self.particles_to_simulate = lise.get_info_all()
266319

267320
def _calculate_moqs(self, particles = None):
268321
"""Calculates mass-to-charge ratios for all particles."""
@@ -299,7 +352,8 @@ def _calculate_srrf(self, fref=None, brho=None, ke=None, gam=None, correct=None)
299352
self.srrf = array([1 - self.alphap * (self.moq[name] - self.moq[self.ref_ion]) / self.moq[self.ref_ion]
300353
for name in self.moq])
301354
if correct:
302-
self.srrf = self.srrf + polyval(array(correct), self.srrf * self.ref_frequency) / self.ref_frequency
355+
correction = polyval(array(correct), self.srrf * self.ref_frequency)
356+
self.srrf = self.srrf + correction / self.ref_frequency
303357

304358
def _simulated_data(self, brho=None, harmonics=None, mode=None, sim_scalingfactor=None, nions=None):
305359
"""Generates the final simulation dictionary for plotting."""
@@ -328,15 +382,15 @@ def _simulated_data(self, brho=None, harmonics=None, mode=None, sim_scalingfacto
328382

329383
self.nuclei_names = array(moq_keys)
330384
self.yield_data = np.array(self.yield_data, dtype=float)
385+
max_yield = np.max(self.yield_data)
386+
if max_yield > 0:
387+
self.yield_data /= max_yield
388+
331389
if sim_scalingfactor:
332390
self.yield_data *= sim_scalingfactor
333391

334392
for harmonic in harmonics:
335-
if mode == 'Frequency':
336-
harmonic_freq = self.srrf * self.ref_frequency
337-
else:
338-
harmonic_freq = self.srrf * self.ref_frequency * harmonic
339-
393+
harmonic_freq = self.srrf * self.ref_frequency * harmonic
340394
arr_stack = stack((harmonic_freq, self.yield_data, self.nuclei_names), axis=1)
341395
self.simulated_data_dict[f'{harmonic}'] = arr_stack
342396

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
# This copy of Barion is bundled with RionID.
1+
# This copy of lisereader is bundled with RionID.
22
# Original Repository @Github: https://github.com/gwgwhc/lisereader
33
# License: GPL-3.0

src/rionid/external/lisereader/reader.py

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
from rionid.external.barion.amedata import AMEData
33
from re import sub
44

5-
65
class LISEreader:
76
def __init__(self, filename):
87
ame = AMEData()
@@ -108,25 +107,4 @@ def float_check(value): #returns float if not string
108107
if value.replace('.','').replace('e-','').replace('e+','').isdigit():
109108
return float(value)
110109
else:
111-
return value
112-
113-
# ================== testing =====================
114-
115-
def test1():
116-
print(f"get_info(\'80Kr\'): {lise_data.get_info('80Kr')}")
117-
118-
def test2():
119-
print(f'get_info_all() snippet[:3]: {lise_data.get_info_all()[:3]}')
120-
121-
def test3():
122-
print(f"get_info_specific([0,1,10]) snippet[:3]: {lise_data.get_info_specific([0,1,10])[:3]}")
123-
124-
if __name__ == '__main__':
125-
filename = 'E143_TEline-ESR-72Ge.lpp'
126-
lise_data = LISEreader(filename)
127-
try:
128-
test1()
129-
test2()
130-
test3()
131-
except:
132-
raise
110+
return value

src/rionid/gui/app.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import sys
2-
from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QSplitter, QDesktopWidget)
2+
from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QSplitter)
33
from PyQt5.QtCore import Qt
44
import logging as log
55

@@ -31,15 +31,20 @@ def __init__(self):
3131
super().__init__()
3232
self.setWindowTitle("RionID")
3333

34-
# Center and Size Window based on screen resolution
35-
width = QDesktopWidget().screenGeometry(-1).width()
36-
height = QDesktopWidget().screenGeometry(-1).height()
37-
self.setGeometry(100, 100, width, height)
34+
screen = QApplication.primaryScreen()
35+
screen_geom = screen.availableGeometry()
36+
width = int(screen_geom.width() * 0.8)
37+
height = int(screen_geom.height() * 0.8)
38+
39+
# Center the window
40+
x = (screen_geom.width() - width) // 2
41+
y = (screen_geom.height() - height) // 2
42+
self.setGeometry(x, y, width, height)
3843

3944
# Create a QSplitter to hold both the input and the visualization
4045
splitter = QSplitter(Qt.Horizontal)
4146

42-
# 1. Create Visualization Widget FIRST
47+
# Create Visualization Widget FIRST
4348
# We must create this first because rion_input needs a reference to it
4449
# to handle the 'Pick' cursor events.
4550
self.visualization_widget = CreatePyGUI()

0 commit comments

Comments
 (0)