5 Commits
dev ... master

Author SHA1 Message Date
5975c08fb2 changing default order of hdf works with node names and displays correct label (#310)
All checks were successful
Build AppImage / Explore-Gitea-Actions (push) Successful in 2m31s
Fix problem with incorrect selection of label and group of HDF files when using not default order

Co-authored-by: Dominik Demuth <dominik.demuth@physik.tu-darmstadt.de>
Reviewed-on: #310
2025-06-03 17:53:48 +00:00
47feebe22f use C function and integration for anisotropic diffusion
All checks were successful
Build AppImage / Explore-Gitea-Actions (push) Successful in 2m18s
2025-05-02 11:35:01 +00:00
11fe47bef1 add sigmoid function to basic models (#307)
All checks were successful
Build AppImage / Explore-Gitea-Actions (push) Successful in 2m13s
fixes #306

Co-authored-by: Dominik Demuth <dominik.demuth@physik.tu-darmstadt.de>
Reviewed-on: #307
2025-02-11 17:31:00 +00:00
f33643955b 304-fitparameter (#305)
Some checks failed
Build AppImage / Explore-Gitea-Actions (push) Has been cancelled
fixes #304

Co-authored-by: Dominik Demuth <dominik.demuth@physik.tu-darmstadt.de>
Reviewed-on: #305
2025-01-23 18:11:45 +00:00
41353b9a54 wide-line spectra handle missing x values better (#303)
All checks were successful
Build AppImage / Explore-Gitea-Actions (push) Successful in 2m37s
see issue #302

Co-authored-by: Dominik Demuth <dominik.demuth@physik.tu-darmstadt.de>
Reviewed-on: #303
2024-12-09 13:45:07 +00:00
9 changed files with 101 additions and 19 deletions

View File

@ -133,7 +133,7 @@ class QAsciiReader(QtWidgets.QDialog, Ui_ascii_reader):
if self.column_checkBox.isChecked() and self.line_spinBox.isEnabled(): if self.column_checkBox.isChecked() and self.line_spinBox.isEnabled():
header_line = self.reader.header[self.line_spinBox.value()-1] header_line = self.reader.header[self.line_spinBox.value()-1]
header_line = header_line.strip('\n\t\r, ') header_line = header_line.strip('\n\t\r, ')
header_line = re.sub(r'[\t ;,]+', ';', header_line) header_line = re.sub(r'[\t, ;]+(?!\w*})', ';', header_line)
self.ascii_table.setHorizontalHeaderLabels(header_line.split(';')) self.ascii_table.setHorizontalHeaderLabels(header_line.split(';'))

View File

@ -0,0 +1,16 @@
/* integrands used in quadrature integration with scipy's LowLevelCallables */
#include <math.h>
double anistropicDiffusion(double x, void *user_data) {
double *c = (double *)user_data;
double q = c[0];
double t = c[1];
double d_perp = c[2];
double d_par = c[3];
double cos_theta = cos(x);
double sin_theta = sin(x);
return exp(-q * q * t * (d_par * cos_theta * cos_theta + d_perp * sin_theta * sin_theta)) * sin_theta;
}

BIN
src/nmreval/clib/diffusion.so Executable file

Binary file not shown.

View File

@ -5,6 +5,17 @@ from ctypes import CDLL, c_double, c_void_p
from ..lib.logger import logger from ..lib.logger import logger
diffusion_lib = None
try:
diffusion_lib = CDLL(str(Path(__file__).parents[1] / 'clib' / 'diffusion.so'))
diffusion_lib.anistropicDiffusion.restype = c_double
diffusion_lib.anistropicDiffusion.argtypes = (c_double, c_void_p)
HAS_C_FUNCS = True
except OSError:
HAS_C_FUNCS = False
lib = None lib = None
try: try:
lib = CDLL(str(Path(__file__).parents[1] / 'clib' / 'integrate.so')) lib = CDLL(str(Path(__file__).parents[1] / 'clib' / 'integrate.so'))
@ -39,10 +50,8 @@ try:
lib.energyDistSuscImag.restype = c_double lib.energyDistSuscImag.restype = c_double
lib.energyDistSuscImag.argtypes = (c_double, c_void_p) lib.energyDistSuscImag.argtypes = (c_double, c_void_p)
HAS_C_FUNCS = True HAS_C_FUNCS = True
logger.info('Use C functions') logger.info('Use C functions')
except OSError: except OSError:
HAS_C_FUNCS = False HAS_C_FUNCS = False
logger.info('Use python functions') logger.info('Use python functions')

View File

@ -49,7 +49,7 @@ class AsciiReader:
with self.fname.open('r') as f: with self.fname.open('r') as f:
for i, line in enumerate(islice(f, len(self.header)+len(self.lines), num_lines)): for i, line in enumerate(islice(f, len(self.header)+len(self.lines), num_lines)):
line = line.strip('\n\t\r, ') line = line.strip('\n\t\r, ')
line = re.sub(r'[\t ;,]+', ';', line) line = re.sub(r'[\t, ;]+(?!\w*})', ';', line)
line = line.split(';') line = line.split(';')
try: try:
@ -146,10 +146,11 @@ class AsciiReader:
raw_data = raw_data.reshape((1, *raw_data.shape)) raw_data = raw_data.reshape((1, *raw_data.shape))
if len(x) == 0 or raw_data.shape[2] == 1: if len(x) == 0 or raw_data.shape[2] == 1:
_temp = np.zeros((raw_data.shape[0], raw_data.shape[1], raw_data.shape[2]+1)) raw_data = raw_data.reshape(raw_data.shape[0], raw_data.shape[2], raw_data.shape[1])
_temp[:, :, 0] = np.arange(raw_data.shape[1]) # _temp = np.zeros((raw_data.shape[0], raw_data.shape[2], raw_data.shape[1]))
_temp[:, :, 1:] = raw_data # _temp[:, :, 0] = np.arange(raw_data.shape[1])
raw_data = _temp # _temp[:, :, 1:] = raw_data
# raw_data = _temp
if y: if y:
y = [i+1 for i in y] y = [i+1 for i in y]

View File

@ -302,15 +302,26 @@ class HdfReader(HdfNode):
def make_signal(self, node, flag: str = 'fid', value: str = None, group: str = None): def make_signal(self, node, flag: str = 'fid', value: str = None, group: str = None):
if value is None: if value is None:
data_name = node.name
value = self._get_parameter_values(node, node.parameter) value = self._get_parameter_values(node, node.parameter)
else: else:
try: try:
data_name = f"{value}={node.parameter[value]}"
value = node.parameter[value] value = node.parameter[value]
except KeyError: except KeyError:
print(node.title_parameter)
try: try:
value = node.title_parameter[1][value] temp = node
while value != temp.title_parameter[0][0]:
if temp.parent is None:
break
temp = temp.parent
value = temp.title_parameter[0][1]
data_name = temp.name
except KeyError: except KeyError:
print(f'{value} is not a valid key for {node.name}') print(f'{value} is not a valid key for {node.name}')
data_name = node.name
value = None value = None
if group is None: if group is None:
@ -343,11 +354,11 @@ class HdfReader(HdfNode):
dw = float(index['dwelltime']) dw = float(index['dwelltime'])
if flag == 'fid': if flag == 'fid':
x = np.arange(len(y)) * dw x = np.arange(len(y)) * dw
ret = FID(x, y, name=node.name, value=value, group=group, filename=self.file.filename) ret = FID(x, y, name=data_name, value=value, group=group, filename=self.file.filename)
elif flag == 'spectrum': elif flag == 'spectrum':
x = np.linspace(-1/dw, 1/dw, num=len(y)) x = np.linspace(-1/dw, 1/dw, num=len(y))
ret = Spectrum(x, y, name=node.name, value=value, group=group, filename=self.file.filename) ret = Spectrum(x, y, name=data_name, value=value, group=group, filename=self.file.filename)
else: else:
raise ValueError(f'{flag} unknown, use `fid` or `spectrum`.') raise ValueError(f'{flag} unknown, use `fid` or `spectrum`.')

View File

@ -203,6 +203,31 @@ class Sinc:
return c * np.sinc(((x-x0)/w)/np.pi) return c * np.sinc(((x-x0)/w)/np.pi)
class Sigmoid:
type = 'Basic'
name = 'Sigmoid'
equation = 'C / [1 + exp(-a * (x - x_{0})] + y_{0}'
params = ['C', 'a', 'x_{0}', 'y_{0}']
@staticmethod
def func(x, c, a, x0, y0):
"""
Sigmoid function
.. math::
y = C / [1 + exp(-a * (x - x_0))] + y_0
Args:
x (array_like): Input values
c (float): Prefactor
a (float): Steepness of the sigmoid
x0 (float): x position of the sigmoid's midpoint
y0 (float): y position of the sigmoid's midpoint
"""
return c / (1 + np.exp(-a * (x - x0))) + y0
class Sine: class Sine:
""" """
Wavy sine function Wavy sine function

View File

@ -1,7 +1,11 @@
from ctypes import c_double, cast, c_void_p, pointer
import numpy as np import numpy as np
from scipy import special as special from scipy import special as special, LowLevelCallable
from scipy.integrate import quad
from ..utils import gamma from ..utils import gamma
from nmreval.distributions.helper import HAS_C_FUNCS, diffusion_lib
class Diffusion: class Diffusion:
@ -103,20 +107,36 @@ class AnisotropicDiffusion(object):
tp = x tp = x
relax = np.exp(-(tp/trel)**brel)*np.exp(-(tp/trel)**brel) relax = np.exp(-(tp/trel)**brel)*np.exp(-(tp/trel)**brel)
q_squared = np.power(g * nucleus * tp, 2) q = g * nucleus * tp
t = 2 * tp / 3 + tm t = 2 * tp / 3 + tm
z = np.sqrt(q_squared * (d_par - d_perp) * t)
# Callaghan eq (6.89) # Callaghan eq (6.89)
diffs = np.exp(-q_squared*t*d_perp) * special.erf(z) / z if HAS_C_FUNCS:
# divide by 2 to normalize by integral sin(x), x=0..pi
diffusion_decay = AnisotropicDiffusion._integrate_c(q, t, d_perp, d_par) / 2
else:
z = np.sqrt(q**2 * (d_par - d_perp) * t)
diffusion_decay = np.exp(-q**2 * t * d_perp) * special.erf(z) / z
return m0 * diffs * relax return m0 * diffusion_decay * relax
@staticmethod
def _integrate_c(q, t, d_perp, d_par) -> np.ndarray:
diffusion_decay = np.zeros_like(t)
for (i, t_i) in enumerate(t):
c = (c_double * 4)(q, t_i, d_perp, d_par)
user_data = cast(pointer(c), c_void_p)
diffusion_decay[i] = quad(LowLevelCallable(diffusion_lib.anistropicDiffusion, user_data), 0, np.pi, epsabs=1e-13)[0]
return diffusion_decay
class Peschier: class Peschier:
name = 'Diffusion + Cross-Relaxation' name = 'Diffusion + Cross-Relaxation'
type = 'Diffusion' type = 'Diffusion'
equation = r'Diffusion with cross-relax f(ast) \rightarrow s(low)' equation = r'Diffusion with cross-relax f(ast) \rightarrow s(low)'
params = ['M_{0}', 'D', 'T_{1,f}', 'T_{1,s}', 'k', 'p_{f}', 't_{ev}', 'g'] params = ['M_{0}', 'D', 'T_{1f}', 'T_{1s}', 'k', 'p_{f}', 't_{ev}', 'g']
bounds = [(0, None), (0, None), (0, None), (0, None), (0, None), (0, None)] bounds = [(0, None), (0, None), (0, None), (0, None), (0, None), (0, None)]
choices = [(r'\gamma', 'nucleus', gamma)] choices = [(r'\gamma', 'nucleus', gamma)]

View File

@ -75,7 +75,7 @@ class TwoSatRecAbsolute:
type = 'Relaxation' type = 'Relaxation'
name = 'Two-step relaxation (abs. int)' name = 'Two-step relaxation (abs. int)'
equation = r'M_{0} + \Sigma \DeltaM_{i}(1-exp(-(x/T_{1,i})^{\beta_{i}}))' equation = r'M_{0} + \Sigma \DeltaM_{i}(1-exp(-(x/T_{1,i})^{\beta_{i}}))'
params = [r'\DeltaM_{1}', 'T_{1,1}', r'\beta_{1}', r'\DeltaM_{2}', 'T_{1,2}', r'\beta_{2}', 'M_{0}'] params = [r'\DeltaM_{1}', 'T_{11}', r'\beta_{1}', r'\DeltaM_{2}', 'T_{12}', r'\beta_{2}', 'M_{0}']
choices = [('Type', 'is_inv', {'Saturation': False, 'Inversion': True})] choices = [('Type', 'is_inv', {'Saturation': False, 'Inversion': True})]
bounds = [(0, None), (0, None), (0, 1), (0, None), (0, None), (0, 1), (None, None)] bounds = [(0, None), (0, None), (0, 1), (0, None), (0, None), (0, 1), (None, None)]
@ -92,7 +92,7 @@ class TwoSatRecRelative:
name = 'Two-step relaxation (rel. int)' name = 'Two-step relaxation (rel. int)'
equation = r'M_{0} + \DeltaM[R(1-exp(-(x/T_{1,1})^{\beta_{1}})) + \n'\ equation = r'M_{0} + \DeltaM[R(1-exp(-(x/T_{1,1})^{\beta_{1}})) + \n'\
r'(1-R)(1-exp(-(x/T_{1,2})^{\beta_{2}}))]' r'(1-R)(1-exp(-(x/T_{1,2})^{\beta_{2}}))]'
params = [r'\DeltaM', 'M_{0}', 'T_{1,1}', r'\beta_{1}', 'T_{1,2}', r'\beta_{2}', 'R'] params = [r'\DeltaM', 'M_{0}', 'T_{11}', r'\beta_{1}', 'T_{12}', r'\beta_{2}', 'R']
choices = [('Type', 'kind', {'Saturation': 'sat', 'Inversion': 'inv'})] choices = [('Type', 'kind', {'Saturation': 'sat', 'Inversion': 'inv'})]
bounds = [(0, None), (None, None), (0, None), (0, 1), (0, None), (0, 1), (0, 1)] bounds = [(0, None), (None, None), (0, None), (0, 1), (0, None), (0, 1), (0, 1)]