diff --git a/.gitignore b/.gitignore index 1b03ab48d..b7172846e 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ cloc-1.62.pl *.so.dSYM *.ui.autosave *.coverage +smooth_bruckner_cython.c log.txt .idea @@ -28,6 +29,8 @@ _build/ _version.py install.sh analysis +.pytest_cache +libsmooth_b* # Build results @@ -178,4 +181,4 @@ $RECYCLE.BIN/ # Mac desktop service store files .DS_Store -\.vscode/ +.vscode/ diff --git a/.travis.yml b/.travis.yml index 92f0fe239..20a448456 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,38 +5,14 @@ language: python sudo: required python: - - 2.7 - 3.6 before_install: - - echo $TRAVIS_PYTHON_VERSION - - if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then - if [ ! -d "/home/travis/miniconda2/bin" ]; then - rm -rf /home/travis/miniconda2; - wget http://repo.continuum.io/miniconda/Miniconda2-latest-Linux-x86_64.sh -O miniconda.sh; - chmod +x miniconda.sh; - ./miniconda.sh -b; - fi - fi - - if [ "$TRAVIS_PYTHON_VERSION" == "3.6" ]; then - if [ ! -d "/home/travis/miniconda3/bin" ]; then - rm -rf /home/travis/miniconda3; - wget http://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; - chmod +x miniconda.sh; - ./miniconda.sh -b; - fi - fi - - - if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then - export PATH=/home/travis/miniconda2/bin:$PATH; - else - export PATH=/home/travis/miniconda3/bin:$PATH; - fi - - - if [ "$TRAVIS_PYTHON_VERSION" == "3.6" ]; then - conda install --yes python=3.6; - fi - + - wget http://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh + - chmod +x miniconda.sh + - ./miniconda.sh -b + - export PATH=/home/travis/miniconda3/bin:$PATH + - conda install --yes python=3.6; - conda update --yes conda - export PYTHONPATH=$PWD/dioptas:$PYTHONPATH @@ -51,8 +27,7 @@ addons: - libgfortran3 install: - - conda install --yes numpy pillow pycifrw scipy pandas matplotlib python-dateutil nose h5py pyqt scikit-image cython future qtpy pyfai fabio lmfit mock pytest pytest-cov -c conda-forge - - conda install --yes pyqtgraph -c cprescher + - conda install --yes pycifrw pandas python-dateutil h5py scikit-image future qtpy pyfai lmfit mock pytest pytest-cov pyqtgraph -c cprescher - pip install pyepics - cd dioptas/model/util/ diff --git a/Dioptas.spec b/Dioptas.spec index 514d67c66..60a008ead 100644 --- a/Dioptas.spec +++ b/Dioptas.spec @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -47,12 +49,46 @@ extra_datas = [ binaries = [] +fabio_hiddenimports = [ + "fabio.edfimage", + "fabio.adscimage", + "fabio.tifimage", + "fabio.marccdimage", + "fabio.mar345image", + "fabio.fit2dmaskimage", + "fabio.brukerimage", + "fabio.bruker100image", + "fabio.pnmimage", + "fabio.GEimage", + "fabio.OXDimage", + "fabio.dm3image", + "fabio.HiPiCimage", + "fabio.pilatusimage", + "fabio.fit2dspreadsheetimage", + "fabio.kcdimage", + "fabio.cbfimage", + "fabio.xsdimage", + "fabio.binaryimage", + "fabio.pixiimage", + "fabio.raxisimage", + "fabio.numpyimage", + "fabio.eigerimage", + "fabio.hdf5image", + "fabio.fit2dimage", + "fabio.speimage", + "fabio.jpegimage", + "fabio.jpeg2kimage", + "fabio.mpaimage", + "fabio.mrcimage" +] + a = Analysis(['Dioptas.py'], pathex=[folder], binaries=binaries, datas=extra_datas, hiddenimports=['scipy.special._ufuncs_cxx', 'scipy._lib.messagestream', 'skimage._shared.geometry', - 'h5py.defs', 'h5py.utils', 'h5py.h5ac', 'h5py', 'h5py._proxy'], + 'h5py.defs', 'h5py.utils', 'h5py.h5ac', 'h5py', 'h5py._proxy', 'pywt._extensions._cwt'] + + fabio_hiddenimports, hookspath=[], runtime_hooks=[], excludes=['PyQt4', 'PySide'], diff --git a/README.md b/README.md index b5c54f6c3..1af7ec683 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Clemens Prescher (clemens.prescher@gmail.com) Requirements ------------ - * python 2.7/3.5 + * python 3.4-3.7 * qtpy with PyQt5/PyQt4/PySide * numpy * scipy @@ -37,9 +37,7 @@ Installation Executable versions for Windows, Mac OsX and Linux (all 64bit) can be downloaded from: -[https://uni-koeln.sciebo.de/index.php/s/rS4RkVjU6guQeRP](https://uni-koeln.sciebo.de/index.php/s/rS4RkVjU6guQeRP) -or -[http://millenia.cars.aps.anl.gov/gsecars/data/Dioptas/](http://millenia.cars.aps.anl.gov/gsecars/data/Dioptas/) +https://github.com/Dioptas/Dioptas/releases The executable versions are self-contained folders, which do not need any python installation. @@ -48,21 +46,25 @@ The executable versions are self-contained folders, which do not need any python In order to make changes to the source code yourself or always get the latest development versions you need to install the required python packages on your machine. -The easiest way to install the all the dependencies for Dioptas is to use the Anaconda 64bit Python 3 distribution. -Please download it from https://www.continuum.io/downloads. After having the added the scripts to you path (or use the +The easiest way to install the all the dependencies for Dioptas is to use the Anaconda (or miniconda) 64bit Python 3 distribution. +Please download it from https://www.continuum.io/downloads. After the installer added the scripts to your path (or use the Anaconda prompt on windows) please run the following commands on the commandline: ```bash -conda install --yes python=3.5 numpy pillow pycifrw scipy pandas matplotlib python-dateutil nose h5py pyqt scikit-image cython future qtpy fabio lmfit mock -c conda-forge -conda install --yes pyqtgraph pyfai -c cprescher -pip install pyepics +conda install --yes dioptas pyfai -c cprescher ``` +and then run Dioptas by typing: +```bash +dioptas +``` +in the commandline. + -Running the Program ------------------- +Running the Program from source +------------------------------- -You can start the program by running the Dioptas.py script in the dioptas folder by: +You can start the program by running the Dioptas.py script in the dioptas folder: ```bash python Dioptas.py diff --git a/changelog.rst b/changelog.rst index f94b687f2..dbc6c3a36 100644 --- a/changelog.rst +++ b/changelog.rst @@ -1,3 +1,34 @@ +0.5.0 (stable 03/05/2019) +------------------------- + +New features: + - Added the capability of using detector distortion correction defined by spline files generated from Fit2D (please + see the calibration parameters) + - PONI and rotation parameters can now be fixed during the calibration + - redesigned parts of the GUI: (1) the top controls in the Integration view no adapt to the used width, and split + into two groups to make use of the space. (2) Overlay and Phase Control Widgets now are mainly controlled by + buttons and the important parameter can be changed for each item individually in the table. (3) There is now a + a different view mode for the integration view, where image and integrated pattern are shown on the left, and all + control panels are on the right. This can be activated by using the change view button on the lower left. + - the cBN Seat Correction and Detector Incidence Absoprtion Correction Controls have been redesigned + - Added the option to use a transfer correction for image intensities. Please see the Cor tab in the integration + view + - azimuthal bins and azimuthal range for the cake integration can now be adjusted manually in the X tab + - the cake image can now be exported (press the save button below the image) + - auto-extracted Pattern background can now be saved as file or later reused as overlay + - the background subtraction algorithm is now also rewritten in cython, which should make deployment easier + - a button was added to undo the last peak selection in the calibration tab + - the jcpds editor now also shows q-values for each line + +Bug fixes: + - cosmic removal in the mask panel is now working again + - changing the radial bins in the X tab in the integration view works now correctly again + - phases with trigonal symmetry should now work correctly + - saved background range should now correctly restore after restarting Dioptas + - browsing files works now correctly from 10 to 9 without leading zeros + - loading a *.poni file prior to an image will not result in an error message anymore + + 0.4.1 (stable 12/22/2017) ------------------------- diff --git a/conda_scripts/construct.yaml b/conda_scripts/construct.yaml new file mode 100644 index 000000000..8bd20eacc --- /dev/null +++ b/conda_scripts/construct.yaml @@ -0,0 +1,47 @@ +name: dioptas +version: 0.4.1.post1 + +channels: + - http://repo.continuum.io/pkgs/main + - http://repo.continuum.io/pkgs/free + - http://repo.continuum.io/pkgs/msys2 [win] + - https://conda.anaconda.org/cprescher/ + +specs: + - python + - conda + - anaconda + - setuptools + - pip + - console_shortcut # [win] + - python.app # [osx] + - numpy + - scipy + - pyqt + - pyqtgraph + - qtpy + - future + - libgfortran # [linux64 or osx] + - h5py + - matplotlib + - six + - pillow + - pywin32 # [win64 or win32] + - yaml + - pyyaml + - requests + - scikit-image + - pycifrw + - fabio>=0.6.0 + - pyfai>=0.15.0 + - lmfit + - dioptas + +## someday, hopefully! +## installer_type: pkg # [osx] + +post_install: post_install_unix.sh # [linux64 or osx] +post_install: post_install_windows.bat # [win] + +license_file: ../license.txt +welcome_image: ../dioptas/resources/icons/icon.png # [win] diff --git a/conda_scripts/post_install_unix.sh b/conda_scripts/post_install_unix.sh new file mode 100644 index 000000000..bb6cdf9ce --- /dev/null +++ b/conda_scripts/post_install_unix.sh @@ -0,0 +1,3 @@ +#/bin/bash + +$PREFIX/bin/dioptas makeshortcut diff --git a/conda_scripts/post_install_windows.bat b/conda_scripts/post_install_windows.bat new file mode 100644 index 000000000..c5fdd5e2d --- /dev/null +++ b/conda_scripts/post_install_windows.bat @@ -0,0 +1,3 @@ +;; post install for windows + +%PREFIX%\bin\dioptas makeshortcut diff --git a/conda_scripts/recipe/meta.yaml b/conda_scripts/recipe/meta.yaml new file mode 100644 index 000000000..1fb56a940 --- /dev/null +++ b/conda_scripts/recipe/meta.yaml @@ -0,0 +1,56 @@ + +package: + name: dioptas + version: 0.4.1.post1 + +source: + git_rev: develop + git_url: https://github.com/newville/Dioptas + +build: + number: 0 + script: python setup.py install + +requirements: + build: + - python + - setuptools + - numpy + - scipy + - pyqt + - pyqtgraph + - python.app # [osx] + - libgfortran # [linux64 or osx] + - patchelf # [linux] + - lmfit + - fabio # [linux64 or osx] + - pyfai # [linux64 or osx] + - pycifrw + - future + - scikit-image + - qtpy + + run: + - python + - setuptools + - numpy + - scipy + - pyqt + - pyqtgraph + - python.app # [osx] + - lmfit + - fabio # [linux64 or osx] + - pyfai # [linux64 or osx] + - pycifrw + - future + - scikit-image + - qtpy + +test: + imports: + - dioptas + +about: + home: https://github.com/Dioptas/Dioptas + license: GPL3 + summary: GUI program for reduction and exploration of 2D X-ray diffraction data diff --git a/create_executable.bat b/create_executable.bat index 8f09ed597..0b8be5062 100644 --- a/create_executable.bat +++ b/create_executable.bat @@ -1,3 +1,53 @@ rmdir /s /q dist rmdir /s /q build -pyinstaller Dioptas.spec \ No newline at end of file + +pyinstaller Dioptas.spec + +cd dist/dioptas* + +rm mkl_avx512.dll, ^ + mkl_avx2.dll, ^ + mkl_avx.dll, ^ + mkl_mc3.dll, ^ + mkl_mc.dll, ^ + mkl_pgi_thread.dll, ^ + mkl_tbb_thread.dll, ^ + mkl_sequential.dll, ^ + mkl_vml_mc.dll, ^ + mkl_vml_mc2.dll, ^ + mkl_vml_mc3.dll, ^ + mkl_vml_avx.dll, ^ + mkl_vml_avx2.dll, ^ + mkl_vml_avx512.dll, ^ + mkl_scalapack_ilp64.dll, ^ + mkl_scalapack_lp64.dll, ^ + Qt5Quick.dll, ^ + Qt5Qml.dll + +rmdir /s /q bokeh, ^ + bottleneck, ^ + certify, ^ + cryptography, ^ + Cython, ^ + cytoolz, ^ + etc, ^ + docutils, ^ + gevent, ^ + jedi, ^ + lxml, ^ + nbconvert, ^ + nbformat, ^ + notebook, ^ + mpl-data, ^ + matplotlib, ^ + mkl-fft, ^ + msgpack, ^ + pandas, ^ + share, ^ + PyQt5\Qt\bin, ^ + PyQt5\Qt\uic + + +Dioptas.exe +cd .. +cd .. \ No newline at end of file diff --git a/dioptas/__init__.py b/dioptas/__init__.py index 5ec093525..c6df2e431 100644 --- a/dioptas/__init__.py +++ b/dioptas/__init__.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -24,7 +26,7 @@ del get_versions if __version__ == "0+unknown": - __version__ = "0.4.1" + __version__ = "0.5.0" import sys import os @@ -45,6 +47,7 @@ data_path = os.path.join(resources_path, 'data') style_path = os.path.join(resources_path, 'style') +from ._desktop_shortcuts import make_shortcut from .widgets.UtilityWidgets import ErrorMessageBox diff --git a/dioptas/_desktop_shortcuts.py b/dioptas/_desktop_shortcuts.py new file mode 100644 index 000000000..c9553a4e9 --- /dev/null +++ b/dioptas/_desktop_shortcuts.py @@ -0,0 +1,239 @@ +#!/usr/bin/env python +""" +Create desktop shortcuts to run Python scripts. +More specifically, this creates a desktop shortcut +for Windows or Linux, or a small App for MacOSX. + +""" + +from __future__ import print_function +import os +import sys +import shutil + + +def unixpath(d): + "unix path" + return d.replace('\\', '/') + +def winpath(d): + "ensure path uses windows delimiters" + if d.startswith('//'): + d = d[1:] + d = d.replace('/', '\\') + return d + +nativepath = unixpath +if os.name == 'nt': + nativepath = winpath + +def fix_anacondapy_pythonw(script): + """fix shebang line for scripts using anaconda python + to use 'pythonw' instead of 'python' + """ + # print(" fix anaconda py (%s) for %s" % (sys.prefix, script)) + fname = os.path.join(sys.prefix, 'bin', script) + with open(fname, 'r') as fh: + try: + lines = fh.readlines() + except IOError: + lines = ['-'] + firstline = lines[0][:-1].strip() + if firstline.startswith('#!') and 'python' in firstline: + firstline = '#!/usr/bin/env pythonw' + fh = open(fname, 'w') + fh.write('%s\n' % firstline) + fh.write("".join(lines[1:])) + fh.close() + +HAS_PWD = True +try: + import pwd +except ImportError: + HAS_PWD = False + +def get_homedir(): + "determine home directory" + home = None + def check(method, s): + "check that os.path.expanduser / expandvars gives a useful result" + try: + if method(s) not in (None, s): + return method(s) + except: + pass + return None + + # for Unixes, allow for sudo case + susername = os.environ.get("SUDO_USER", None) + if HAS_PWD and susername is not None and home is None: + home = pwd.getpwnam(susername).pw_dir + + # try expanding '~' -- should work on most Unixes + if home is None: + home = check(os.path.expanduser, '~') + + # try the common environmental variables + if home is None: + for var in ('$HOME', '$HOMEPATH', '$USERPROFILE', '$ALLUSERSPROFILE'): + home = check(os.path.expandvars, var) + if home is not None: + break + + # For Windows, ask for parent of Roaming 'Application Data' directory + if home is None and os.name == 'nt': + try: + from win32com.shell import shellcon, shell + home = shell.SHGetFolderPath(0, shellcon.CSIDL_APPDATA, 0, 0) + except ImportError: + pass + + # finally, use current folder + if home is None: + home = os.path.abspath('.') + return nativepath(home) + +## Desktop/Larch folder +homedir = get_homedir() +desktop = os.path.join(homedir, 'Desktop') + + +def make_shortcut_macosx(name, script, description='', + icon_path='.', icon=None, in_terminal=False): + """create minimal Mac App to run script""" + pyexe = sys.executable + if 'Anaconda' in sys.version: + pyexe = "{prefix:s}/python.app/Contents/MacOS/python".format(prefix=sys.prefix) + fix_anacondapy_pythonw(script) + dest = os.path.join(desktop, name + '.app') + if os.path.exists(dest): + shutil.rmtree(dest) + os.mkdir(dest) + os.mkdir(os.path.join(dest, 'Contents')) + os.mkdir(os.path.join(dest, 'Contents', 'MacOS')) + os.mkdir(os.path.join(dest, 'Contents', 'Resources')) + opts = dict(name=name, desc=description, script=script, + prefix=sys.prefix, pyexe=pyexe) + + info = """ + + + + CFBundleGetInfoString {desc:s} + CFBundleName {name:s} + CFBundleExecutable {name:s} + CFBundleIconFile {name:s} + CFBundlePackageType APPL + + +""" + + header = """#!/bin/bash +## Run script with Python that created this script +export PYTHONEXECUTABLE={prefix:s}/bin/python +export PY={pyexe:s} +export SCRIPT={prefix:s}/bin/{script:s} +""" + ## {pyexe:s} {prefix:s}/bin/{script:s} + text = "$PY $SCRIPT" + if in_terminal: + text = """ +osascript -e 'tell application "Terminal" to do script "'${{PY}}\ ${{SCRIPT}}'"' +""" + + with open(os.path.join(dest, 'Contents', 'Info.plist'), 'w') as fout: + fout.write(info.format(**opts)) + + script_name = os.path.join(dest, 'Contents', 'MacOS', name) + with open(script_name, 'w') as fout: + fout.write(header.format(**opts)) + fout.write(text.format(**opts)) + fout.write("\n") + + os.chmod(script_name, 493) ## = octal 755 / rwxr-xr-x + if icon is not None: + icon_dest = os.path.join(dest, 'Contents', 'Resources', name + '.icns') + icon_src = os.path.join(icon_path, icon + '.icns') + shutil.copy(icon_src, icon_dest) + +def make_shortcut_windows(name, script, description='', + icon_path='.', icon=None, in_terminal=False): + """create windows shortcut""" + from win32com.client import Dispatch + + pyexe = os.path.join(sys.prefix, 'python.exe') # could be pythonw? + if in_terminal: + pyexe = os.path.join(sys.prefix, 'python.exe') + target = os.path.join(sys.prefix, 'Scripts', script) + + # add several checks for valid ways to run each script, including + # accounting for Anaconda's automagic renaming and creation of exes. + target_exe = '%s.exe' % target + target_bat = '%s.bat' % target + target_spy = '%s-script.py' % target + + if os.path.exists(target_exe): + target = target_exe + elif os.path.exists(target_spy): + target = "%s %s" % (pyexe, target_spy) + elif os.path.exists(target): + fbat = open(target_bat, 'w') + fbat.write("""@echo off + +%s %%~dp0%%~n0 %%1 %%2 %%3 %%4 %%5 + + """ % (pyexe)) + fbat.close() + target = target_bat + + shortcut = Dispatch('WScript.Shell').CreateShortCut( + os.path.join(desktop, name) + '.lnk') + shortcut.Targetpath = target + shortcut.WorkingDirectory = homedir + shortcut.WindowStyle = 0 + shortcut.Description = description + if icon is not None: + shortcut.IconLocation = os.path.join(icon_path, icon + '.ico') + shortcut.save() + + +def make_shortcut_linux(name, script, description='', + icon_path='.', icon=None, in_terminal=False): + """create linux desktop app""" + buff = ['[Desktop Entry]'] + buff.append('Name=%s' % name) + buff.append('Type=Application') + buff.append('Comment=%s' % description) + if in_terminal: + buff.append('Terminal=true') + else: + buff.append('Terminal=false') + if icon: + buff.append('Icon=%s' % os.path.join(icon_path, '%s.ico' % icon)) + + buff.append('Exec=%s' % os.path.join(sys.prefix, 'bin', script)) + buff.append('') + + with open(os.path.join(desktop, '%s.desktop' % name), 'w') as fout: + fout.write("\n".join(buff)) + + +make_shortcut = None + +if sys.platform.startswith('win') or os.name == 'nt': + make_shortcut = make_shortcut_windows +elif sys.platform == 'darwin': + make_shortcut = make_shortcut_macosx +elif sys.platform == 'linux': + make_shortcut = make_shortcut_linux + +# def make_desktop_shortcuts(): +# """make desktop shortcuts with icons for current OS +# using definitions in APPS +# """ +# +# if maker is not None: +# for (name, script, description, iconfile, in_terminal) in APPS: +# maker(name, script, description=description, +# icon=iconfile, in_terminal=in_terminal) diff --git a/dioptas/controller/CalibrationController.py b/dioptas/controller/CalibrationController.py index 08e5b1209..d75797ea0 100755 --- a/dioptas/controller/CalibrationController.py +++ b/dioptas/controller/CalibrationController.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -28,6 +30,7 @@ from ..widgets.CalibrationWidget import CalibrationWidget from ..widgets.UtilityWidgets import open_file_dialog from ..model.DioptasModel import DioptasModel +from ..model.CalibrationModel import NotEnoughSpacingsInCalibrant class CalibrationController(object): @@ -77,6 +80,10 @@ def create_signals(self): self.widget.refine_btn.clicked.connect(self.refine) self.widget.clear_peaks_btn.clicked.connect(self.clear_peaks_btn_click) + self.widget.undo_peaks_btn.clicked.connect(self.undo_peaks_btn_clicked) + + self.widget.load_spline_btn.clicked.connect(self.load_spline_btn_click) + self.widget.spline_reset_btn.clicked.connect(self.reset_spline_btn_click) self.widget.f2_wavelength_cb.stateChanged.connect(self.wavelength_cb_changed) self.widget.pf_wavelength_cb.stateChanged.connect(self.wavelength_cb_changed) @@ -86,6 +93,12 @@ def create_signals(self): self.widget.pf_distance_cb.stateChanged.connect(self.distance_cb_changed) self.widget.sv_distance_cb.stateChanged.connect(self.distance_cb_changed) + self.widget.pf_poni1_cb.stateChanged.connect(self.poni1_cb_changed) + self.widget.pf_poni2_cb.stateChanged.connect(self.poni2_cb_changed) + self.widget.pf_rot1_cb.stateChanged.connect(self.rot1_cb_changed) + self.widget.pf_rot2_cb.stateChanged.connect(self.rot2_cb_changed) + self.widget.pf_rot3_cb.stateChanged.connect(self.rot3_cb_changed) + self.widget.use_mask_cb.stateChanged.connect(self.plot_mask) self.widget.mask_transparent_cb.stateChanged.connect(self.mask_transparent_status_changed) @@ -156,7 +169,8 @@ def load_img(self): Loads an image file. """ filename = open_file_dialog(self.widget, caption="Load Calibration Image", - directory=self.model.working_directories['image']) + directory=self.model.working_directories['image'], + ) if filename is not '': self.model.working_directories['image'] = os.path.dirname(filename) @@ -238,12 +252,13 @@ def load_calibrant(self, wavelength_from='start_values'): np.array(self.model.calibration_model.calibrant.get_2th()) / np.pi * 180, '2th_deg', integration_unit, wavelength) # filter them to only show the ones visible with the current pattern - pattern_min = np.min(self.model.pattern.x) - pattern_max = np.max(self.model.pattern.x) - calibrant_line_positions = calibrant_line_positions[calibrant_line_positions>pattern_min] - calibrant_line_positions = calibrant_line_positions[calibrant_line_positions 0: + pattern_min = np.min(self.model.pattern.x) + pattern_max = np.max(self.model.pattern.x) + calibrant_line_positions = calibrant_line_positions[calibrant_line_positions > pattern_min] + calibrant_line_positions = calibrant_line_positions[calibrant_line_positions < pattern_max] + self.widget.pattern_widget.plot_vertical_lines(positions=calibrant_line_positions, + name=self._calibrants_file_names_list[current_index]) def set_calibrant(self, index): """ @@ -318,6 +333,28 @@ def clear_peaks_btn_click(self): self.widget.img_widget.clear_scatter_plot() self.widget.peak_num_sb.setValue(1) + def undo_peaks_btn_clicked(self): + """ + undoes clicked peaks + """ + num_points = self.model.calibration_model.remove_last_peak() + self.widget.img_widget.remove_last_scatter_points(num_points) + if self.widget.automatic_peak_num_inc_cb.isChecked(): + self.widget.peak_num_sb.setValue(self.widget.peak_num_sb.value() - 1) + + def load_spline_btn_click(self): + filename = open_file_dialog(self.widget, caption="Load Distortion Spline File", + directory=self.model.working_directories['image'], + filter='*.spline') + + if filename is not '': + self.model.calibration_model.load_distortion(filename) + self.widget.spline_filename_txt.setText(os.path.basename(filename)) + + def reset_spline_btn_click(self): + self.model.calibration_model.reset_distortion_correction() + self.widget.spline_filename_txt.setText('None') + def wavelength_cb_changed(self, value): """ Sets the fit_wavelength parameter in the calibration data according to the GUI state. @@ -354,6 +391,21 @@ def distance_cb_changed(self, value): self.model.calibration_model.fit_distance = value + def poni1_cb_changed(self, value): + self.model.calibration_model.fit_poni1 = value + + def poni2_cb_changed(self, value): + self.model.calibration_model.fit_poni2 = value + + def rot1_cb_changed(self, value): + self.model.calibration_model.fit_rot1 = value + + def rot2_cb_changed(self, value): + self.model.calibration_model.fit_rot2 = value + + def rot3_cb_changed(self, value): + self.model.calibration_model.fit_rot3 = value + def calibrate(self): """ Performs calibration based on the previously inputted/searched peaks and start values. @@ -384,9 +436,9 @@ def create_progress_dialog(self, text_str, abort_str, end_value, show_cancel_btn self.widget) progress_dialog.move(int(self.widget.tab_widget.x() + self.widget.tab_widget.size().width() / 2.0 - \ - progress_dialog.size().width() / 2.0), + progress_dialog.size().width() / 2.0), int(self.widget.tab_widget.y() + self.widget.tab_widget.size().height() / 2.0 - - progress_dialog.size().height() / 2.0)) + progress_dialog.size().height() / 2.0)) progress_dialog.setWindowTitle(' ') progress_dialog.setWindowModality(QtCore.Qt.WindowModal) @@ -444,8 +496,15 @@ def refine(self): refinement_canceled = False for i in range(num_rings - 2): - points = self.model.calibration_model.search_peaks_on_ring(i + 2, delta_tth, intensity_min_factor, - intensity_max, mask) + try: + points = self.model.calibration_model.search_peaks_on_ring(i + 2, delta_tth, intensity_min_factor, + intensity_max, mask) + except NotEnoughSpacingsInCalibrant: + QtWidgets.QMessageBox.critical(self.widget, + 'Not enough d-spacings!.', + 'The calibrant file does not contain enough d-spacings.', + QtWidgets.QMessageBox.Ok) + break self.widget.peak_num_sb.setValue(i + 4) if len(self.model.calibration_model.points): self.plot_points(points) @@ -477,7 +536,8 @@ def load_calibration(self): if filename is not '': self.model.working_directories['calibration'] = os.path.dirname(filename) self.model.calibration_model.load(filename) - self.update_all() + if self.model.img_model.filename != '': + self.update_all() def plot_mask(self): """ @@ -535,7 +595,7 @@ def update_all(self, integrate=True): self.widget.tab_widget.setCurrentIndex(1) if self.widget.ToolBox.currentIndex() is not 2 or \ - self.widget.ToolBox.currentIndex() is not 3: + self.widget.ToolBox.currentIndex() is not 3: self.widget.ToolBox.setCurrentIndex(2) self.update_calibration_parameter_in_view() self.load_calibrant('pyFAI') @@ -548,6 +608,12 @@ def update_calibration_parameter_in_view(self): pyFAI_parameter, fit2d_parameter = self.model.calibration_model.get_calibration_parameter() self.widget.set_calibration_parameters(pyFAI_parameter, fit2d_parameter) + if self.model.calibration_model.distortion_spline_filename: + self.widget.spline_filename_txt.setText( + os.path.basename(self.model.calibration_model.distortion_spline_filename)) + else: + self.widget.spline_filename_txt.setText('None') + def save_calibration(self): """ Saves the current calibration in a file. diff --git a/dioptas/controller/ConfigurationController.py b/dioptas/controller/ConfigurationController.py index dfd452413..ae5f62efc 100644 --- a/dioptas/controller/ConfigurationController.py +++ b/dioptas/controller/ConfigurationController.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/controller/MainController.py b/dioptas/controller/MainController.py index 78544acce..9d8fee71e 100755 --- a/dioptas/controller/MainController.py +++ b/dioptas/controller/MainController.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -135,13 +137,13 @@ def tab_changed(self): old_hist_levels = None if old_index == 0: # calibration tab old_view_range = self.widget.calibration_widget.img_widget.img_view_box.targetRange() - old_hist_levels = self.widget.calibration_widget.img_widget.img_histogram_LUT.getExpLevels() + old_hist_levels = self.widget.calibration_widget.img_widget.img_histogram_LUT_horizontal.getExpLevels() elif old_index == 1: # mask tab old_view_range = self.widget.mask_widget.img_widget.img_view_box.targetRange() - old_hist_levels = self.widget.mask_widget.img_widget.img_histogram_LUT.getExpLevels() + old_hist_levels = self.widget.mask_widget.img_widget.img_histogram_LUT_horizontal.getExpLevels() elif old_index == 2: old_view_range = self.widget.integration_widget.img_widget.img_view_box.targetRange() - old_hist_levels = self.widget.integration_widget.img_widget.img_histogram_LUT.getExpLevels() + old_hist_levels = self.widget.integration_widget.img_widget.img_histogram_LUT_horizontal.getExpLevels() # update the GUI if ind == 2: # integration tab @@ -150,7 +152,7 @@ def tab_changed(self): self.integration_controller.widget.calibration_lbl.setText(self.model.calibration_model.calibration_name) self.integration_controller.image_controller._auto_scale = False - if self.integration_controller.image_controller.img_mode == "Image": + if self.widget.integration_widget.img_mode == "Image": self.integration_controller.image_controller.plot_img() if self.model.use_mask: @@ -160,12 +162,13 @@ def tab_changed(self): else: self.model.pattern_changed.emit() self.widget.integration_widget.img_widget.set_range(x_range=old_view_range[0], y_range=old_view_range[1]) - self.widget.integration_widget.img_widget.img_histogram_LUT.setLevels(*old_hist_levels) + self.widget.integration_widget.img_widget.img_histogram_LUT_horizontal.setLevels(*old_hist_levels) + self.widget.integration_widget.img_widget.img_histogram_LUT_vertical.setLevels(*old_hist_levels) elif ind == 1: # mask tab self.mask_controller.plot_mask() self.mask_controller.plot_image() self.widget.mask_widget.img_widget.set_range(x_range=old_view_range[0], y_range=old_view_range[1]) - self.widget.mask_widget.img_widget.img_histogram_LUT.setLevels(*old_hist_levels) + self.widget.mask_widget.img_widget.img_histogram_LUT_vertical.setLevels(*old_hist_levels) elif ind == 0: # calibration tab self.calibration_controller.plot_mask() try: @@ -173,7 +176,7 @@ def tab_changed(self): except (TypeError, AttributeError): pass self.widget.calibration_widget.img_widget.set_range(x_range=old_view_range[0], y_range=old_view_range[1]) - self.widget.calibration_widget.img_widget.img_histogram_LUT.setLevels(*old_hist_levels) + self.widget.calibration_widget.img_widget.img_histogram_LUT_vertical.setLevels(*old_hist_levels) def update_title(self): """ @@ -185,8 +188,8 @@ def update_title(self): calibration_name = self.model.calibration_model.calibration_name str = 'Dioptas ' + __version__ if img_filename is '' and pattern_filename is '': - self.widget.setWindowTitle(str + u' - © 2017 C. Prescher') - self.widget.integration_widget.img_frame.setWindowTitle(str + u' - © 2017 C. Prescher') + self.widget.setWindowTitle(str + u' - © 2019 C. Prescher') + self.widget.integration_widget.img_frame.setWindowTitle(str + u' - © 2019 C. Prescher') return if img_filename is not '' or pattern_filename is not '': @@ -200,7 +203,7 @@ def update_title(self): if calibration_name is not None: str += ', calibration: ' + calibration_name str += ']' - str += u' - © 2017 C. Prescher' + str += u' - © 2019 C. Prescher' self.widget.setWindowTitle(str) self.widget.integration_widget.img_frame.setWindowTitle(str) diff --git a/dioptas/controller/MaskController.py b/dioptas/controller/MaskController.py index fa01cfae2..5f9f8bb10 100755 --- a/dioptas/controller/MaskController.py +++ b/dioptas/controller/MaskController.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/controller/__init__.py b/dioptas/controller/__init__.py index 849094a6f..8b970cd10 100755 --- a/dioptas/controller/__init__.py +++ b/dioptas/controller/__init__.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/controller/integration/BackgroundController.py b/dioptas/controller/integration/BackgroundController.py index e14059d88..c685ce88f 100644 --- a/dioptas/controller/integration/BackgroundController.py +++ b/dioptas/controller/integration/BackgroundController.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -21,7 +23,7 @@ import numpy as np from qtpy import QtWidgets, QtCore -from ...widgets.UtilityWidgets import open_file_dialog +from ...widgets.UtilityWidgets import open_file_dialog, save_file_dialog # imports for type hinting in PyCharm -- DO NOT DELETE from ...widgets.integration import IntegrationWidget @@ -44,6 +46,7 @@ def __init__(self, widget, dioptas_model): :type dioptas_model: DioptasModel """ self.widget = widget + self.background_widget = widget.integration_control_widget.background_control_widget self.model = dioptas_model self.model.configuration_selected.connect(self.update_bkg_image_widgets) @@ -74,6 +77,8 @@ def create_pattern_background_signals(self): self.widget.bkg_pattern_x_max_txt.editingFinished.connect(self.bkg_pattern_parameters_changed) self.widget.bkg_pattern_inspect_btn.toggled.connect(self.bkg_pattern_inspect_btn_toggled_callback) + self.widget.bkg_pattern_save_btn.clicked.connect(self.bkg_pattern_save_btn_callback) + self.widget.bkg_pattern_as_overlay_btn.clicked.connect(self.bkg_pattern_as_overlay_btn_callback) self.widget.qa_bkg_pattern_inspect_btn.toggled.connect(self.bkg_pattern_inspect_btn_toggled_callback) self.model.pattern_changed.connect(self.update_bkg_gui_parameters) @@ -107,12 +112,10 @@ def remove_background_image(self): self.model.img_model.reset_background() def update_bkg_image_scale_step(self): - value = np.float(self.widget.bkg_image_scale_step_msb.text()) - self.widget.bkg_image_scale_sb.setSingleStep(value) + self.widget.bkg_image_scale_sb.setSingleStep(self.widget.bkg_image_scale_step_msb.value()) def update_bkg_image_offset_step(self): - value = np.float(self.widget.bkg_image_offset_step_msb.text()) - self.widget.bkg_image_offset_sb.setSingleStep(value) + self.widget.bkg_image_offset_sb.setSingleStep(self.widget.bkg_image_offset_step_msb.value()) def update_background_image_filename(self): if self.model.img_model.has_background(): @@ -139,8 +142,8 @@ def bkg_pattern_gb_toggled_callback(self, is_checked): self.widget.qa_bkg_pattern_inspect_btn.setVisible(is_checked) if is_checked: - bkg_pattern_parameters = self.widget.get_bkg_pattern_parameters() - bkg_pattern_roi = self.widget.get_bkg_pattern_roi() + bkg_pattern_parameters = self.background_widget.get_bkg_pattern_parameters() + bkg_pattern_roi = self.background_widget.get_bkg_pattern_roi() self.model.pattern_model.set_auto_background_subtraction(bkg_pattern_parameters, bkg_pattern_roi) else: self.widget.bkg_pattern_inspect_btn.setChecked(False) @@ -149,15 +152,15 @@ def bkg_pattern_gb_toggled_callback(self, is_checked): self.model.pattern_model.unset_auto_background_subtraction() def bkg_pattern_parameters_changed(self): - bkg_pattern_parameters = self.widget.get_bkg_pattern_parameters() - bkg_pattern_roi = self.widget.get_bkg_pattern_roi() + bkg_pattern_parameters = self.background_widget.get_bkg_pattern_parameters() + bkg_pattern_roi = self.background_widget.get_bkg_pattern_roi() if self.model.pattern_model.pattern.auto_background_subtraction: self.model.pattern_model.set_auto_background_subtraction(bkg_pattern_parameters, bkg_pattern_roi) def update_bkg_gui_parameters(self): if self.model.pattern_model.pattern.auto_background_subtraction: - self.widget.set_bkg_pattern_parameters(self.model.pattern.auto_background_subtraction_parameters) - self.widget.set_bkg_pattern_roi(self.model.pattern.auto_background_subtraction_roi) + self.background_widget.set_bkg_pattern_parameters(self.model.pattern.auto_background_subtraction_parameters) + self.background_widget.set_bkg_pattern_roi(self.model.pattern.auto_background_subtraction_roi) self.widget.pattern_widget.linear_region_item.blockSignals(True) self.widget.pattern_widget.set_linear_region( @@ -189,6 +192,19 @@ def bkg_pattern_inspect_btn_toggled_callback(self, checked): self.widget.bkg_pattern_x_max_txt.editingFinished.disconnect(self.update_bkg_pattern_linear_region) self.model.pattern_changed.emit() + def bkg_pattern_save_btn_callback(self): + img_filename, _ = os.path.splitext(os.path.basename(self.model.img_model.filename)) + filename = save_file_dialog( + self.widget, "Save Fit Background as Pattern Data.", + os.path.join(self.model.working_directories['pattern'], + img_filename + '.xy'), ('Data (*.xy);;Data (*.chi);;Data (*.dat);;GSAS (*.fxye)')) + + if filename is not '': + self.model.current_configuration.save_background_pattern(filename) + + def bkg_pattern_as_overlay_btn_callback(self): + self.model.overlay_model.add_overlay_pattern(self.model.pattern.auto_background_pattern) + def bkg_pattern_linear_region_callback(self): x_min, x_max = self.widget.pattern_widget.get_linear_region() self.widget.bkg_pattern_x_min_txt.setText('{:.3f}'.format(x_min)) @@ -208,18 +224,13 @@ def update_bkg_image_widgets(self): def update_auto_pattern_bkg_widgets(self): # set the state of the toggles: + self.widget.bkg_pattern_gb.blockSignals(True) + self.widget.qa_bkg_pattern_btn.blockSignals(True) self.widget.bkg_pattern_gb.setChecked(self.model.pattern.auto_background_subtraction) self.widget.qa_bkg_pattern_btn.setChecked(self.model.pattern.auto_background_subtraction) + self.widget.bkg_pattern_gb.blockSignals(False) + self.widget.qa_bkg_pattern_btn.blockSignals(False) + self.update_bkg_gui_parameters() self.widget.qa_bkg_pattern_inspect_btn.setChecked(False) self.widget.bkg_pattern_inspect_btn.setChecked(False) - - def auto_background_set(self, bg_params, bg_roi): - self.widget.bkg_pattern_gb.setChecked(True) - self.widget.qa_bkg_pattern_btn.setChecked(True) - self.widget.bkg_pattern_smooth_width_sb.setValue(bg_params[0]) - self.widget.bkg_pattern_iterations_sb.setValue(bg_params[1]) - self.widget.bkg_pattern_poly_order_sb.setValue(bg_params[2]) - self.widget.bkg_pattern_x_min_txt.setText(str(bg_roi[0])) - self.widget.bkg_pattern_x_max_txt.setText(str(bg_roi[1])) - self.bkg_pattern_gb_toggled_callback(True) diff --git a/dioptas/controller/integration/CorrectionController.py b/dioptas/controller/integration/CorrectionController.py new file mode 100644 index 000000000..80d2804f8 --- /dev/null +++ b/dioptas/controller/integration/CorrectionController.py @@ -0,0 +1,316 @@ +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import numpy as np +import time +import os +from qtpy import QtWidgets + +from ...model.util.ImgCorrection import CbnCorrection, ObliqueAngleDetectorAbsorptionCorrection + +# imports for type hinting in PyCharm -- DO NOT DELETE +from ...widgets.integration import IntegrationWidget +from ...widgets.UtilityWidgets import open_file_dialog +from ...model.DioptasModel import DioptasModel + + +class CorrectionController(object): + """ + The CorrectionController manages the Correction controls in the integration window. + """ + + def __init__(self, widget, dioptas_model): + """ + :param widget: Reference to IntegrationWidget + :param dioptas_model: Reference to DioptasModel object + + :type widget: IntegrationWidget + :type dioptas_model: DioptasModel + """ + + self.widget = widget + self.model = dioptas_model + + self.create_signals() + + def create_signals(self): + # cbn correction + self.widget.cbn_groupbox.clicked.connect(self.cbn_groupbox_changed) + for row_ind in range(self.widget.cbn_param_tw.rowCount()): + self.widget.cbn_param_tw.cellWidget(row_ind, 1).editingFinished.connect(self.cbn_groupbox_changed) + self.widget.cbn_plot_btn.clicked.connect(self.cbn_plot_correction_btn_clicked) + + # oiadac correction + self.widget.oiadac_groupbox.clicked.connect(self.oiadac_groupbox_changed) + for row_ind in range(self.widget.oiadac_param_tw.rowCount()): + self.widget.oiadac_param_tw.cellWidget(row_ind, 1).editingFinished.connect(self.oiadac_groupbox_changed) + self.widget.oiadac_plot_btn.clicked.connect(self.oiadac_plot_btn_clicked) + + # transfer correction + self.widget.transfer_load_original_btn.clicked.connect(self.transfer_load_original_btn_clicked) + self.widget.transfer_load_response_btn.clicked.connect(self.transfer_load_response_btn_clicked) + self.widget.transfer_plot_btn.clicked.connect(self.transfer_plot_btn_clicked) + self.widget.transfer_gb.toggled.connect(self.transfer_gb_toggled) + + # general + self.model.img_model.corrections_removed.connect(self.corrections_removed) + + # toggle visibilities + self.widget.oiadac_groupbox.toggled.connect( + self.widget.integration_control_widget.corrections_control_widget.toggle_oiadac_widget_visibility) + self.widget.cbn_groupbox.toggled.connect( + self.widget.integration_control_widget.corrections_control_widget.toggle_cbn_widget_visibility) + self.widget.transfer_gb.toggled.connect( + self.widget.integration_control_widget.corrections_control_widget.toggle_transfer_widget_visibility) + + # resetting plot buttons + self.model.img_changed.connect(self.reset_plot_btns) + self.model.cake_changed.connect(self.reset_plot_btns) + + # configurations + self.model.configuration_selected.connect(self.update_gui) + + def transfer_load_original_btn_clicked(self): + filename = open_file_dialog(self.widget, caption="Load Original Image File", + directory=self.model.working_directories['image']) + if filename is not '': + self.widget.transfer_original_filename_lbl.setText(os.path.basename(filename)) + self.model.img_model.transfer_correction.load_original_image(filename) + self.model.img_model.enable_transfer_function() + + def transfer_load_response_btn_clicked(self): + filename = open_file_dialog(self.widget, caption="Load Response Image File", + directory=self.model.working_directories['image']) + if filename is not '': + self.widget.transfer_response_filename_lbl.setText(os.path.basename(filename)) + self.model.img_model.transfer_correction.load_response_image(filename) + self.model.img_model.enable_transfer_function() + + def transfer_plot_btn_clicked(self): + if self.widget.transfer_plot_btn.isChecked(): + transfer_data = self.model.img_model.transfer_correction.get_data() + if transfer_data is not None: + self.widget.img_widget.plot_image(transfer_data, auto_level=True) + self.widget.transfer_plot_btn.setText('Back') + else: + self.widget.transfer_plot_btn.setChecked(False) + else: + self.widget.transfer_plot_btn.setText('Plot') + self.reset_img_widget() + + def update_transfer_widgets(self): + original_filename = self.model.img_model.transfer_correction.original_filename + response_filename = self.model.img_model.transfer_correction.response_filename + if original_filename is not None: + self.widget.transfer_original_filename_lbl.setText(os.path.basename(original_filename)) + else: + self.widget.transfer_original_filename_lbl.setText('None') + if original_filename is not None: + self.widget.transfer_response_filename_lbl.setText(os.path.basename(response_filename)) + else: + self.widget.transfer_response_filename_lbl.setText('None') + + def transfer_gb_toggled(self): + if self.widget.transfer_gb.isChecked(): + self.model.img_model.enable_transfer_function() + else: + self.model.img_model.disable_transfer_function() + + def corrections_removed(self): + self.widget.cbn_groupbox.setChecked(False) + self.widget.oiadac_groupbox.setChecked(False) + self.widget.transfer_gb.setChecked(False) + self.widget.transfer_original_filename_lbl.setText('None') + self.widget.transfer_response_filename_lbl.setText('None') + QtWidgets.QMessageBox.critical(self.widget, + 'Shape Mismatch', + 'The loaded image and corrections have different shapes. ' + \ + 'The corrections have been reset.') + + def cbn_groupbox_changed(self): + if not self.model.calibration_model.is_calibrated: + self.widget.cbn_groupbox.setChecked(False) + QtWidgets.QMessageBox.critical(self.widget, + 'ERROR', + 'Please calibrate the geometry first or load an existent calibration file. ' + \ + 'The cBN seat correction needs a calibrated geometry.') + return + + if self.widget.cbn_groupbox.isChecked(): + diamond_thickness = self.widget.cbn_param_tw.cellWidget(0, 1).value() + seat_thickness = self.widget.cbn_param_tw.cellWidget(1, 1).value() + inner_seat_radius = self.widget.cbn_param_tw.cellWidget(2, 1).value() + outer_seat_radius = self.widget.cbn_param_tw.cellWidget(3, 1).value() + tilt = self.widget.cbn_param_tw.cellWidget(4, 1).value() + tilt_rotation = self.widget.cbn_param_tw.cellWidget(5, 1).value() + center_offset = self.widget.cbn_param_tw.cellWidget(6, 1).value() + center_offset_angle = self.widget.cbn_param_tw.cellWidget(7, 1).value() + seat_absorption_length = self.widget.cbn_param_tw.cellWidget(8, 1).value() + anvil_absorption_length = self.widget.cbn_param_tw.cellWidget(9, 1).value() + + tth_array = 180.0 / np.pi * self.model.calibration_model.pattern_geometry.ttha + azi_array = 180.0 / np.pi * self.model.calibration_model.pattern_geometry.chia + + new_cbn_correction = CbnCorrection( + tth_array=tth_array, + azi_array=azi_array, + diamond_thickness=diamond_thickness, + seat_thickness=seat_thickness, + small_cbn_seat_radius=inner_seat_radius, + large_cbn_seat_radius=outer_seat_radius, + tilt=tilt, + tilt_rotation=tilt_rotation, + center_offset=center_offset, + center_offset_angle=center_offset_angle, + cbn_abs_length=seat_absorption_length, + diamond_abs_length=anvil_absorption_length + ) + if not new_cbn_correction == self.model.img_model.get_img_correction("cbn"): + t1 = time.time() + new_cbn_correction.update() + print("Time needed for correction calculation: {0}".format(time.time() - t1)) + try: + self.model.img_model.delete_img_correction("cbn") + except KeyError: + pass + self.model.img_model.add_img_correction(new_cbn_correction, "cbn") + else: + self.model.img_model.delete_img_correction("cbn") + + def cbn_plot_correction_btn_clicked(self): + if str(self.widget.cbn_plot_btn.text()) == 'Plot': + self.widget.img_widget.plot_image(self.model.img_model.img_corrections.get_correction("cbn").get_data(), + True) + self.widget.cbn_plot_btn.setText('Back') + self.widget.oiadac_plot_btn.setText('Plot') + else: + self.widget.cbn_plot_btn.setText('Plot') + self.reset_img_widget() + + def update_cbn_widgets(self): + params = self.model.img_model.img_corrections.get_correction("cbn").get_params() + self.widget.cbn_param_tw.cellWidget(0, 1).setText(str(params['diamond_thickness'])) + self.widget.cbn_param_tw.cellWidget(1, 1).setText(str(params['seat_thickness'])) + self.widget.cbn_param_tw.cellWidget(2, 1).setText(str(params['small_cbn_seat_radius'])) + self.widget.cbn_param_tw.cellWidget(3, 1).setText(str(params['large_cbn_seat_radius'])) + self.widget.cbn_param_tw.cellWidget(4, 1).setText(str(params['tilt'])) + self.widget.cbn_param_tw.cellWidget(5, 1).setText(str(params['tilt_rotation'])) + self.widget.cbn_param_tw.cellWidget(6, 1).setText(str(params['diamond_abs_length'])) + self.widget.cbn_param_tw.cellWidget(7, 1).setText(str(params['seat_abs_length'])) + self.widget.cbn_param_tw.cellWidget(8, 1).setText(str(params['center_offset'])) + self.widget.cbn_param_tw.cellWidget(9, 1).setText(str(params['center_offset_angle'])) + self.widget.cbn_groupbox.setChecked(True) + + def oiadac_groupbox_changed(self): + if not self.model.calibration_model.is_calibrated: + self.widget.oiadac_groupbox.setChecked(False) + QtWidgets.QMessageBox.critical( + self.widget, + 'ERROR', + 'Please calibrate the geometry first or load an existent calibration file. ' + + 'The oblique incidence angle detector absorption correction needs a calibrated' + + 'geometry.' + ) + return + + if self.widget.oiadac_groupbox.isChecked(): + detector_thickness = self.widget.oiadac_param_tw.cellWidget(0, 1).value() + absorption_length = self.widget.oiadac_param_tw.cellWidget(1, 1).value() + + _, fit2d_parameter = self.model.calibration_model.get_calibration_parameter() + detector_tilt = fit2d_parameter['tilt'] + detector_tilt_rotation = fit2d_parameter['tiltPlanRotation'] + + tth_array = self.model.calibration_model.pattern_geometry.ttha + azi_array = self.model.calibration_model.pattern_geometry.chia + import time + + t1 = time.time() + + oiadac_correction = ObliqueAngleDetectorAbsorptionCorrection( + tth_array, azi_array, + detector_thickness=detector_thickness, + absorption_length=absorption_length, + tilt=detector_tilt, + rotation=detector_tilt_rotation, + ) + print("Time needed for correction calculation: {0}".format(time.time() - t1)) + try: + self.model.img_model.delete_img_correction("oiadac") + except KeyError: + pass + self.model.img_model.add_img_correction(oiadac_correction, "oiadac") + else: + self.model.img_model.delete_img_correction("oiadac") + + def oiadac_plot_btn_clicked(self): + if str(self.widget.oiadac_plot_btn.text()) == 'Plot': + self.widget.img_widget.plot_image(self.model.img_model._img_corrections.get_correction("oiadac").get_data(), + True) + self.widget.oiadac_plot_btn.setText('Back') + self.widget.cbn_plot_btn.setText('Plot') + else: + self.widget.oiadac_plot_btn.setText('Plot') + self.reset_img_widget() + + def reset_img_widget(self): + if self.widget.img_mode == 'Cake': + self.model.cake_changed.emit() + elif self.widget.img_mode == 'Image': + self.model.img_changed.emit() + + def update_oiadac_widgets(self): + params = self.model.img_model.img_corrections.get_correction("oiadac").get_params() + self.widget.oiadac_param_tw.cellWidget(0, 1).setText(str(params['detector_thickness'])) + self.widget.oiadac_param_tw.cellWidget(1, 1).setText(str(params['absorption_length'])) + self.widget.oiadac_groupbox.setChecked(True) + + def reset_plot_btns(self): + self.widget.oiadac_plot_btn.setText('Plot') + self.widget.oiadac_plot_btn.setChecked(False) + self.widget.cbn_plot_btn.setText('Plot') + self.widget.cbn_plot_btn.setChecked(False) + self.widget.transfer_plot_btn.setText('Plot') + self.widget.transfer_plot_btn.setChecked(False) + + def update_gui(self): + if self.model.img_model.get_img_correction('cbn') is not None: + self.update_cbn_widgets() + self.widget.cbn_groupbox.blockSignals(True) + self.widget.cbn_groupbox.setChecked(True) + self.widget.cbn_groupbox.blockSignals(False) + else: + self.widget.cbn_groupbox.setChecked(False) + + if self.model.img_model.get_img_correction('oiadac') is not None: + self.update_oiadac_widgets() + self.widget.oiadac_groupbox.blockSignals(True) + self.widget.oiadac_groupbox.setChecked(True) + self.widget.oiadac_groupbox.blockSignals(False) + else: + self.widget.oiadac_groupbox.setChecked(False) + + if self.model.img_model.get_img_correction('transfer') is not None: + self.update_transfer_widgets() + # self.widget.transfer_gb.blockSignals(True) + self.widget.transfer_gb.setChecked(True) + # self.widget.transfer_gb.blockSignals(False) + else: + self.widget.transfer_gb.setChecked(False) diff --git a/dioptas/controller/integration/EpicsController.py b/dioptas/controller/integration/EpicsController.py index b0717eeee..789a6974a 100644 --- a/dioptas/controller/integration/EpicsController.py +++ b/dioptas/controller/integration/EpicsController.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- +# -*- coding: utf-8 -*- # Dioptas - GUI program for fast processing of 2D X-ray diffraction data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/controller/integration/ImageController.py b/dioptas/controller/integration/ImageController.py index c1fad91a7..36aca665f 100755 --- a/dioptas/controller/integration/ImageController.py +++ b/dioptas/controller/integration/ImageController.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -16,9 +18,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . - import os -import time from functools import partial import numpy as np @@ -26,7 +26,6 @@ from qtpy import QtWidgets, QtCore from ...widgets.UtilityWidgets import open_file_dialog, open_files_dialog, save_file_dialog -from ...model.util.ImgCorrection import CbnCorrection, ObliqueAngleDetectorAbsorptionCorrection # imports for type hinting in PyCharm -- DO NOT DELETE from ...widgets.integration import IntegrationWidget from ...model.DioptasModel import DioptasModel @@ -49,19 +48,23 @@ def __init__(self, widget, dioptas_model): :type widget: IntegrationWidget :type dioptas_model: DioptasModel """ - self.widget = widget self.model = dioptas_model self.epics_controller = EpicsController(self.widget, self.model) - self.img_mode = 'Image' self.img_docked = True + self.view_mode = 'normal' # modes available: normal, alternative self.roi_active = False self.clicked_tth = None self.clicked_azi = None + self.vertical_splitter_alternative_state = None + self.vertical_splitter_normal_state = None + self.horizontal_splitter_alternative_state = None + self.horizontal_splitter_normal_state = None + self.initialize() self.create_signals() self.create_mouse_behavior() @@ -102,6 +105,7 @@ def plot_cake(self, auto_scale=None): shift_amount = self.widget.cake_shift_azimuth_sl.value() self.widget.img_widget.plot_image(np.roll(self.model.cake_data, shift_amount, axis=0)) + self.update_cake_axes_range() if auto_scale: self.widget.img_widget.auto_level() @@ -109,7 +113,7 @@ def plot_mask(self): """ Plots the mask data. """ - if self.model.use_mask and self.img_mode == 'Image': + if self.model.use_mask and self.widget.img_mode == 'Image': self.widget.img_widget.plot_mask(self.model.mask_model.get_img()) self.widget.img_mask_btn.setChecked(True) else: @@ -169,25 +173,8 @@ def create_signals(self): self.connect_click_function(self.widget.qa_save_img_btn, self.save_img) self.connect_click_function(self.widget.load_calibration_btn, self.load_calibration) - self.connect_click_function(self.widget.cbn_groupbox, self.cbn_groupbox_changed) - self.widget.cbn_diamond_thickness_txt.editingFinished.connect(self.cbn_groupbox_changed) - self.widget.cbn_seat_thickness_txt.editingFinished.connect(self.cbn_groupbox_changed) - self.widget.cbn_inner_seat_radius_txt.editingFinished.connect(self.cbn_groupbox_changed) - self.widget.cbn_outer_seat_radius_txt.editingFinished.connect(self.cbn_groupbox_changed) - self.widget.cbn_cell_tilt_txt.editingFinished.connect(self.cbn_groupbox_changed) - self.widget.cbn_tilt_rotation_txt.editingFinished.connect(self.cbn_groupbox_changed) - self.widget.cbn_center_offset_txt.editingFinished.connect(self.cbn_groupbox_changed) - self.widget.cbn_center_offset_angle_txt.editingFinished.connect(self.cbn_groupbox_changed) - self.widget.cbn_anvil_al_txt.editingFinished.connect(self.cbn_groupbox_changed) - self.widget.cbn_seat_al_txt.editingFinished.connect(self.cbn_groupbox_changed) - self.connect_click_function(self.widget.cbn_plot_correction_btn, self.cbn_plot_correction_btn_clicked) - - self.connect_click_function(self.widget.oiadac_groupbox, self.oiadac_groupbox_changed) - self.widget.oiadac_thickness_txt.editingFinished.connect(self.oiadac_groupbox_changed) - self.widget.oiadac_abs_length_txt.editingFinished.connect(self.oiadac_groupbox_changed) - self.connect_click_function(self.widget.oiadac_plot_btn, self.oiadac_plot_btn_clicked) - # signals + self.connect_click_function(self.widget.change_view_btn, self.change_view_btn_clicked) self.widget.autoprocess_cb.toggled.connect(self.auto_process_cb_click) def connect_click_function(self, emitter, function): @@ -227,7 +214,6 @@ def load_file(self, *args, **kwargs): self._load_multiple_files(filenames) elif self.widget.img_batch_mode_image_save_rb.isChecked(): self._save_multiple_image_files(filenames) - self._check_absorption_correction_shape() def _load_multiple_files(self, filenames): if not self.model.calibration_model.is_calibrated: @@ -461,7 +447,7 @@ def update_img(self, reset_img_levels=None): self.widget.img_directory_txt.setText(os.path.dirname(self.model.img_model.filename)) self.widget.file_info_widget.text_lbl.setText(self.model.img_model.file_info) - self.widget.cbn_plot_correction_btn.setText('Plot') + self.widget.cbn_plot_btn.setText('Plot') self.widget.oiadac_plot_btn.setText('Plot') # update the window due to some errors on mac when using macports @@ -529,7 +515,7 @@ def activate_cake_mode(self): if self.roi_active: self.widget.img_widget.deactivate_roi() self.widget.img_mode_btn.setText('Image') - self.img_mode = str("Cake") + self.widget.img_mode = str("Cake") self.model.img_changed.disconnect(self.plot_img) self.model.img_changed.disconnect(self.plot_mask) @@ -542,8 +528,8 @@ def activate_cake_mode(self): self.update_cake_axes_range() self.widget.cake_shift_azimuth_sl.setVisible(True) - self.widget.cake_shift_azimuth_sl.setMinimum(-len(self.model.cake_azi)/2) - self.widget.cake_shift_azimuth_sl.setMaximum(len(self.model.cake_azi)/2) + self.widget.cake_shift_azimuth_sl.setMinimum(-len(self.model.cake_azi) / 2) + self.widget.cake_shift_azimuth_sl.setMaximum(len(self.model.cake_azi) / 2) self.widget.cake_shift_azimuth_sl.setSingleStep(1) def activate_image_mode(self): @@ -561,7 +547,7 @@ def activate_image_mode(self): self.widget.img_widget.activate_roi() self.widget.img_widget.img_view_box.setAspectLocked(True) self.widget.img_mode_btn.setText('Cake') - self.img_mode = str("Image") + self.widget.img_mode = str("Image") self.model.img_changed.connect(self.plot_img) self.model.img_changed.connect(self.plot_mask) @@ -656,11 +642,10 @@ def update_cake_axes_range(self): self.update_cake_azimuth_axis() self.update_cake_x_axis() - def update_cake_azimuth_axis(self): data_img_item = self.widget.integration_image_widget.img_view.data_img_item shift_amount = self.widget.cake_shift_azimuth_sl.value() - cake_azi = self.model.cake_azi-shift_amount*np.mean(np.diff(self.model.cake_azi)) + cake_azi = self.model.cake_azi - shift_amount * np.mean(np.diff(self.model.cake_azi)) height = data_img_item.viewRect().height() bottom = data_img_item.viewRect().top() @@ -692,7 +677,6 @@ def update_cake_x_axis(self): self.convert_x_value(min_tth, '2th_deg', 'q_A^-1'), self.convert_x_value(max_tth, '2th_deg', 'q_A^-1')) - def set_cake_axis_unit(self, unit='2th_deg'): if unit == '2th_deg': self.widget.integration_image_widget.img_view.bottom_axis_cake.setLabel(u'2θ', u'°') @@ -701,10 +685,7 @@ def set_cake_axis_unit(self, unit='2th_deg'): self.update_cake_x_axis() def show_img_mouse_position(self, x, y): - if self.img_mode == "Image": - img_shape = self.model.img_data.shape - elif self.img_mode == "Cake": - img_shape = (len(self.model.cake_tth), len(self.model.cake_azi)) + img_shape = self.widget.img_widget.img_data.shape if x > 0 and y > 0 and x < img_shape[1] - 1 and y < img_shape[0] - 1: x_pos_string = 'X: %4d' % x @@ -725,7 +706,7 @@ def show_img_mouse_position(self, x, y): x_temp = x x = np.array([y]) y = np.array([x_temp]) - if self.img_mode == 'Cake': + if self.widget.img_mode == 'Cake': tth = get_partial_value(self.model.cake_tth, y - 0.5) shift_amount = self.widget.cake_shift_azimuth_sl.value() cake_azi = self.model.cake_azi - shift_amount * np.mean(np.diff(self.model.cake_azi)) @@ -778,21 +759,23 @@ def img_mouse_click(self, x, y): if self.model.calibration_model.is_calibrated: x, y = y, x # the indices are reversed for the img_array - if self.img_mode == 'Cake': # cake mode - cake_shape = (len(self.model.cake_tth), len(self.model.cake_azi)) - if x < 0 or y < 0 or x > (cake_shape[0] - 1) or y > (cake_shape[1] - 1): + if self.widget.img_mode == 'Cake': # cake mode + cake_shape = self.model.cake_data.shape + if x < 0 or y < 0 or x > cake_shape[0] - 1 or y > cake_shape[1] - 1: return - y = np.array([y]) x = np.array([x]) + y = np.array([y]) tth = get_partial_value(self.model.cake_tth, y - 0.5) / 180 * np.pi shift_amount = self.widget.cake_shift_azimuth_sl.value() azi = get_partial_value(np.roll(self.model.cake_azi, shift_amount), x - 0.5) - elif self.img_mode == 'Image': # image mode + elif self.widget.img_mode == 'Image': # image mode img_shape = self.model.img_data.shape if x < 0 or y < 0 or x > img_shape[0] - 1 or y > img_shape[1] - 1: return + x = np.array([x]) + y = np.array([y]) tth = self.model.calibration_model.get_two_theta_img(x, y) - azi = self.model.calibration_model.get_azi_img(np.array([x]), np.array([y])) / np.pi * 180 + azi = self.model.calibration_model.get_azi_img(x, y) / np.pi * 180 self.widget.img_widget.set_circle_line( self.model.calibration_model.get_two_theta_array(), tth) else: # in the case of whatever @@ -875,180 +858,44 @@ def save_img(self, filename=None): filename = save_file_dialog(self.widget, "Save Image.", os.path.join(self.model.working_directories['image'], img_filename + '.png'), - ('Image (*.png);;Data (*.tiff)')) + ('Image (*.png);;Data (*.tiff);;Text (*.txt)')) if filename is not '': if filename.endswith('.png'): - if self.img_mode == 'Cake': + if self.widget.img_mode == 'Cake': self.widget.img_widget.deactivate_vertical_line() - elif self.img_mode == 'Image': + elif self.widget.img_mode == 'Image': self.widget.img_widget.deactivate_circle_scatter() self.widget.img_widget.deactivate_roi() QtWidgets.QApplication.processEvents() self.widget.img_widget.save_img(filename) - if self.img_mode == 'Cake': + if self.widget.img_mode == 'Cake': self.widget.img_widget.activate_vertical_line() - elif self.img_mode == 'Image': + elif self.widget.img_mode == 'Image': self.widget.img_widget.activate_circle_scatter() if self.roi_active: self.widget.img_widget.activate_roi() elif filename.endswith('.tiff') or filename.endswith('.tif'): - if self.img_mode == 'Image': + if self.widget.img_mode == 'Image': im_array = np.int32(self.model.img_data) - elif self.img_mode == 'Cake': + elif self.widget.img_mode == 'Cake': im_array = np.int32(self.model.cake_data) im_array = np.flipud(im_array) im = Image.fromarray(im_array) im.save(filename) - - def cbn_groupbox_changed(self): - if not self.model.calibration_model.is_calibrated: - self.widget.cbn_groupbox.setChecked(False) - QtWidgets.QMessageBox.critical(self.widget, - 'ERROR', - 'Please calibrate the geometry first or load an existent calibration file. ' + \ - 'The cBN seat correction needs a calibrated geometry.') - return - - if self.widget.cbn_groupbox.isChecked(): - diamond_thickness = float(str(self.widget.cbn_diamond_thickness_txt.text())) - seat_thickness = float(str(self.widget.cbn_seat_thickness_txt.text())) - inner_seat_radius = float(str(self.widget.cbn_inner_seat_radius_txt.text())) - outer_seat_radius = float(str(self.widget.cbn_outer_seat_radius_txt.text())) - tilt = float(str(self.widget.cbn_cell_tilt_txt.text())) - tilt_rotation = float(str(self.widget.cbn_tilt_rotation_txt.text())) - center_offset = float(str(self.widget.cbn_center_offset_txt.text())) - center_offset_angle = float(str(self.widget.cbn_center_offset_angle_txt.text())) - seat_absorption_length = float(str(self.widget.cbn_seat_al_txt.text())) - anvil_absorption_length = float(str(self.widget.cbn_anvil_al_txt.text())) - - tth_array = 180.0 / np.pi * self.model.calibration_model.pattern_geometry.ttha - azi_array = 180.0 / np.pi * self.model.calibration_model.pattern_geometry.chia - - new_cbn_correction = CbnCorrection( - tth_array=tth_array, - azi_array=azi_array, - diamond_thickness=diamond_thickness, - seat_thickness=seat_thickness, - small_cbn_seat_radius=inner_seat_radius, - large_cbn_seat_radius=outer_seat_radius, - tilt=tilt, - tilt_rotation=tilt_rotation, - center_offset=center_offset, - center_offset_angle=center_offset_angle, - cbn_abs_length=seat_absorption_length, - diamond_abs_length=anvil_absorption_length - ) - if not new_cbn_correction == self.model.img_model.get_img_correction("cbn"): - t1 = time.time() - new_cbn_correction.update() - print("Time needed for correction calculation: {0}".format(time.time() - t1)) - try: - self.model.img_model.delete_img_correction("cbn") - except KeyError: - pass - self.model.img_model.add_img_correction(new_cbn_correction, "cbn") - else: - self.model.img_model.delete_img_correction("cbn") - - def cbn_plot_correction_btn_clicked(self): - if str(self.widget.cbn_plot_correction_btn.text()) == 'Plot': - self.widget.img_widget.plot_image(self.model.img_model.img_corrections.get_correction("cbn").get_data(), - True) - self.widget.cbn_plot_correction_btn.setText('Back') - self.widget.oiadac_plot_btn.setText('Plot') - else: - self.widget.cbn_plot_correction_btn.setText('Plot') - if self.img_mode == 'Cake': - self.plot_cake(True) - elif self.img_mode == 'Image': - self.plot_img(True) - - def update_cbn_widgets(self): - params = self.model.img_model.img_corrections.get_correction("cbn").get_params() - self.widget.cbn_diamond_thickness_txt.setText(str(params['diamond_thickness'])) - self.widget.cbn_seat_thickness_txt.setText(str(params['seat_thickness'])) - self.widget.cbn_inner_seat_radius_txt.setText(str(params['small_cbn_seat_radius'])) - self.widget.cbn_outer_seat_radius_txt.setText(str(params['large_cbn_seat_radius'])) - self.widget.cbn_cell_tilt_txt.setText(str(params['tilt'])) - self.widget.cbn_tilt_rotation_txt.setText(str(params['tilt_rotation'])) - self.widget.cbn_anvil_al_txt.setText(str(params['diamond_abs_length'])) - self.widget.cbn_seat_al_txt.setText(str(params['seat_abs_length'])) - self.widget.cbn_center_offset_txt.setText(str(params['center_offset'])) - self.widget.cbn_center_offset_angle_txt.setText(str(params['center_offset_angle'])) - self.widget.cbn_groupbox.setChecked(True) - - def oiadac_groupbox_changed(self): - if not self.model.calibration_model.is_calibrated: - self.widget.oiadac_groupbox.setChecked(False) - QtWidgets.QMessageBox.critical( - self.widget, - 'ERROR', - 'Please calibrate the geometry first or load an existent calibration file. ' + \ - 'The oblique incidence angle detector absorption correction needs a calibrated' + \ - 'geometry.' - ) - return - - if self.widget.oiadac_groupbox.isChecked(): - detector_thickness = float(str(self.widget.oiadac_thickness_txt.text())) - absorption_length = float(str(self.widget.oiadac_abs_length_txt.text())) - - _, fit2d_parameter = self.model.calibration_model.get_calibration_parameter() - detector_tilt = fit2d_parameter['tilt'] - detector_tilt_rotation = fit2d_parameter['tiltPlanRotation'] - - tth_array = self.model.calibration_model.pattern_geometry.ttha - azi_array = self.model.calibration_model.pattern_geometry.chia - import time - - t1 = time.time() - - oiadac_correction = ObliqueAngleDetectorAbsorptionCorrection( - tth_array, azi_array, - detector_thickness=detector_thickness, - absorption_length=absorption_length, - tilt=detector_tilt, - rotation=detector_tilt_rotation, - ) - print("Time needed for correction calculation: {0}".format(time.time() - t1)) - try: - self.model.img_model.delete_img_correction("oiadac") - except KeyError: - pass - self.model.img_model.add_img_correction(oiadac_correction, "oiadac") - else: - self.model.img_model.delete_img_correction("oiadac") - - def oiadac_plot_btn_clicked(self): - if str(self.widget.oiadac_plot_btn.text()) == 'Plot': - self.widget.img_widget.plot_image(self.model.img_model._img_corrections.get_correction("oiadac").get_data(), - True) - self.widget.oiadac_plot_btn.setText('Back') - self.widget.cbn_plot_correction_btn.setText('Plot') - else: - self.widget.oiadac_plot_btn.setText('Plot') - if self.img_mode == 'Cake': - self.plot_cake(True) - elif self.img_mode == 'Image': - self.plot_img(True) - - def update_oiadac_widgets(self): - params = self.model.img_model.img_corrections.get_correction("oiadac").get_params() - self.widget.oiadac_thickness_txt.setText(str(params['detector_thickness'])) - self.widget.oiadac_abs_length_txt.setText(str(params['absorption_length'])) - self.widget.oiadac_groupbox.setChecked(True) - - def _check_absorption_correction_shape(self): - if self.model.img_model.has_corrections() is None and self.widget.cbn_groupbox.isChecked(): - self.widget.cbn_groupbox.setChecked(False) - self.widget.oiadac_groupbox.setChecked(False) - QtWidgets.QMessageBox.critical(self.widget, - 'ERROR', - 'Due to a change in image dimensions the absorption ' + - 'corrections have been removed') + elif filename.endswith('.txt') or filename.endswith('.csv'): + if self.widget.img_mode == 'Image': + return + elif self.widget.img_mode == 'Cake': # saving cake data as a text file for export. + with open(filename, 'w') as out_file: # this is done in an odd and slow way because the headers + # should be floats and the data itself int. + cake_tth = np.insert(self.model.cake_tth, 0, 0) + np.savetxt(out_file, cake_tth[None], fmt='%6.3f') + for azi, row in zip(self.model.cake_azi, self.model.cake_data): + row_str = " ".join(["{:6.0f}".format(el) for el in row]) + out_file.write("{:6.2f}".format(azi) + row_str + '\n') def update_gui_from_configuration(self): self.widget.img_mask_btn.setChecked(self.model.use_mask) @@ -1059,15 +906,55 @@ def update_gui_from_configuration(self): self.update_mask_mode() self.update_roi_in_gui() - if self.model.current_configuration.auto_integrate_cake and self.img_mode == 'Image': + if self.model.current_configuration.auto_integrate_cake and self.widget.img_mode == 'Image': self.activate_cake_mode() - elif not self.model.current_configuration.auto_integrate_cake and self.img_mode == 'Cake': + elif not self.model.current_configuration.auto_integrate_cake and self.widget.img_mode == 'Cake': self.activate_image_mode() - elif self.model.current_configuration.auto_integrate_cake and self.img_mode == 'Cake': + elif self.model.current_configuration.auto_integrate_cake and self.widget.img_mode == 'Cake': self._update_cake_line_pos() self._update_cake_mouse_click_pos() - elif not self.model.current_configuration.auto_integrate_cake and self.img_mode == 'Image': + elif not self.model.current_configuration.auto_integrate_cake and self.widget.img_mode == 'Image': self._update_image_line_pos() self._update_image_mouse_click_pos() + def change_view_btn_clicked(self): + if self.view_mode == 'alternative': + self.change_view_to_normal() + elif self.view_mode == 'normal': + self.change_view_to_alternative() + + def change_view_to_normal(self): + if self.view_mode == 'normal': + return + self.vertical_splitter_alternative_state = self.widget.vertical_splitter.saveState() + self.horizontal_splitter_alternative_state = self.widget.horizontal_splitter.saveState() + self.widget.vertical_splitter.addWidget(self.widget.integration_pattern_widget) + + self.widget.integration_control_widget.setOrientation(QtCore.Qt.Horizontal) + + if self.vertical_splitter_normal_state: + self.widget.vertical_splitter.restoreState(self.vertical_splitter_normal_state) + if self.horizontal_splitter_normal_state: + self.widget.horizontal_splitter.restoreState(self.horizontal_splitter_normal_state) + + self.widget.img_widget.set_orientation("horizontal") + self.view_mode = 'normal' + + def change_view_to_alternative(self): + if self.view_mode == 'alternative': + return + + self.vertical_splitter_normal_state = self.widget.vertical_splitter.saveState() + self.horizontal_splitter_normal_state = self.widget.horizontal_splitter.saveState() + + self.widget.vertical_splitter_left.insertWidget(0, self.widget.integration_pattern_widget) + + self.widget.integration_control_widget.setOrientation(QtCore.Qt.Vertical) + + if self.vertical_splitter_alternative_state: + self.widget.vertical_splitter.restoreState(self.vertical_splitter_alternative_state) + if self.horizontal_splitter_alternative_state: + self.widget.horizontal_splitter.restoreState(self.horizontal_splitter_alternative_state) + self.widget.img_widget.set_orientation("vertical") + self.view_mode = 'alternative' diff --git a/dioptas/controller/integration/IntegrationController.py b/dioptas/controller/integration/IntegrationController.py index 63fb6c0d9..32dd7e5fa 100755 --- a/dioptas/controller/integration/IntegrationController.py +++ b/dioptas/controller/integration/IntegrationController.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -19,6 +21,7 @@ import pyqtgraph as pg from .BackgroundController import BackgroundController +from .CorrectionController import CorrectionController from .ImageController import ImageController from .OverlayController import OverlayController from .PatternController import PatternController @@ -63,4 +66,5 @@ def create_sub_controller(self): self.overlay_controller = OverlayController(self.widget, self.model) self.phase_controller = PhaseController(self.widget, self.model) self.background_controller = BackgroundController(self.widget, self.model) + self.correction_controller = CorrectionController(self.widget, self.model) self.options_controller = OptionsController(self.widget, self.model) diff --git a/dioptas/controller/integration/JcpdsEditorController.py b/dioptas/controller/integration/JcpdsEditorController.py index 9e1431f02..79f2f29c1 100644 --- a/dioptas/controller/integration/JcpdsEditorController.py +++ b/dioptas/controller/integration/JcpdsEditorController.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -283,7 +285,7 @@ def reflections_delete_btn_click(self): def reflections_add_btn_click(self): self.jcpds_phase.add_reflection() - self.widget.add_reflection_to_table(0., 0., 0., 0., 0., 0., 0., 0.) + self.widget.add_reflection_to_table(0., 0., 0., 0., 0., 0., 0., 0., 0., 0.) self.widget.reflection_table.selectRow(self.widget.reflection_table.rowCount() - 1) self.reflection_line_added.emit() self.phase_modified.emit() @@ -368,14 +370,30 @@ def save_as_btn_clicked(self, filename=False): if filename is False: filename = save_file_dialog(self.widget, "Save JCPDS phase.", self.model.working_directories['phase'], - ('JCPDS Phase (*.jcpds)')) + ('JCPDS Phase (*.jcpds);;Export Table (*.txt)')) if filename != '': - self.jcpds_phase.save_file(filename) + if filename.endswith('.jcpds'): + self.jcpds_phase.save_file(filename) + elif filename.endswith('.txt'): + self.export_table_data(filename) self.show_phase(self.jcpds_phase) self.lattice_param_changed.emit() self.phase_modified.emit() + def export_table_data(self, filename): + fp = open(filename, 'w', encoding='utf-8') + for col in range(self.widget.reflection_table.columnCount()): + fp.write(self.widget.reflection_table.horizontalHeaderItem(col).text() + '\t') + fp.write('\n') + for row in range(self.widget.reflection_table.rowCount()): + line = '' + for col in range(self.widget.reflection_table.columnCount()): + line = line + self.widget.reflection_table.item(row, col).text() + '\t' + line = line + '\n' + fp.write(line) + fp.close() + def reload_file_btn_clicked(self): self.jcpds_phase.reload_file() self.canceled_editor.emit(self.jcpds_phase) diff --git a/dioptas/controller/integration/OptionsController.py b/dioptas/controller/integration/OptionsController.py index 5ba260c50..8cf73570e 100644 --- a/dioptas/controller/integration/OptionsController.py +++ b/dioptas/controller/integration/OptionsController.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -48,10 +50,61 @@ def __init__(self, widget, dioptas_model): def connect_signals(self): self.options_widget.correct_solid_angle_cb.stateChanged.connect(self.correct_solid_angle_cb_clicked) self.model.configuration_selected.connect(self.update_gui) + self.model.pattern_changed.connect(self.update_gui) + + self.options_widget.cake_azimuth_points_sb.valueChanged.connect(self.cake_azimuth_points_changed) + self.options_widget.cake_azimuth_min_txt.editingFinished.connect(self.cake_azimuth_range_changed) + self.options_widget.cake_azimuth_max_txt.editingFinished.connect(self.cake_azimuth_range_changed) + + self.options_widget.cake_full_toggle_btn.toggled.connect(self.cake_full_toggled_btn_changed) def correct_solid_angle_cb_clicked(self): self.model.current_configuration.correct_solid_angle = self.options_widget.correct_solid_angle_cb.isChecked() def update_gui(self): - print(self.model.current_configuration.correct_solid_angle) - self.options_widget.correct_solid_angle_cb.setChecked(self.model.current_configuration.correct_solid_angle) \ No newline at end of file + self.options_widget.blockSignals(True) + self.options_widget.correct_solid_angle_cb.setChecked(self.model.current_configuration.correct_solid_angle) + self.options_widget.bin_count_txt.setText("{:1.0f}".format(self.model.calibration_model.num_points)) + + self.options_widget.cake_azimuth_points_sb.setValue(self.model.current_configuration.cake_azimuth_points) + + if self.model.current_configuration.cake_azimuth_range is None: + self.enable_full_cake_range() + else: + self.options_widget.cake_azimuth_min_txt.setText( + '{}'.format(self.model.current_configuration.cake_azimuth_range[0])) + self.options_widget.cake_azimuth_max_txt.setText( + '{}'.format(self.model.current_configuration.cake_azimuth_range[1])) + self.options_widget.blockSignals(False) + self.disable_full_cake_range() + + def cake_azimuth_range_changed(self): + range_min = float(self.options_widget.cake_azimuth_min_txt.text()) + range_max = float(self.options_widget.cake_azimuth_max_txt.text()) + self.model.current_configuration.cake_azimuth_range = (range_min, range_max) + + def cake_azimuth_points_changed(self): + self.model.current_configuration.cake_azimuth_points = int( + self.options_widget.cake_azimuth_points_sb.value()) + + def cake_full_toggled_btn_changed(self): + if self.options_widget.cake_full_toggle_btn.isChecked(): + self.enable_full_cake_range() + self.model.current_configuration.cake_azimuth_range = None + + elif not self.options_widget.cake_full_toggle_btn.isChecked(): + self.disable_full_cake_range() + self.cake_azimuth_range_changed() + + def enable_full_cake_range(self): + self.options_widget.cake_azimuth_min_txt.setDisabled(True) + self.options_widget.cake_azimuth_max_txt.setDisabled(True) + self.integration_widget.cake_shift_azimuth_sl.setDisabled(False) + + def disable_full_cake_range(self): + self.options_widget.cake_azimuth_min_txt.setDisabled(False) + self.options_widget.cake_azimuth_max_txt.setDisabled(False) + self.integration_widget.cake_shift_azimuth_sl.setDisabled(True) + self.integration_widget.cake_shift_azimuth_sl.setValue(0) + + diff --git a/dioptas/controller/integration/OverlayController.py b/dioptas/controller/integration/OverlayController.py index e957f49c8..01f56cddf 100755 --- a/dioptas/controller/integration/OverlayController.py +++ b/dioptas/controller/integration/OverlayController.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -60,16 +62,18 @@ def create_signals(self): self.overlay_widget.show_cb_state_changed.connect(self.show_cb_state_changed) self.overlay_widget.name_changed.connect(self.rename_overlay) - self.overlay_widget.scale_step_msb.editingFinished.connect(self.update_scale_step) - self.overlay_widget.offset_step_msb.editingFinished.connect(self.update_overlay_offset_step) - self.overlay_widget.scale_sb.valueChanged.connect(self.scale_sb_changed) - self.overlay_widget.offset_sb.valueChanged.connect(self.offset_sb_changed) + self.overlay_widget.scale_step_msb.valueChanged.connect(self.update_scale_step) + self.overlay_widget.offset_step_msb.valueChanged.connect(self.update_overlay_offset_step) + self.overlay_widget.scale_sb_value_changed.connect(self.scale_sb_changed) + self.overlay_widget.offset_sb_value_changed.connect(self.offset_sb_changed) self.overlay_widget.waterfall_btn.clicked.connect(self.waterfall_btn_click_callback) self.overlay_widget.waterfall_reset_btn.clicked.connect(self.model.overlay_model.reset_overlay_offsets) self.overlay_widget.set_as_bkg_btn.clicked.connect(self.set_as_bkg_btn_click_callback) + self.overlay_widget.overlay_tw.horizontalHeader().sectionClicked.connect(self.overlay_tw_header_section_clicked) + # creating the quick-actions signals self.connect_click_function(self.integration_widget.qa_set_as_overlay_btn, self.set_current_pattern_as_overlay) @@ -174,17 +178,19 @@ def clear_overlays_btn_click_callback(self): def update_scale_step(self): """ - Sets the step size for scale spinbox from the step text box. + Sets the step size for the scale spinboxes from the step text box. """ value = self.overlay_widget.scale_step_msb.value() - self.overlay_widget.scale_sb.setSingleStep(value) + for scale_sb in self.overlay_widget.scale_sbs: + scale_sb.setSingleStep(value) def update_overlay_offset_step(self): """ Sets the step size for the offset spinbox from the offset_step text box. """ value = self.overlay_widget.offset_step_msb.value() - self.overlay_widget.offset_sb.setSingleStep(value) + for offset_sb in self.overlay_widget.offset_sbs: + offset_sb.setSingleStep(value) def overlay_selected(self, row, *args): """ @@ -193,15 +199,7 @@ def overlay_selected(self, row, *args): the set_as_bkg_btn appropriately. :param row: selected row in the overlay table """ - cur_ind = row - self.overlay_widget.scale_sb.blockSignals(True) - self.overlay_widget.offset_sb.blockSignals(True) - self.overlay_widget.scale_sb.setValue(self.model.overlay_model.overlays[cur_ind].scaling) - self.overlay_widget.offset_sb.setValue(self.model.overlay_model.overlays[cur_ind].offset) - - self.overlay_widget.scale_sb.blockSignals(False) - self.overlay_widget.offset_sb.blockSignals(False) - if self.model.pattern_model.background_pattern == self.model.overlay_model.overlays[cur_ind]: + if self.model.pattern_model.background_pattern == self.model.overlay_model.overlays[row]: self.overlay_widget.set_as_bkg_btn.setChecked(True) else: self.overlay_widget.set_as_bkg_btn.setChecked(False) @@ -222,36 +220,34 @@ def color_btn_clicked(self, ind, button): self.integration_widget.pattern_widget.set_overlay_color(ind, color) button.setStyleSheet('background-color:' + color) - def scale_sb_changed(self, value): + def scale_sb_changed(self, overlay_ind, new_value): """ Callback for scale_sb spinbox. - :param value: new scale value + :param overlay_ind: index of overlay + :param new_value: new scale value """ - cur_ind = self.overlay_widget.get_selected_overlay_row() - self.model.overlay_model.set_overlay_scaling(cur_ind, value) - if self.model.overlay_model.overlays[cur_ind] == self.model.pattern_model.background_pattern: + self.model.overlay_model.set_overlay_scaling(overlay_ind, new_value) + if self.model.overlay_model.overlays[overlay_ind] == self.model.pattern_model.background_pattern: self.model.pattern_changed.emit() - def offset_sb_changed(self, value): + def offset_sb_changed(self, overlay_ind, new_value): """ Callback gor the offset_sb spinbox. - :param value: new value + :param overlay_ind: index of overlay + :param new_value: new value """ - cur_ind = self.overlay_widget.get_selected_overlay_row() - self.model.overlay_model.set_overlay_offset(cur_ind, value) - if self.model.overlay_model.overlays[cur_ind] == self.model.pattern_model.background_pattern: + self.model.overlay_model.set_overlay_offset(overlay_ind, new_value) + if self.model.overlay_model.overlays[overlay_ind] == self.model.pattern_model.background_pattern: self.model.pattern_changed.emit() def overlay_changed(self, ind): self.integration_widget.pattern_widget.update_overlay(self.model.overlay_model.overlays[ind], ind) - cur_ind = self.overlay_widget.get_selected_overlay_row() - if ind == cur_ind: - self.overlay_widget.offset_sb.blockSignals(True) - self.overlay_widget.scale_sb.blockSignals(True) - self.overlay_widget.offset_sb.setValue(self.model.overlay_model.get_overlay_offset(ind)) - self.overlay_widget.scale_sb.setValue(self.model.overlay_model.get_overlay_scaling(ind)) - self.overlay_widget.offset_sb.blockSignals(False) - self.overlay_widget.scale_sb.blockSignals(False) + self.overlay_widget.offset_sbs[ind].blockSignals(True) + self.overlay_widget.scale_sbs[ind].blockSignals(True) + self.overlay_widget.offset_sbs[ind].setValue(self.model.overlay_model.get_overlay_offset(ind)) + self.overlay_widget.scale_sbs[ind].setValue(self.model.overlay_model.get_overlay_scaling(ind)) + self.overlay_widget.offset_sbs[ind].blockSignals(False) + self.overlay_widget.scale_sbs[ind].blockSignals(False) def waterfall_btn_click_callback(self): separation = self.overlay_widget.waterfall_separation_msb.value() @@ -320,3 +316,16 @@ def rename_overlay(self, ind, name): """ self.integration_widget.pattern_widget.rename_overlay(ind, name) self.model.overlay_model.overlays[ind].name = name + + def overlay_tw_header_section_clicked(self, ind): + if ind != 0: + return + + current_checkbox_state = False + # check whether any checkbox is checked, if one is true current_checkbox_state will be True too + for cb in self.overlay_widget.show_cbs: + current_checkbox_state = current_checkbox_state or cb.isChecked() + + # assign the the opposite to all checkboxes + for cb in self.overlay_widget.show_cbs: + cb.setChecked(not current_checkbox_state) \ No newline at end of file diff --git a/dioptas/controller/integration/PatternController.py b/dioptas/controller/integration/PatternController.py index 31aa502d0..7bb166f7a 100755 --- a/dioptas/controller/integration/PatternController.py +++ b/dioptas/controller/integration/PatternController.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -148,9 +150,9 @@ def reset_background(self, popup=True): def integration_binning_changed(self): current_value = self.widget.automatic_binning_cb.isChecked() if current_value: - self.model.current_configuration.integration_num_points = None + self.model.current_configuration.integration_rad_points = None else: - self.model.current_configuration.integration_num_points = float(str(self.widget.bin_count_txt.text())) + self.model.current_configuration.integration_rad_points = float(str(self.widget.bin_count_txt.text())) self.widget.bin_count_txt.setEnabled(not current_value) def supersampling_changed(self, value): diff --git a/dioptas/controller/integration/PhaseController.py b/dioptas/controller/integration/PhaseController.py index 29d5f197d..ac7177185 100755 --- a/dioptas/controller/integration/PhaseController.py +++ b/dioptas/controller/integration/PhaseController.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -61,24 +63,24 @@ def __init__(self, integration_widget, dioptas_model): def create_signals(self): self.connect_click_function(self.phase_widget.add_btn, self.add_btn_click_callback) - self.connect_click_function(self.phase_widget.delete_btn, self.remove_btn_click_callback) + self.connect_click_function(self.phase_widget.delete_btn, self.delete_btn_click_callback) self.connect_click_function(self.phase_widget.clear_btn, self.clear_phases) self.connect_click_function(self.phase_widget.edit_btn, self.edit_btn_click_callback) self.connect_click_function(self.phase_widget.save_list_btn, self.save_btn_clicked_callback) - self.connect_click_function(self.phase_widget.load_list_btn, self.load_btn_clicked_callback) + self.connect_click_function(self.phase_widget.load_list_btn, self.load_list_btn_clicked_callback) - self.phase_widget.pressure_step_msb.editingFinished.connect(self.update_pressure_step) - self.phase_widget.temperature_step_msb.editingFinished.connect(self.update_temperature_step) + self.phase_widget.pressure_step_msb.valueChanged.connect(self.update_pressure_step) + self.phase_widget.temperature_step_msb.valueChanged.connect(self.update_temperature_step) - self.phase_widget.pressure_sb.valueChanged.connect(self.pressure_sb_changed) - self.phase_widget.temperature_sb.valueChanged.connect(self.temperature_sb_changed) - - self.phase_widget.show_in_pattern_cb.stateChanged.connect(self.update_phase_legend) + self.phase_widget.pressure_sb_value_changed.connect(self.pressure_sb_changed) + self.phase_widget.temperature_sb_value_changed.connect(self.temperature_sb_changed) self.phase_widget.phase_tw.currentCellChanged.connect(self.phase_selection_changed) self.phase_widget.color_btn_clicked.connect(self.color_btn_clicked) self.phase_widget.show_cb_state_changed.connect(self.show_cb_state_changed) + self.phase_widget.phase_tw.horizontalHeader().sectionClicked.connect(self.phase_tw_header_section_clicked) + self.pattern_widget.view_box.sigRangeChangedManually.connect(self.update_all_phase_intensities) # self.widget.pattern_view.view_box.sigRangeChanged.connect(self.update_all_phase_intensities) self.pattern_widget.pattern_plot.autoBtn.clicked.connect(self.update_all_phase_intensities) @@ -137,7 +139,6 @@ def add_btn_click_callback(self, *args, **kwargs): break progress_dialog.close() QtWidgets.QApplication.processEvents() - self.update_temperature_control_visibility() def _add_phase(self, filename): try: @@ -149,11 +150,12 @@ def _add_phase(self, filename): self.cif_conversion_dialog.int_cutoff, self.cif_conversion_dialog.min_d_spacing) - if self.phase_widget.apply_to_all_cb.isChecked(): - pressure = self.phase_widget.pressure_sb.value() - temperature = self.phase_widget.temperature_sb.value() + if self.phase_widget.apply_to_all_cb.isChecked() and len(self.model.phase_model.phases)>1 : + pressure = float(self.phase_widget.pressure_sbs[0].value()) + temperature = float(self.phase_widget.temperature_sbs[0].value()) self.model.phase_model.phases[-1].compute_d(pressure=pressure, temperature=temperature) + assert(True) else: pressure = 0 temperature = 298 @@ -176,7 +178,7 @@ def _add_phase(self, filename): def phase_added(self): self.model.phase_model.get_lines_d(-1) color = self.add_phase_plot() - print(self.model.phase_model.phases[-1].params['modified']) + self.phase_widget.add_phase(self.model.phase_model.phases[-1].name, '#%02x%02x%02x' % (int(color[0]), int(color[1]), int(color[2]))) @@ -185,6 +187,7 @@ def phase_added(self): self.update_temperature(len(self.model.phase_model.phases) - 1, self.model.phase_model.phases[-1].params['temperature']) self.update_phase_legend() + if self.jcpds_editor_controller.active: self.jcpds_editor_controller.show_phase(self.model.phase_model.phases[-1]) @@ -210,10 +213,11 @@ def add_phase_plot(self): def edit_btn_click_callback(self): cur_ind = self.phase_widget.get_selected_phase_row() - self.jcpds_editor_controller.show_phase(self.model.phase_model.phases[cur_ind]) - self.jcpds_editor_controller.show_view() + if cur_ind >= 0: + self.jcpds_editor_controller.show_phase(self.model.phase_model.phases[cur_ind]) + self.jcpds_editor_controller.show_view() - def remove_btn_click_callback(self): + def delete_btn_click_callback(self): """ Deletes the currently selected Phase """ @@ -224,7 +228,6 @@ def remove_btn_click_callback(self): def phase_removed(self, ind): self.phase_widget.del_phase(ind) self.pattern_widget.del_phase(ind) - self.update_temperature_control_visibility() if self.jcpds_editor_controller.active: ind = self.phase_widget.get_selected_phase_row() if ind >= 0: @@ -232,7 +235,7 @@ def phase_removed(self, ind): else: self.jcpds_editor_controller.widget.close() - def load_btn_clicked_callback(self): + def load_list_btn_clicked_callback(self): filename = open_file_dialog(self.integration_widget, caption="Load Phase List", directory=self.model.working_directories['phase'], filter="*.txt") @@ -250,13 +253,13 @@ def load_btn_clicked_callback(self): self.phase_widget.phase_color_btns[row].setStyleSheet('background-color:' + color) self.pattern_widget.set_phase_color(row, color) self.phase_widget.phase_tw.item(row, 2).setText(name) - self.phase_widget.set_phase_pressure(row, pressure.replace(' GPa', '')) - self.model.phase_model.set_pressure(row, float(pressure.replace(' GPa', ''))) - temperature = temperature.replace(' K', '').replace('-', '') + self.phase_widget.set_phase_pressure(row, float(pressure)) + self.model.phase_model.set_pressure(row, float(pressure)) + temperature = float(temperature) if temperature is not '': self.phase_widget.set_phase_temperature(row, temperature) - self.model.phase_model.set_temperature(row, float(temperature)) + self.model.phase_model.set_temperature(row, temperature) self.update_phase_intensities(row) self.update_phase_legend() @@ -278,26 +281,26 @@ def save_btn_clicked_callback(self): phase_file.write(file_name + ',' + str(phase_cb.isChecked()) + ',' + color_btn.styleSheet().replace('background-color:', '').replace(' ', '') + ',' + self.phase_widget.phase_tw.item(row, 2).text() + ',' + - self.phase_widget.phase_tw.item(row, 3).text() + ',' + - self.phase_widget.phase_tw.item(row, 4).text() + '\n') + self.phase_widget.pressure_sbs[row].text() + ',' + + self.phase_widget.temperature_sbs[row].text() + '\n') def clear_phases(self): """ Deletes all phases from the GUI and phase data """ while self.phase_widget.phase_tw.rowCount() > 0: - self.remove_btn_click_callback() + self.delete_btn_click_callback() self.jcpds_editor_controller.close_view() def update_pressure_step(self): - value = self.phase_widget.pressure_step_msb.value() - self.phase_widget.pressure_sb.setSingleStep(value) + for pressure_sb in self.phase_widget.pressure_sbs: + pressure_sb.setSingleStep(self.phase_widget.pressure_step_msb.value()) def update_temperature_step(self): - value = self.phase_widget.temperature_step_msb.value() - self.phase_widget.temperature_sb.setSingleStep(value) + for temperature_sb in self.phase_widget.temperature_sbs: + temperature_sb.setSingleStep(self.phase_widget.temperature_step_msb.value()) - def pressure_sb_changed(self, val): + def pressure_sb_changed(self, ind, val): """ Called when pressure spinbox emits a new value. Calculates the appropriate EOS values and updates line positions and intensities. @@ -309,15 +312,14 @@ def pressure_sb_changed(self, val): self.update_all_phase_intensities() else: - cur_ind = self.phase_widget.get_selected_phase_row() - self.model.phase_model.set_pressure(cur_ind, np.float(val)) - self.phase_widget.set_phase_pressure(cur_ind, val) - self.update_phase_intensities(cur_ind) + self.model.phase_model.set_pressure(ind, np.float(val)) + self.phase_widget.set_phase_pressure(ind, val) + self.update_phase_intensities(ind) self.update_phase_legend() self.update_jcpds_editor() - def temperature_sb_changed(self, val): + def temperature_sb_changed(self, ind, val): """ Called when temperature spinbox emits a new value. Calculates the appropriate EOS values and updates line positions and intensities. @@ -326,11 +328,9 @@ def temperature_sb_changed(self, val): for ind in range(len(self.model.phase_model.phases)): self.update_temperature(ind, val) self.update_all_phase_intensities() - else: - cur_ind = self.phase_widget.get_selected_phase_row() - self.update_temperature(cur_ind, val) - self.update_phase_intensities(cur_ind) + self.update_temperature(ind, val) + self.update_phase_intensities(ind) self.update_phase_legend() self.update_jcpds_editor() @@ -338,58 +338,28 @@ def update_temperature(self, ind, val): if self.model.phase_model.phases[ind].has_thermal_expansion(): self.model.phase_model.set_temperature(ind, np.float(val)) self.phase_widget.set_phase_temperature(ind, val) + self.phase_widget.temperature_sbs[ind].setEnabled(True) else: self.model.phase_model.set_temperature(ind, 298) - self.phase_widget.set_phase_temperature(ind, '-') + self.phase_widget.set_phase_temperature(ind, 298) + self.phase_widget.temperature_sbs[ind].setEnabled(False) self.update_phase_legend() def update_phase_legend(self): - value = self.phase_widget.show_in_pattern_cb.isChecked() - self.phase_widget.show_parameter_in_pattern = value for ind in range(len(self.model.phase_model.phases)): name = self.model.phase_model.phases[ind].name - if self.phase_widget.show_in_pattern_cb.isChecked(): - parameter_str = '' - pressure = self.model.phase_model.phases[ind].params['pressure'] - temperature = self.model.phase_model.phases[ind].params['temperature'] - if pressure != 0: - parameter_str += '{:0.2f} GPa '.format(pressure) - if temperature != 0 and temperature != 298 and temperature is not None: - parameter_str += '{:0.2f} K '.format(temperature) - self.pattern_widget.rename_phase(ind, parameter_str + name) - else: - self.pattern_widget.rename_phase(ind, name) + parameter_str = '' + pressure = self.model.phase_model.phases[ind].params['pressure'] + temperature = self.model.phase_model.phases[ind].params['temperature'] + if pressure != 0: + parameter_str += '{:0.2f} GPa '.format(pressure) + if temperature != 0 and temperature != 298 and temperature is not None: + parameter_str += '{:0.2f} K '.format(temperature) + self.pattern_widget.rename_phase(ind, parameter_str + name) def phase_selection_changed(self, row, col, prev_row, prev_col): - cur_ind = row - pressure = self.model.phase_model.phases[cur_ind].params['pressure'] - temperature = self.model.phase_model.phases[cur_ind].params['temperature'] - - self.phase_widget.pressure_sb.blockSignals(True) - self.phase_widget.pressure_sb.setValue(pressure) - self.phase_widget.pressure_sb.blockSignals(False) - - self.phase_widget.temperature_sb.blockSignals(True) - self.phase_widget.temperature_sb.setValue(temperature) - self.phase_widget.temperature_sb.blockSignals(False) - self.update_temperature_control_visibility(row) - if self.jcpds_editor_controller.active: - self.jcpds_editor_controller.show_phase(self.model.phase_model.phases[cur_ind]) - - def update_temperature_control_visibility(self, row_ind=None): - if row_ind is None: - row_ind = self.phase_widget.get_selected_phase_row() - - if row_ind == -1: - return - - if self.model.phase_model.phases[row_ind].has_thermal_expansion(): - self.phase_widget.temperature_sb.setEnabled(True) - self.phase_widget.temperature_step_msb.setEnabled(True) - else: - self.phase_widget.temperature_sb.setDisabled(True) - self.phase_widget.temperature_step_msb.setDisabled(True) + self.jcpds_editor_controller.show_phase(self.model.phase_model.phases[row]) def color_btn_clicked(self, ind, button): previous_color = button.palette().color(1) @@ -407,6 +377,19 @@ def show_cb_state_changed(self, ind, state): else: self.pattern_widget.hide_phase(ind) + def phase_tw_header_section_clicked(self, ind): + if ind != 0: + return + + current_checkbox_state = False + # check whether any checkbox is checked, if one is true current_checkbox_state will be True too + for cb in self.phase_widget.phase_show_cbs: + current_checkbox_state = current_checkbox_state or cb.isChecked() + + # assign the the opposite to all checkboxes + for cb in self.phase_widget.phase_show_cbs: + cb.setChecked(not current_checkbox_state) + def get_unit(self): """ returns the unit currently selected in the GUI @@ -477,7 +460,6 @@ def update_cur_phase_parameters(self): cur_ind = self.phase_widget.get_selected_phase_row() self.model.phase_model.get_lines_d(cur_ind) self.update_phase_intensities(cur_ind) - self.update_temperature_control_visibility(cur_ind) self.pattern_widget.update_phase_line_visibility(cur_ind) def jcpds_editor_reflection_removed(self, reflection_ind): diff --git a/dioptas/controller/integration/__init__.py b/dioptas/controller/integration/__init__.py index d3a9e3e96..0de1019b9 100644 --- a/dioptas/controller/integration/__init__.py +++ b/dioptas/controller/integration/__init__.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/controller/integration/econfig.py b/dioptas/controller/integration/econfig.py index f901b75e5..c59e7f851 100644 --- a/dioptas/controller/integration/econfig.py +++ b/dioptas/controller/integration/econfig.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/model/CalibrationModel.py b/dioptas/model/CalibrationModel.py index 2ba9419fd..7d1f8c19d 100755 --- a/dioptas/model/CalibrationModel.py +++ b/dioptas/model/CalibrationModel.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -62,6 +64,11 @@ def __init__(self, img_model=None): self.orig_pixel2 = 79e-6 self.fit_wavelength = False self.fit_distance = True + self.fit_poni1 = True + self.fit_poni2 = True + self.fit_rot1 = True + self.fit_rot2 = True + self.fit_rot3 = True self.is_calibrated = False self.use_mask = False self.filename = '' @@ -71,6 +78,8 @@ def __init__(self, img_model=None): self.correct_solid_angle = True self._calibrants_working_dir = calibrants_path + self.distortion_spline_filename = None + self.tth = np.linspace(0, 25) self.int = np.sin(self.tth) self.num_points = len(self.int) @@ -133,8 +142,15 @@ def clear_peaks(self): self.points = [] self.points_index = [] + def remove_last_peak(self): + if self.points: + num_points = int(self.points[-1].size/2) # each peak is x, y so length is twice as number of peaks + self.points.pop(-1) + self.points_index.pop(-1) + return num_points + def create_cake_geometry(self): - self.cake_geometry = AzimuthalIntegrator() + self.cake_geometry = AzimuthalIntegrator(splineFile=self.distortion_spline_filename) pyFAI_parameter = self.pattern_geometry.getPyFAI() pyFAI_parameter['polarization_factor'] = self.polarization_factor @@ -198,6 +214,8 @@ def search_peaks_on_ring(self, ring_index, delta_tth=0.1, min_mean_factor=1, # get appropriate two theta value for the ring number tth_calibrant_list = self.calibrant.get_2th() + if ring_index >= len(tth_calibrant_list): + raise NotEnoughSpacingsInCalibrant() tth_calibrant = np.float(tth_calibrant_list[ring_index]) # get the calculated two theta values for the whole image @@ -254,7 +272,8 @@ def calibrate(self): wavelength=self.start_values['wavelength'], pixel1=self.start_values['pixel_width'], pixel2=self.start_values['pixel_height'], - calibrant=self.calibrant) + calibrant=self.calibrant, + splineFile=self.distortion_spline_filename) self.orig_pixel1 = self.start_values['pixel_width'] self.orig_pixel2 = self.start_values['pixel_height'] @@ -276,6 +295,16 @@ def refine(self): fix = [] if not self.fit_distance: fix.append('dist') + if not self.fit_poni1: + fix.append('poni1') + if not self.fit_poni2: + fix.append('poni2') + if not self.fit_rot1: + fix.append('rot1') + if not self.fit_rot2: + fix.append('rot2') + if not self.fit_rot3: + fix.append('rot3') if self.fit_wavelength: self.pattern_geometry.refine2() self.pattern_geometry.refine2_wavelength(fix=fix) @@ -348,7 +377,9 @@ def integrate_1d(self, num_points=None, mask=None, polarization_factor=None, fil self.int = self.int[ind] return self.tth, self.int - def integrate_2d(self, mask=None, polarization_factor=None, unit='2th_deg', method='csr', dimensions=(2048, 2048)): + def integrate_2d(self, mask=None, polarization_factor=None, unit='2th_deg', method='csr', + rad_points=None, azimuth_points=360, + azimuth_range=None): if polarization_factor is None: polarization_factor = self.polarization_factor @@ -357,9 +388,14 @@ def integrate_2d(self, mask=None, polarization_factor=None, unit='2th_deg', meth self.cake_geometry.reset() self.cake_geometry_img_shape = self.img_model.img_data.shape + if rad_points is None: + rad_points = self.calculate_number_of_pattern_points(2) + self.num_points = rad_points + t1 = time.time() - res = self.cake_geometry.integrate2d(self.img_model.img_data, dimensions[0], dimensions[1], + res = self.cake_geometry.integrate2d(self.img_model.img_data, rad_points, azimuth_points, + azimuth_range=azimuth_range, method=method, mask=mask, unit=unit, @@ -495,6 +531,18 @@ def set_pyFAI(self, pyFAI_parameter): self.is_calibrated = True self.set_supersampling() + def load_distortion(self, spline_filename): + self.distortion_spline_filename = spline_filename + self.pattern_geometry.set_splineFile(spline_filename) + if self.cake_geometry: + self.cake_geometry.set_splineFile(spline_filename) + + def reset_distortion_correction(self): + self.distortion_spline_filename = None + self.pattern_geometry.set_splineFile(None) + if self.cake_geometry: + self.cake_geometry.set_splineFile(None) + def set_supersampling(self, factor=None): """ Sets the supersampling to a specific factor. Whereby the factor determines in how many artificial pixel the @@ -523,27 +571,31 @@ def get_two_theta_img(self, x, y): """ Gives the two_theta value for the x,y coordinates on the image. Be aware that this function will be incorrect for pixel indices, since it does not correct for center of the pixel. - :return: - two theta in radians + :param x: x-coordinate in pixel on the image + :type x: ndarray + :param y: y-coordinate in pixel on the image + :type y: ndarray + + :return : two theta in radians """ - x = np.array([x]) * self.supersampling_factor - y = np.array([y]) * self.supersampling_factor + x *= self.supersampling_factor + y *= self.supersampling_factor return self.pattern_geometry.tth(x - 0.5, y - 0.5)[0] # deletes 0.5 because tth function uses pixel indices def get_azi_img(self, x, y): """ Gives chi for position on image. - :param x: - x-coordinate in pixel - :param y: - y-coordinate in pixel - :return: - azimuth in radians + :param x: x-coordinate in pixel on the image + :type x: ndarray + :param y: y-coordinate in pixel on the image + :type y: ndarray + + :return : azimuth in radians """ x *= self.supersampling_factor y *= self.supersampling_factor - return self.pattern_geometry.chi(x, y)[0] + return self.pattern_geometry.chi(x - 0.5, y - 0.5)[0] def get_two_theta_array(self): return self.pattern_geometry.twoThetaArray(self.img_model.img_data.shape)[::self.supersampling_factor, @@ -573,6 +625,10 @@ def wavelength(self): return self.pattern_geometry.wavelength +class NotEnoughSpacingsInCalibrant(Exception): + pass + + class DummyStdOut(object): @classmethod def write(cls, *args, **kwargs): diff --git a/dioptas/model/Configuration.py b/dioptas/model/Configuration.py index 5f8fa3d41..2538f0758 100644 --- a/dioptas/model/Configuration.py +++ b/dioptas/model/Configuration.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -57,10 +59,12 @@ def __init__(self, working_directories=None): self.transparent_mask = False - self._integration_num_points = None - self._integration_azimuth_points = 2048 + self._integration_rad_points = None self._integration_unit = '2th_deg' + self._cake_azimuth_points = 360 + self._cake_azimuth_range = None + self._auto_integrate_pattern = True self._auto_integrate_cake = False @@ -92,7 +96,7 @@ def integrate_image_1d(self): mask = None x, y = self.calibration_model.integrate_1d(mask=mask, unit=self.integration_unit, - num_points=self.integration_num_points) + num_points=self.integration_rad_points) self.pattern_model.set_pattern(x, y, self.img_model.filename, unit=self.integration_unit) # @@ -113,7 +117,10 @@ def integrate_image_2d(self): mask = None self.calibration_model.integrate_2d(mask=mask, - dimensions=(2048, 2048)) + rad_points=self._integration_rad_points, + azimuth_points=self._cake_azimuth_points, + azimuth_range=self._cake_azimuth_range) + self.cake_changed.emit() def save_pattern(self, filename=None, subtract_background=False): @@ -134,6 +141,21 @@ def save_pattern(self, filename=None, subtract_background=False): else: self.pattern_model.save_pattern(filename, subtract_background=subtract_background) + def save_background_pattern(self, filename=None): + """ + Saves the current fit background as a pattern. The format depends on the file ending. Possible file formats: + [*.xy, *.chi, *.dat, *.fxye] + """ + if filename is None: + filename = self.img_model.filename + + if filename.endswith('.xy'): + self.pattern_model.save_background_as_pattern(filename, header=self._create_xy_header()) + elif filename.endswith('.fxye'): + self.pattern_model.save_background_as_pattern(filename, header=self._create_fxye_header(filename)) + else: + self.pattern_model.save_pattern(filename) + def _create_xy_header(self): """ Creates the header for the xy file format (contains information about calibration parameters). @@ -191,13 +213,35 @@ def update_mask_dimension(self): self.mask_model.set_dimension(self.img_model._img_data.shape) @property - def integration_num_points(self): - return self._integration_num_points + def integration_rad_points(self): + return self._integration_rad_points - @integration_num_points.setter - def integration_num_points(self, new_value): - self._integration_num_points = new_value + @integration_rad_points.setter + def integration_rad_points(self, new_value): + self._integration_rad_points = new_value self.integrate_image_1d() + if self.auto_integrate_cake: + self.integrate_image_2d() + + @property + def cake_azimuth_points(self): + return self._cake_azimuth_points + + @cake_azimuth_points.setter + def cake_azimuth_points(self, new_value): + self._cake_azimuth_points = new_value + if self.auto_integrate_cake: + self.integrate_image_2d() + + @property + def cake_azimuth_range(self): + return self._cake_azimuth_range + + @cake_azimuth_range.setter + def cake_azimuth_range(self, new_value): + self._cake_azimuth_range = new_value + if self.auto_integrate_cake: + self.integrate_image_2d() @property def integration_unit(self): @@ -326,14 +370,26 @@ def save_in_hdf5(self, hdf5_group): f = hdf5_group # save general information general_information = f.create_group('general_information') + # integration parameters: general_information.attrs['integration_unit'] = self.integration_unit - if self.integration_num_points: - general_information.attrs['integration_num_points'] = self.integration_num_points + if self.integration_rad_points: + general_information.attrs['integration_num_points'] = self.integration_rad_points else: general_information.attrs['integration_num_points'] = 0 + + # cake parameters: general_information.attrs['auto_integrate_cake'] = self.auto_integrate_cake + general_information.attrs['cake_azimuth_points'] = self.cake_azimuth_points + if self.cake_azimuth_range is None: + general_information.attrs['cake_azimuth_range'] = "None" + else: + general_information.attrs['cake_azimuth_range'] = self.cake_azimuth_range + + # mask parameters general_information.attrs['use_mask'] = self.use_mask general_information.attrs['transparent_mask'] = self.transparent_mask + + # auto save parameters general_information.attrs['auto_save_integrated_pattern'] = self.auto_save_integrated_pattern formats = [n.encode('ascii', 'ignore') for n in self.integrated_patterns_file_formats] general_information.create_dataset('integrated_patterns_file_formats', (len(formats), 1), 'S10', formats) @@ -368,14 +424,20 @@ def save_in_hdf5(self, hdf5_group): corrections_group = image_group.create_group('corrections') corrections_group.attrs['has_corrections'] = self.img_model.has_corrections() for correction, correction_object in self.img_model.img_corrections.corrections.items(): - correction_data = correction_object.get_data() - imcd = corrections_group.create_dataset(correction, correction_data.shape, 'f', correction_data) - if correction == 'cbn': - for param, value in correction_object.get_params().items(): - imcd.attrs[param] = value - elif correction == 'oiadac': + if correction in ['cbn', 'oiadac']: + correction_data = correction_object.get_data() + imcd = corrections_group.create_dataset(correction, correction_data.shape, 'f', correction_data) for param, value in correction_object.get_params().items(): imcd.attrs[param] = value + elif correction == 'transfer': + params = correction_object.get_params() + transfer_group = corrections_group.create_group('transfer') + original_data = params['original_data'] + response_data = params['response_data'] + original_ds = transfer_group.create_dataset('original_data', original_data.shape, 'f', original_data) + original_ds.attrs['filename'] = params['original_filename'] + response_ds = transfer_group.create_dataset('response_data', response_data.shape, 'f', response_data) + response_ds.attrs['filename'] = params['response_filename'] # the actual image image_group.attrs['filename'] = self.img_model.filename @@ -422,6 +484,8 @@ def save_in_hdf5(self, hdf5_group): except TypeError: pfp.attrs[key] = '' calibration_group.attrs['correct_solid_angle'] = self.correct_solid_angle + if self.calibration_model.distortion_spline_filename is not None: + calibration_group.attrs['distortion_spline_filename'] = self.calibration_model.distortion_spline_filename # save background pattern and pattern model background_pattern_group = f.create_group('background_pattern') @@ -508,10 +572,15 @@ def load_from_hdf5(self, hdf5_group): pass try: - self.correct_solid_angle = f.get('calibration_model').attrs['correct_solid_angle'] + self.correct_solid_angle = f.get('calibration_model').attrs['correct_solid_angle'] except KeyError: pass + try: + distortion_spline_filename = f.get('calibration_model').attrs['distortion_spline_filename'] + self.calibration_model.load_distortion(distortion_spline_filename) + except KeyError: + pass # load img_model self.img_model._img_data = np.copy(f.get('image_model').get('raw_image_data')[...]) @@ -555,6 +624,7 @@ def load_from_hdf5(self, hdf5_group): f.get('pattern').attrs['pattern_filename'], f.get('pattern').attrs['unit']) self.pattern_model.file_iteration_mode = f.get('pattern').attrs['file_iteration_mode'] + self.integration_unit = f.get('general_information').attrs['integration_unit'] if f.get('background_pattern').attrs['has_background_pattern']: self.pattern_model.background_pattern = Pattern(f.get('background_pattern').get('x')[...], @@ -573,40 +643,67 @@ def load_from_hdf5(self, hdf5_group): recalc_pattern=False) # load general configuration - self.integration_unit = f.get('general_information').attrs['integration_unit'] if f.get('general_information').attrs['integration_num_points']: - self.integration_num_points = f.get('general_information').attrs['integration_num_points'] + self.integration_rad_points = f.get('general_information').attrs['integration_num_points'] + # cake parameters: self.auto_integrate_cake = f.get('general_information').attrs['auto_integrate_cake'] + try: + self.cake_azimuth_points = f.get('general_information').attrs['cake_azimuth_points'] + except KeyError as e: + pass + try: + if f.get('general_information').attrs['cake_azimuth_range'] == "None": + self.cake_azimuth_range = None + else: + self.cake_azimuth_range = f.get('general_information').attrs['cake_azimuth_range'] + except KeyError as e: + pass + + # mask parameters self.use_mask = f.get('general_information').attrs['use_mask'] self.transparent_mask = f.get('general_information').attrs['transparent_mask'] - self.auto_save_integrated_pattern = f.get('general_information').attrs['auto_save_integrated_pattern'] - self.integrated_patterns_file_formats = [] - for file_format in f.get('general_information').get('integrated_patterns_file_formats'): - self.integrated_patterns_file_formats.append(file_format[0].decode('utf-8')) - - self.integrate_image_1d() - + # corrections if f.get('image_model').get('corrections').attrs['has_corrections']: for name, correction_group in f.get('image_model').get('corrections').items(): + params = {} + for param, val in correction_group.attrs.items(): + params[param] = val if name == 'cbn': tth_array = 180.0 / np.pi * self.calibration_model.pattern_geometry.ttha azi_array = 180.0 / np.pi * self.calibration_model.pattern_geometry.chia cbn_correction = CbnCorrection(tth_array=tth_array, azi_array=azi_array) - params = {} - for param, val in correction_group.attrs.items(): - params[param] = val + cbn_correction.set_params(params) cbn_correction.update() - self.img_model.add_img_correction(cbn_correction, name, name) + self.img_model.add_img_correction(cbn_correction, name) elif name == 'oiadac': tth_array = 180.0 / np.pi * self.calibration_model.pattern_geometry.ttha azi_array = 180.0 / np.pi * self.calibration_model.pattern_geometry.chia oiadac = ObliqueAngleDetectorAbsorptionCorrection(tth_array=tth_array, azi_array=azi_array) - params = {} - for param, val in correction_group.attrs.items(): - params[param] = val + oiadac.set_params(params) oiadac.update() - self.img_model.add_img_correction(oiadac, name, name) + self.img_model.add_img_correction(oiadac, name) + elif name == 'transfer': + params = { + 'original_data': correction_group.get('original_data')[...], + 'original_filename': correction_group.get('original_data').attrs['filename'], + 'response_data': correction_group.get('response_data')[...], + 'response_filename': correction_group.get('response_data').attrs['filename'] + } + + self.img_model.transfer_correction.set_params(params) + self.img_model.enable_transfer_function() + + # autosave parameters + self.auto_save_integrated_pattern = f.get('general_information').attrs['auto_save_integrated_pattern'] + self.integrated_patterns_file_formats = [] + for file_format in f.get('general_information').get('integrated_patterns_file_formats'): + self.integrated_patterns_file_formats.append(file_format[0].decode('utf-8')) + + if self.calibration_model.is_calibrated: + self.integrate_image_1d() + else: + self.pattern_model.pattern.recalculate_pattern() diff --git a/dioptas/model/DioptasModel.py b/dioptas/model/DioptasModel.py index bfe4313d4..05929b994 100644 --- a/dioptas/model/DioptasModel.py +++ b/dioptas/model/DioptasModel.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -144,7 +146,6 @@ def save(self, filename): phase_reflection_group.attrs['h'] = reflection.h phase_reflection_group.attrs['k'] = reflection.k phase_reflection_group.attrs['l'] = reflection.l - print(phase_parameter_group.attrs['modified']) f.flush() f.close() diff --git a/dioptas/model/ImgModel.py b/dioptas/model/ImgModel.py index c731ad054..2c819934c 100755 --- a/dioptas/model/ImgModel.py +++ b/dioptas/model/ImgModel.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -29,7 +31,7 @@ from .util.spe import SpeFile from .util.NewFileWatcher import NewFileInDirectoryWatcher from .util.HelperModule import rotate_matrix_p90, rotate_matrix_m90, FileNameIterator -from .util.ImgCorrection import ImgCorrectionManager, ImgCorrectionInterface +from .util.ImgCorrection import ImgCorrectionManager, ImgCorrectionInterface, TransferFunctionCorrection logger = logging.getLogger(__name__) @@ -49,8 +51,8 @@ class ImgModel(QtCore.QObject): """ img_changed = QtCore.Signal() autoprocess_changed = QtCore.Signal() - cbn_correction_changed = QtCore.Signal() - oiadac_correction_changed = QtCore.Signal() + transformations_changed = QtCore.Signal() + corrections_removed = QtCore.Signal() def __init__(self): super(ImgModel, self).__init__() @@ -78,13 +80,15 @@ def __init__(self): self._factor = 1 + self.transfer_correction = TransferFunctionCorrection() + self.file_info = '' self.motors_info = {} self._img_corrections = ImgCorrectionManager() self._img_data = np.zeros((2048, 2048)) - ### setting up autoprocess + # setting up autoprocess self._autoprocess = False self._directory_watcher = NewFileInDirectoryWatcher( file_types=['.img', '.sfrm', '.dm3', '.edf', '.xml', @@ -180,16 +184,16 @@ def add(self, filename): img_data_fabio = fabio.open(filename) img_data = img_data_fabio.data[::-1] - if not self._img_data.shape == img_data.shape: - return - for transformation in self.img_transformations: img_data = transformation(img_data) + if not self._img_data.shape == img_data.shape: + return + logger.info("Adding {0}.".format(filename)) - if self._img_data.dtype == np.uint16: # if dtype is only uint16 we will convert to 32 bit, so that more - # additions are possible + if self._img_data.dtype == np.uint16: # if dtype is only uint16 we will convert to 32 bit, so that more + # additions are possible self._img_data = self._img_data.astype(np.uint32) self._img_data += img_data @@ -323,7 +327,10 @@ def _calculate_img_data(self): if self._img_data.shape != self._background_data.shape: self._background_data = None if self._img_corrections.has_items(): - self._img_corrections.set_shape(self._img_data.shape) + if self._img_data.shape != self._img_corrections.shape: + self._img_corrections.clear() + self.transfer_correction.reset() + self.corrections_removed.emit() # calculate the current _img_data if self._background_data is not None and not self._img_corrections.has_items(): @@ -335,7 +342,7 @@ def _calculate_img_data(self): elif self._background_data is not None and self._img_corrections.has_items(): self._img_data_background_subtracted_absorption_corrected = (self._img_data - ( - self._background_scaling * self._background_data + self._background_offset)) / \ + self._background_scaling * self._background_data + self._background_offset)) / \ self._img_corrections.get_data() # supersample the current image data @@ -391,7 +398,6 @@ def img_data(self): return self._img_data_supersampled_background_subtracted_absorption_corrected * self.factor return self._img_data * self.factor - @property def raw_img_data(self): return self._img_data @@ -409,6 +415,7 @@ def rotate_img_p90(self): self.img_transformations.append(rotate_matrix_p90) + self.transformations_changed.emit() self._calculate_img_data() self.img_changed.emit() @@ -422,6 +429,7 @@ def rotate_img_m90(self): if self._background_data is not None: self._background_data = rotate_matrix_m90(self._background_data) self.img_transformations.append(rotate_matrix_m90) + self.transformations_changed.emit() self._calculate_img_data() self.img_changed.emit() @@ -436,6 +444,7 @@ def flip_img_horizontally(self): if self._background_data is not None: self._background_data = np.fliplr(self._background_data) self.img_transformations.append(np.fliplr) + self.transformations_changed.emit() self._calculate_img_data() self.img_changed.emit() @@ -450,6 +459,7 @@ def flip_img_vertically(self): if self._background_data is not None: self._background_data = np.flipud(self._background_data) self.img_transformations.append(np.flipud) + self.transformations_changed.emit() self._calculate_img_data() self.img_changed.emit() @@ -473,6 +483,8 @@ def reset_img_transformations(self): if self._background_data is not None: self._background_data = transformation(self._background_data) self.img_transformations = [] + self.transformations_changed.emit() + self._calculate_img_data() self.img_changed.emit() @@ -529,7 +541,6 @@ def load_transformations_string_list(self, transformations): self._perform_img_transformations() self._perform_background_transformations() - def set_supersampling(self, factor=None): """ Stores the supersampling factor and calculates supersampled original and background image arrays. @@ -559,7 +570,7 @@ def supersample_data(self, img_data, factor): else: return img_data - def add_img_correction(self, correction, name=None, external=None): + def add_img_correction(self, correction, name=None): """ Adds a correction to be applied to the image. Corrections are applied multiplicative for each pixel and after each other, depending on the order of addition. @@ -572,10 +583,6 @@ def add_img_correction(self, correction, name=None, external=None): self._img_corrections.add(correction, name) self._calculate_img_data() self.img_changed.emit() - if external == 'cbn': - self.cbn_correction_changed.emit() - if external == 'oiadac': - self.oiadac_correction_changed.emit() def get_img_correction(self, name): """ @@ -593,6 +600,18 @@ def delete_img_correction(self, name=None): self._calculate_img_data() self.img_changed.emit() + def enable_transfer_function(self): + if self.transfer_correction.get_data() is not None and \ + self.get_img_correction('transfer') is None: + self.add_img_correction(self.transfer_correction, 'transfer') + if self.get_img_correction('transfer') is not None: + self._calculate_img_data() + self.img_changed.emit() + + def disable_transfer_function(self): + if self.get_img_correction('transfer') is not None: + self.delete_img_correction('transfer') + @property def img_corrections(self): return self._img_corrections diff --git a/dioptas/model/MaskModel.py b/dioptas/model/MaskModel.py index 58b161086..0fe97b99d 100755 --- a/dioptas/model/MaskModel.py +++ b/dioptas/model/MaskModel.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/model/OverlayModel.py b/dioptas/model/OverlayModel.py index 40219a0f1..62246cab9 100644 --- a/dioptas/model/OverlayModel.py +++ b/dioptas/model/OverlayModel.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -58,8 +60,8 @@ def add_overlay_pattern(self, pattern): Adds a pattern as overlay to the list of overlays, does not use its original scaling parameters """ overlay_pattern = Pattern(np.copy(pattern.x), - np.copy(pattern.y), - copy(pattern.name)) + np.copy(pattern.y), + copy(pattern.name)) self.overlays.append(overlay_pattern) self.overlay_added.emit() @@ -91,7 +93,6 @@ def get_overlay(self, ind): except IndexError: return None - def set_overlay_scaling(self, ind, scaling): """ Sets the scaling of the specified overlay @@ -126,7 +127,6 @@ def get_overlay_offset(self, ind): """ return self.overlays[ind].offset - def overlay_waterfall(self, separation): offset = 0 for ind in range(len(self.overlays)): @@ -134,7 +134,6 @@ def overlay_waterfall(self, separation): self.overlays[-(ind + 1)].offset = offset self.overlay_changed.emit(len(self.overlays) - (ind + 1)) - def reset_overlay_offsets(self): for ind, overlay in enumerate(self.overlays): overlay.offset = 0 diff --git a/dioptas/model/PatternModel.py b/dioptas/model/PatternModel.py index 5963346d1..60fdd3818 100755 --- a/dioptas/model/PatternModel.py +++ b/dioptas/model/PatternModel.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -122,6 +124,46 @@ def save_pattern(self, filename, header=None, subtract_background=False): file_handle.write('{0:.9E} {1:.9E}\n'.format(x[ind], y[ind])) file_handle.close() + def save_background_as_pattern(self, filename, header=None): + """ + Saves the current data pattern. + :param filename: where to save + :param header: you can specify any specific header + """ + x, y = self.pattern.auto_background_pattern.data + + file_handle = open(filename, 'w') + num_points = len(x) + + if filename.endswith('.chi'): + if header is None or header == '': + file_handle.write(filename + '\n') + file_handle.write(self.unit + '\n\n') + file_handle.write(" {0}\n".format(num_points)) + else: + file_handle.write(header) + for ind in range(num_points): + file_handle.write(' {0:.7E} {1:.7E}\n'.format(x[ind], y[ind])) + elif filename.endswith('.fxye'): + factor = 100 + if 'CONQ' in header: + factor = 1 + header = header.replace('NUM_POINTS', '{0:.6g}'.format(num_points)) + header = header.replace('MIN_X_VAL', '{0:.6g}'.format(factor * x[0])) + header = header.replace('STEP_X_VAL', '{0:.6g}'.format(factor * (x[1] - x[0]))) + + file_handle.write(header) + file_handle.write('\n') + for ind in range(num_points): + file_handle.write('\t{0:.6g}\t{1:.6g}\t{2:.6g}\n'.format(factor * x[ind], y[ind], sqrt(abs(y[ind])))) + else: + if header is not None: + file_handle.write(header) + file_handle.write('\n') + for ind in range(num_points): + file_handle.write('{0:.9E} {1:.9E}\n'.format(x[ind], y[ind])) + file_handle.close() + def get_pattern(self): return self.pattern diff --git a/dioptas/model/PhaseModel.py b/dioptas/model/PhaseModel.py index 93414c147..8ab997ba6 100755 --- a/dioptas/model/PhaseModel.py +++ b/dioptas/model/PhaseModel.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/model/__init__.py b/dioptas/model/__init__.py index ca55c2e44..56330a4d8 100755 --- a/dioptas/model/__init__.py +++ b/dioptas/model/__init__.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/model/util/BackgroundExtraction.py b/dioptas/model/util/BackgroundExtraction.py index 2a478a0c3..a6271033f 100644 --- a/dioptas/model/util/BackgroundExtraction.py +++ b/dioptas/model/util/BackgroundExtraction.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -24,13 +26,21 @@ try: from .smooth_bruckner import smooth_bruckner -except ImportError as e: - print(e) - logger.warning( - "Could not import the Fortran version of smooth_bruckner. Using python implementation instead. Please" - " run 'f2py -c -m smooth_bruckner smooth_bruckner.f95' in the model/util folder for faster" - " implementation") - from .smooth_bruckner_python import smooth_bruckner +except ImportError: + try: + from .smooth_bruckner_cython import smooth_bruckner + except ImportError: + try: + import pyximport + pyximport.install(language_level=3) + from .smooth_bruckner_cython import smooth_bruckner + except ImportError as e: + print(e) + logger.warning( + "Could not import the Fortran or Cython version of smooth_bruckner. Using python implementation instead. Please" + " run 'f2py -c -m smooth_bruckner smooth_bruckner.f95' in the model/util folder for faster" + " implementation") + from .smooth_bruckner_python import smooth_bruckner def extract_background(x, y, smooth_width=0.1, iterations=50, cheb_order=50): diff --git a/dioptas/model/util/HelperModule.py b/dioptas/model/util/HelperModule.py index 9c7a7fdd7..3b48ab2ff 100755 --- a/dioptas/model/util/HelperModule.py +++ b/dioptas/model/util/HelperModule.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -17,7 +19,6 @@ # along with this program. If not, see . - import os import re import time @@ -87,23 +88,27 @@ def _iterate_file_number(self, path, step, pos=None): match_iterator = pattern.finditer(file_str) for ind, match in enumerate(reversed(list(match_iterator))): - number_span = match.span() - left_ind = number_span[0] - right_ind = number_span[1] - number = int(file_str[left_ind:right_ind]) + step - new_file_str = "{left_str}{number:0{len}}{right_str}".format( - left_str=file_str[:left_ind], - number=number, - len=right_ind - left_ind, - right_str=file_str[right_ind:] - ) - if pos is None: + if (pos is None) or (ind == pos): + number_span = match.span() + left_ind = number_span[0] + right_ind = number_span[1] + number = int(file_str[left_ind:right_ind]) + step + new_file_str = "{left_str}{number:0{len}}{right_str}".format( + left_str=file_str[:left_ind], + number=number, + len=right_ind - left_ind, + right_str=file_str[right_ind:] + ) + new_file_str_no_leading_zeros = "{left_str}{number}{right_str}".format( + left_str=file_str[:left_ind], + number=number, + right_str=file_str[right_ind:] + ) new_complete_path = os.path.join(directory, new_file_str) if os.path.exists(new_complete_path): self.complete_path = new_complete_path return new_complete_path - elif ind == pos: - new_complete_path = os.path.join(directory, new_file_str) + new_complete_path = os.path.join(directory, new_file_str_no_leading_zeros) if os.path.exists(new_complete_path): self.complete_path = new_complete_path return new_complete_path @@ -226,7 +231,7 @@ def update_filename(self, new_filename): except AttributeError: pass if self.directory != new_directory: - if self.directory is not None and self.directory !='': + if self.directory is not None and self.directory != '': self.directory_watcher.removePath(self.directory) self.directory_watcher.addPath(new_directory) self.directory = new_directory diff --git a/dioptas/model/util/ImgCorrection.py b/dioptas/model/util/ImgCorrection.py index bc70242cb..41e32116b 100644 --- a/dioptas/model/util/ImgCorrection.py +++ b/dioptas/model/util/ImgCorrection.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -16,9 +18,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from qtpy import QtCore - import numpy as np +import fabio +from PIL import Image class ImgCorrectionManager(object): @@ -57,11 +59,6 @@ def clear(self): self.shape = None self._ind = 0 - def set_shape(self, shape): - if self.shape != shape: - self.clear() - self.shape = shape - def get_data(self): if len(self._corrections) == 0: return None @@ -165,7 +162,7 @@ def update(self): # calculate radius of the cone for each pixel specific to a center_offset and rotation angle if self._center_offset != 0: beta = azi - np.arcsin( - self._center_offset * np.sin((np.pi - (azi + center_offset_angle))) / r1) + center_offset_angle + self._center_offset * np.sin((np.pi - (azi + center_offset_angle))) / r1) + center_offset_angle r1 = np.sqrt(r1 ** 2 + self._center_offset ** 2 - 2 * r1 * self._center_offset * np.cos(beta)) r2 = np.sqrt(r2 ** 2 + self._center_offset ** 2 - 2 * r2 * self._center_offset * np.cos(beta)) @@ -287,8 +284,8 @@ def update(self): rotation_rad = self.rotation / 180.0 * np.pi path_length = self.detector_thickness / np.cos( - np.sqrt(self.tth_array ** 2 + tilt_rad ** 2 - 2 * tilt_rad * self.tth_array * \ - np.cos(np.pi - self.azi_array + rotation_rad))) + np.sqrt(self.tth_array ** 2 + tilt_rad ** 2 - 2 * tilt_rad * self.tth_array * \ + np.cos(np.pi - self.azi_array + rotation_rad))) attenuation_constant = 1.0 / self.absorption_length absorption_correction = (1 - np.exp(-attenuation_constant * path_length)) / \ @@ -297,6 +294,79 @@ def update(self): self._data = absorption_correction +class TransferFunctionCorrection(ImgCorrectionInterface): + def __init__(self, original_filename=None, response_filename=None, img_transformations=None): + self.original_filename = None + self.response_filename = None + self.original_data = None + self.response_data = None + self.transfer_data = None + + self.img_transformations = img_transformations + + if original_filename: + self.load_original_image(original_filename) + if response_filename: + self.load_response_image(response_filename) + + def load_original_image(self, img_filename): + self.original_filename = img_filename + self.original_data = load_image(img_filename) + if self.response_filename: + self.calculate_transfer_data() + + def load_response_image(self, img_filename): + self.response_filename = img_filename + self.response_data = load_image(img_filename) + if self.original_filename: + self.calculate_transfer_data() + + def set_img_transformations(self, img_transformations): + """ + sets the image transformations + :param img_transformations: + """ + self.img_transformations = img_transformations + if self.response_filename and self.original_filename: + self.calculate_transfer_data() + + def calculate_transfer_data(self): + transfer_data = self.response_data / self.original_data + if self.img_transformations: + for transformation in self.img_transformations: + transfer_data = transformation(transfer_data) + self.transfer_data = transfer_data + + def get_data(self): + return self.transfer_data + + def shape(self): + return self.transfer_data.shape + + def get_params(self): + return { + 'original_filename': self.original_filename, + 'response_filename': self.response_filename, + 'original_data': self.original_data, + 'response_data': self.response_data, + } + + def set_params(self, params): + self.original_filename = params['original_filename'] + self.response_filename = params['response_filename'] + self.original_data = params['original_data'] + self.response_data = params['response_data'] + self.calculate_transfer_data() + + def reset(self): + self.original_filename = None + self.response_filename = None + self.original_data = None + self.response_data = None + self.transfer_data = None + self.img_transformations = None + + class DummyCorrection(ImgCorrectionInterface): """ Used in particular for unit tests @@ -313,6 +383,17 @@ def shape(self): return self._shape +def load_image(filename): + try: + im = Image.open(filename) + img_data = np.array(im)[::-1] + im.close() + except IOError: + _img_data_fabio = fabio.open(filename) + img_data = _img_data_fabio.data[::-1] + return img_data + + def vector_len(vec): return np.sqrt(vec[0] ** 2 + vec[1] ** 2 + vec[2] ** 2) diff --git a/dioptas/model/util/NewFileWatcher.py b/dioptas/model/util/NewFileWatcher.py index 0711e8f1e..1ccd2291e 100644 --- a/dioptas/model/util/NewFileWatcher.py +++ b/dioptas/model/util/NewFileWatcher.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/model/util/Pattern.py b/dioptas/model/util/Pattern.py index f791ae76b..e58faee99 100644 --- a/dioptas/model/util/Pattern.py +++ b/dioptas/model/util/Pattern.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -164,8 +166,7 @@ def recalculate_pattern(self): self.auto_background_subtraction_parameters[0], self.auto_background_subtraction_parameters[1], self.auto_background_subtraction_parameters[2]) - self._auto_background_pattern = Pattern(x, y_bkg) - + self._auto_background_pattern = Pattern(x, y_bkg, name='auto_bg_' + self.name) y -= y_bkg if self._smoothing > 0: diff --git a/dioptas/model/util/PeakShapes.py b/dioptas/model/util/PeakShapes.py index dd3ce4fbf..fccf51eb0 100644 --- a/dioptas/model/util/PeakShapes.py +++ b/dioptas/model/util/PeakShapes.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/model/util/__init__.py b/dioptas/model/util/__init__.py index 67d101719..2a19e1efc 100644 --- a/dioptas/model/util/__init__.py +++ b/dioptas/model/util/__init__.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/model/util/calc.py b/dioptas/model/util/calc.py index 10bd8b8df..3c9bd560c 100644 --- a/dioptas/model/util/calc.py +++ b/dioptas/model/util/calc.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/model/util/cif.py b/dioptas/model/util/cif.py index 719d4950e..f35e985af 100644 --- a/dioptas/model/util/cif.py +++ b/dioptas/model/util/cif.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/model/util/cosmics.py b/dioptas/model/util/cosmics.py index 0528aaba9..4eb62ed61 100755 --- a/dioptas/model/util/cosmics.py +++ b/dioptas/model/util/cosmics.py @@ -736,7 +736,7 @@ def rebin(a, newshape): factor = np.asarray(shape) / np.asarray(newshape) # print factor evList = ['a.reshape('] + \ - ['newshape[%d],factor[%d],' % (i, i) for i in range(lenShape)] + \ + ['int(newshape[%d]),int(factor[%d]),' % (i, i) for i in range(lenShape)] + \ [')'] + ['.sum(%d)' % (i + 1) for i in range(lenShape)] + \ ['/factor[%d]' % i for i in range(lenShape)] diff --git a/dioptas/model/util/jcpds.py b/dioptas/model/util/jcpds.py index 0076968db..bf6c8b060 100755 --- a/dioptas/model/util/jcpds.py +++ b/dioptas/model/util/jcpds.py @@ -260,7 +260,7 @@ def load_file(self, filename): self.params['comments'].append(line) # Read above line = fp.readline() # Replace any commas with blanks, split at blanks - temp = string.split(line.replace(',', ' ')) + temp = line.replace(',', ' ').split() temp = list(map(float, temp[0:5])) # The symmetry codes are as follows: # 1 -- cubic @@ -435,7 +435,7 @@ def compute_v0(self): self.params['beta0'] = 90. self.params['gamma0'] = 90. - elif self.params['symmetry'] == 'HEXAGONAL': + elif self.params['symmetry'] == 'HEXAGONAL' or self.params['symmetry'] == "TRIGONAL": self.params['b0'] = self.params['a0'] self.params['alpha0'] = 90. self.params['beta0'] = 90. @@ -689,7 +689,7 @@ def compute_d(self, pressure=None, temperature=None): d2inv = (h ** 2 + k ** 2) / a ** 2 + l ** 2 / c ** 2 elif self.params['symmetry'] == 'ORTHORHOMBIC': d2inv = h ** 2 / a ** 2 + k ** 2 / b ** 2 + l ** 2 / c ** 2 - elif self.params['symmetry'] == 'HEXAGONAL': + elif self.params['symmetry'] == 'HEXAGONAL' or self.params['symmetry'] == 'TRIGONAL': d2inv = (h ** 2 + h * k + k ** 2) * 4. / 3. / a ** 2 + l ** 2 / c ** 2 elif self.params['symmetry'] == 'RHOMBOHEDRAL': d2inv = (((1. + np.cos(alpha)) * ((h ** 2 + k ** 2 + l ** 2) - diff --git a/dioptas/model/util/smooth_bruckner_cython.pyx b/dioptas/model/util/smooth_bruckner_cython.pyx new file mode 100644 index 000000000..e8bf5f1f7 --- /dev/null +++ b/dioptas/model/util/smooth_bruckner_cython.pyx @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +import numpy as np +cimport cython + +@cython.boundscheck(False) # Deactivate bounds checking +@cython.wraparound(False) # Deactivate negative indexing. +def smooth_bruckner(y, Py_ssize_t smooth_points, Py_ssize_t iterations): + cdef Py_ssize_t j, i, window_size, n + + n = y.size + window_size = smooth_points * 2 + 1 + + cdef double[:] y_extended = np.empty(n + smooth_points + smooth_points) + + for j in range(0, smooth_points): + y_extended[j] = y[0] + for j in range(n): + y_extended[smooth_points+j] = y[j] + for j in range(smooth_points + n,n + smooth_points + smooth_points): + y_extended[j] = y[n-1] + + for j in range(0, iterations): + window_avg = sum(y_extended[0: 2 * smooth_points + 1]) / (2 * smooth_points + 1) + for i in range(smooth_points, n-smooth_points-2): + if y_extended[i] > window_avg: + y_new = window_avg + # updating central value in average (first bracket) + # and shifting average by one index (second bracket) + window_avg += ((window_avg - y_extended[i]) + ( + y_extended[i + smooth_points + 1] - y_extended[i - smooth_points])) / window_size + y_extended[i] = y_new + else: + # shifting average by one index + window_avg += (y_extended[i + smooth_points + 1] - y_extended[i - smooth_points]) / window_size + return y_extended[smooth_points:smooth_points + n] diff --git a/dioptas/model/util/smooth_bruckner_python.py b/dioptas/model/util/smooth_bruckner_python.py index de4242db0..8d57af5db 100644 --- a/dioptas/model/util/smooth_bruckner_python.py +++ b/dioptas/model/util/smooth_bruckner_python.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/model/util/spe.py b/dioptas/model/util/spe.py index eb8aa8dd5..486a997b0 100644 --- a/dioptas/model/util/spe.py +++ b/dioptas/model/util/spe.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/resources/icons/arrow_down.ico b/dioptas/resources/icons/arrow_down.ico new file mode 100644 index 000000000..a230f036f Binary files /dev/null and b/dioptas/resources/icons/arrow_down.ico differ diff --git a/dioptas/resources/icons/arrow_up.ico b/dioptas/resources/icons/arrow_up.ico new file mode 100644 index 000000000..fc919a088 Binary files /dev/null and b/dioptas/resources/icons/arrow_up.ico differ diff --git a/dioptas/resources/icons/delete.png b/dioptas/resources/icons/delete.png new file mode 100644 index 000000000..1b903f275 Binary files /dev/null and b/dioptas/resources/icons/delete.png differ diff --git a/dioptas/resources/icons/delete.svg b/dioptas/resources/icons/delete.svg new file mode 100644 index 000000000..9659a5371 --- /dev/null +++ b/dioptas/resources/icons/delete.svg @@ -0,0 +1,62 @@ + +image/svg+xml \ No newline at end of file diff --git a/dioptas/resources/icons/edit.png b/dioptas/resources/icons/edit.png new file mode 100644 index 000000000..526e4a8d3 Binary files /dev/null and b/dioptas/resources/icons/edit.png differ diff --git a/dioptas/resources/icons/edit.svg b/dioptas/resources/icons/edit.svg new file mode 100644 index 000000000..592640a0c --- /dev/null +++ b/dioptas/resources/icons/edit.svg @@ -0,0 +1,48 @@ + +image/svg+xml \ No newline at end of file diff --git a/dioptas/resources/icons/reset_dark.ico b/dioptas/resources/icons/reset_dark.ico new file mode 100644 index 000000000..31e08e5ef Binary files /dev/null and b/dioptas/resources/icons/reset_dark.ico differ diff --git a/dioptas/resources/style/stylesheet.qss b/dioptas/resources/style/stylesheet.qss index 5311948d3..8b8609493 100644 --- a/dioptas/resources/style/stylesheet.qss +++ b/dioptas/resources/style/stylesheet.qss @@ -1,7 +1,10 @@ /* -Dioptas - GUI program for fast processing of 2D X-ray data -Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -GSECARS, University of Chicago +Dioptas - GUI program for fast processing of 2D X-ray diffraction data +Principal author: Clemens Prescher (clemens.prescher@gmail.com) +Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +Copyright (C) 2019 DESY, Hamburg, Germany + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -15,49 +18,49 @@ along with this program. If not, see . */ #mainView, #calibration_tab, #mask_tab, #integration_tab { - background: #3C3C3C; - border: 5px solid #3C3C3C; - } + background: #3C3C3C; + border: 5px solid #3C3C3C; +} QTabWidget::tab-bar{ alignment: center; } QTabWidget::pane { - border: 1px solid #2F2F2F; - border-radius: 3px; + border: 1px solid #2F2F2F; + border-radius: 3px; } QWidget{ - color: #F1F1F1; + color: #F1F1F1; } QTabBar::tab:left, QTabBar::tab:right { - background: qlineargradient(spread:pad, x1:1, y1:0, x2:0, y2:0, stop:0 #3C3C3C, stop:1 #505050); - border: 1px solid #5B5B5B; - font: normal 14px; - color: #F1F1F1; - border-radius:2px; + background: qlineargradient(spread:pad, x1:1, y1:0, x2:0, y2:0, stop:0 #3C3C3C, stop:1 #505050); + border: 1px solid #5B5B5B; + font: normal 14px; + color: #F1F1F1; + border-radius:2px; padding: 0px; - width: 20px; + width: 20px; min-height:140px; - } +} QTabBar::tab::top, QTabBar::tab::bottom { - background: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 #3C3C3C, stop:1 #505050); - border: 1px solid #5B5B5B; - border-right: 0px solid white; - color: #F1F1F1; + background: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 #3C3C3C, stop:1 #505050); + border: 1px solid #5B5B5B; + border-right: 0px solid white; + color: #F1F1F1; font: normal 11px; - border-radius:2px; - min-width: 65px; + border-radius:2px; + min-width: 65px; height: 19px; padding: 0px; - margin-top: 1px ; - margin-right: 1px; + margin-top: 1px; + margin-right: 1px; } QTabBar::tab::left:last, QTabBar::tab::right:last{ border-bottom-left-radius: 10px; @@ -69,26 +72,25 @@ QTabBar::tab:left:first, QTabBar::tab:right:first{ } QTabWidget, QTabWidget::tab-bar, QTabWidget::panel, QWidget{ - background: #3C3C3C; - } + background: #3C3C3C; +} QTabWidget::tab-bar { alignment: center; } - QTabBar::tab:hover { - border: 1px solid #ADADAD; - } - - QTabBar:tab:selected{ +QTabBar::tab:hover { + border: 1px solid #ADADAD; +} - background: qlineargradient( - x1: 0, y1: 1, - x2: 0, y2: 0, - stop: 0 #727272, - stop: 1 #444444 - ); - border:1px solid rgb(255, 120,00);/*#ADADAD; */ +QTabBar:tab:selected{ + background: qlineargradient( + x1: 0, y1: 1, + x2: 0, y2: 0, + stop: 0 #727272, + stop: 1 #444444 + ); + border:1px solid rgb(255, 120,00);/*#ADADAD; */ } QTabBar::tab:bottom:last, QTabBar::tab:top:last{ @@ -99,10 +101,10 @@ QTabBar::tab:bottom:first, QTabBar::tab:top:first{ border-top-left-radius: 10px; border-bottom-left-radius: 10px; } - QTabBar::tab:top:!selected { +QTabBar::tab:top:!selected { margin-top: 1px; padding-top:1px; - } +} QTabBar::tab:bottom:!selected{ margin-bottom: 1px; padding-bottom:1px; @@ -112,40 +114,40 @@ QGraphicsView { border-style: none; } - QLabel , QCheckBox, QGroupBox, QRadioButton, QListWidget::item, QPushButton, QToolBox::tab, QSpinBox, QDoubleSpinBox , QComboBox{ - color: #F1F1F1; +QLabel , QCheckBox, QGroupBox, QRadioButton, QListWidget::item, QPushButton, QToolBox::tab, QSpinBox, QDoubleSpinBox , QComboBox{ + color: #F1F1F1; font-size: 12px; - } - QCheckBox{ - border-radius: 5px; - } - QRadioButton, QCheckBox { - font-weight: normal; - height: 15px; - } +} +QCheckBox{ + border-radius: 5px; +} +QRadioButton, QCheckBox { + font-weight: normal; + height: 15px; +} - QLineEdit { - border-radius: 2px; - background: #F1F1F1; - color: black; - height: 18 px; - } +QLineEdit { + border-radius: 2px; + background: #F1F1F1; + color: black; + height: 18 px; +} QLineEdit::focus{ - border-style: none; - border-radius: 2px; - background: #F1F1F1; - color: black; + border-style: none; + border-radius: 2px; + background: #F1F1F1; + color: black; } QLineEdit:disabled, QSpinBox:disabled, QDoubleSpinBox:disabled{ color:rgb(148, 148, 148) } QSpinBox, QDoubleSpinBox { - background-color: #F1F1F1; - color: black; - /*margin-left: -15px; - margin-right: -2px;*/ + background-color: #F1F1F1; + color: black; + /*margin-left: -15px; + margin-right: -2px;*/ } QComboBox QAbstractItemView{ @@ -177,12 +179,12 @@ QComboBox::item::selected { } QToolBox::tab:QToolButton{ background: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 #3C3C3C, stop:1 #505050); - border: 1px solid #5B5B5B; + border: 1px solid #5B5B5B; - border-radius:2px; - padding-right: 10px; + border-radius:2px; + padding-right: 10px; - color: #F1F1F1; + color: #F1F1F1; font-size: 12px; padding: 3px; } @@ -199,27 +201,28 @@ QToolBox::tab:QToolButton{ } QPushButton{ - color: #F1F1F1; - background: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 #323232, stop:1 #505050); - border: 1px solid #5B5B5B; - border-radius: 5px; - padding-left: 8px; -height: 18px; + color: #F1F1F1; + background: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 #323232, stop:1 #505050); + border: 1px solid #5B5B5B; + border-radius: 5px; + padding-left: 8px; + height: 18px; padding-right: 8px; - } +} + QPushButton:pressed{ - margin-top: 2px; - margin-left: 2px; + margin-top: 2px; + margin-left: 2px; } + QPushButton::disabled{ } -QPushButton::hover{ - border:1px solid #ADADAD; - } - +QPushButton::hover { + border:1px solid #ADADAD; +} -QPushButton::checked{ +QPushButton::checked { background: qlineargradient( x1: 0, y1: 1, x2: 0, y2: 0, @@ -232,20 +235,20 @@ QPushButton::checked{ QPushButton::focus { outline: None; } - QGroupBox { - border: 1px solid #ADADAD; - border-radius: 4px; - margin-top: 7px; - padding: 0px - } - QGroupBox::title { - subcontrol-origin: margin; - left: 20px - } +QGroupBox { + border: 1px solid #ADADAD; + border-radius: 4px; + margin-top: 7px; + padding: 0px +} +QGroupBox::title { + subcontrol-origin: margin; + left: 20px +} QSplitter::handle:hover { background: #3C3C3C; - } +} QGraphicsView{ @@ -253,29 +256,32 @@ QGraphicsView{ } QScrollBar:vertical { - border: 2px solid #3C3C3C; - background: qlineargradient(spread:pad, x1:1, y1:0, x2:0, y2:0, stop:0 #323232, stop:1 #505050); - width: 12px; - margin: 0px 0px 0px 0px; - } - QScrollBar::handle:vertical { - background: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0, stop:0 #969696, stop:1 #CACACA); - border-radius: 3px; - min-height: 20px; - padding: 15px; - } - QScrollBar::add-line:vertical { - border: 0px solid grey; - height: 0px; - } - - QScrollBar::sub-line:vertical { - border: 0px solid grey; - height: 0px; - } - QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { - background: none; - } + border: 2px solid #3C3C3C; + background: qlineargradient(spread:pad, x1:1, y1:0, x2:0, y2:0, stop:0 #323232, stop:1 #505050); + width: 12px; + margin: 0px 0px 0px 0px; +} + +QScrollBar::handle:vertical { + background: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0, stop:0 #969696, stop:1 #CACACA); + border-radius: 3px; + min-height: 20px; + padding: 15px; +} + +QScrollBar::add-line:vertical { + border: 0px solid grey; + height: 0px; +} + +QScrollBar::sub-line:vertical { + border: 0px solid grey; + height: 0px; +} + +QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; +} QScrollBar:horizontal { border: 2px solid #3C3C3C; @@ -286,9 +292,9 @@ QScrollBar:horizontal { QScrollBar::handle:horizontal { background: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 #969696, stop:1 #CACACA); - border-radius: 3px; + border-radius: 3px; min-width: 20px; - padding: 15px; + padding: 15px; } QScrollBar::add-line:horizontal { border: 0px solid grey; @@ -303,47 +309,36 @@ QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { background: none; } - - QSplitterHandle:hover {} QSplitter::handle:vertical{ - image: url(Views/UiFiles/images/vertical_splitter.png); - background: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0.3 #3C3C3C, stop:0.5 #505050, -stop: 0.7 #3C3C3C); + background: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0.3 #3C3C3C, stop:0.5 #505050, stop: 0.7 #3C3C3C); height: 15px; } QSplitter::handle:vertical:pressed, QSplitter::handle:vertical:hover{ - image: url(Views/UiFiles/images/vertical_splitter_pressed.png); - background: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0.3 #3C3C3C, stop:0.5 #5C5C5C, -stop: 0.7 #3C3C3C); + background: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0.3 #3C3C3C, stop:0.5 #5C5C5C, stop: 0.7 #3C3C3C); } QSplitter::handle:horizontal{ - image: url(Views/UiFiles/images/horizontal_splitter.png); - background: qlineargradient(spread:pad, x1:1, y1:0, x2:0, y2:0, stop:0.3 #3C3C3C, stop:0.5 #505050, -stop: 0.7 #3C3C3C); + background: qlineargradient(spread:pad, x1:1, y1:0, x2:0, y2:0, stop:0.3 #3C3C3C, stop:0.5 #505050, stop: 0.7 #3C3C3C); width: 15px; } QSplitter::handle:horizontal:pressed, QSplitter::handle:horizontal:hover{ - image: url(Views/UiFiles/images/horizontal_splitter_pressed.png); - background: qlineargradient(spread:pad, x1:1, y1:0, x2:0, y2:0, stop:0.3 #3C3C3C, stop:0.5 #5C5C5C, -stop: 0.7 #3C3C3C); + background: qlineargradient(spread:pad, x1:1, y1:0, x2:0, y2:0, stop:0.3 #3C3C3C, stop:0.5 #5C5C5C, stop: 0.7 #3C3C3C); } QSplitter::handle:hover { background: #3C3C3C; } -QHeaderView::section -{ - spacing: 10px; - color: #F1F1F1; - background: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 #323232, stop:1 #505050); - border: None; - font-size: 12px; +QHeaderView::section { + spacing: 10px; + color: #F1F1F1; + background: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 #323232, stop:1 #505050); + border: None; + font-size: 12px; } QTableWidget { @@ -351,6 +346,68 @@ QTableWidget { text-align: center; } +QTableWidget QPushButton { + margin: 5px; +} + + +QTableWidget QPushButton::pressed{ + margin-top: 7px; + margin-left: 7px; +} + +QTableWidget { + selection-background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 rgba(177,80,0,255), stop:1 rgba(255,120,0,255)); + selection-color: #F1F1F1; +} + +#phase_table_widget QCheckBox, #overlay_table_widget QCheckBox { + margin-left: 5px; +} + +#overlay_table_widget QDoubleSpinBox, #phase_table_widget QDoubleSpinBox { + min-width: 50; + max-width: 70; + background: transparent; + background-color: transparent; + color:#D1D1D1; + border: 1px solid transparent; +} + +#overlay_table_widget QDoubleSpinBox:disabled, #phase_table_widget QDoubleSpinBox:disabled { + color:#888; +} + +#overlay_table_widget QDoubleSpinBox::up-button, #overlay_table_widget QDoubleSpinBox::down-button, +#phase_table_widget QDoubleSpinBox::up-button, #phase_table_widget QDoubleSpinBox::down-button { + width: 11px; + height: 9px; + background: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 #323232, stop:1 #505050); + border: 0.5px solid #5B5B5B; + border-radius: 2px; +} +#overlay_table_widget QDoubleSpinBox::up-button:hover, #overlay_table_widget QDoubleSpinBox::down-button:hover, +#phase_table_widget QDoubleSpinBox::up-button:hover, #phase_table_widget QDoubleSpinBox::down-button:hover +{ + border:0.5px solid #ADADAD; +} + +#overlay_table_widget QDoubleSpinBox::up-button:pressed, #phase_table_widget QDoubleSpinBox::up-button:pressed{ + width: 10px; + height: 8px; +} +#overlay_table_widget QDoubleSpinBox::down-button:pressed, #phase_table_widget QDoubleSpinBox::down-button:pressed { + width: 10px; + height: 8px; +} + +#overlay_table_widget QDoubleSpinBox::up-button, #phase_table_widget QDoubleSpinBox::up-button { + image: url(dioptas/resources/icons/arrow_up.ico) 1; +} + +#overlay_table_widget QDoubleSpinBox::down-button, #phase_table_widget QDoubleSpinBox::down-button { + image: url(dioptas/resources/icons/arrow_down.ico) 1; +} QFrame#main_frame { color: #F1F1F1; @@ -367,6 +424,7 @@ QFrame#main_frame { border-top-right-radius:8px; border-bottom-right-radius: 8px; } + #integration_mode_btn { border-top-left-radius:8px; border-bottom-left-radius: 8px; diff --git a/dioptas/tests/Profiling/__init__.py b/dioptas/tests/Profiling/__init__.py index 3978ecf85..ea6703229 100644 --- a/dioptas/tests/Profiling/__init__.py +++ b/dioptas/tests/Profiling/__init__.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/tests/Profiling/profiling_cbn_correction.py b/dioptas/tests/Profiling/profiling_cbn_correction.py index fed907831..ae3e91812 100644 --- a/dioptas/tests/Profiling/profiling_cbn_correction.py +++ b/dioptas/tests/Profiling/profiling_cbn_correction.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/tests/__init__.py b/dioptas/tests/__init__.py index 22767dfec..5afc72098 100644 --- a/dioptas/tests/__init__.py +++ b/dioptas/tests/__init__.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/tests/controller_tests/__init__.py b/dioptas/tests/controller_tests/__init__.py index 76b3deb76..6257ce947 100644 --- a/dioptas/tests/controller_tests/__init__.py +++ b/dioptas/tests/controller_tests/__init__.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/tests/controller_tests/test_BackgroundController.py b/dioptas/tests/controller_tests/test_BackgroundController.py index 6d3ad901d..04e443ddb 100644 --- a/dioptas/tests/controller_tests/test_BackgroundController.py +++ b/dioptas/tests/controller_tests/test_BackgroundController.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -18,13 +20,18 @@ import os import numpy as np +from mock import MagicMock -from ..utility import QtTest, unittest_data_path, click_button +from qtpy import QtWidgets + +from ..utility import QtTest, unittest_data_path, click_button, delete_if_exists from ...widgets.integration import IntegrationWidget from ...controller.integration.BackgroundController import BackgroundController from ...controller.integration.PatternController import PatternController from ...model.DioptasModel import DioptasModel +data_path = os.path.join(os.path.dirname(__file__), '../data') + class BackgroundControllerTest(QtTest): def setUp(self): @@ -40,6 +47,9 @@ def setUp(self): dioptas_model=self.model ) + def tearDown(self): + delete_if_exists(os.path.join(data_path, "test.xy")) + def test_configuration_selected_changes_background_image_widgets(self): self.model.img_model.load(os.path.join(unittest_data_path, 'image_001.tif')) self.model.img_model.load_background(os.path.join(unittest_data_path, 'image_001.tif')) @@ -75,7 +85,6 @@ def test_configuration_selected_changes_auto_background_widgets(self): self.model.select_configuration(1) self.assertFalse(self.widget.qa_bkg_pattern_inspect_btn.isChecked()) - def test_changing_unit(self): # load calibration and image self.model.calibration_model.load(os.path.join(unittest_data_path, 'LaB6_40keV_MarCCD.poni')) @@ -95,3 +104,10 @@ def test_changing_unit(self): self.assertEqual(x_q[0], x_q_bkg[0]) self.assertEqual(x_q[-1], x_q_bkg[-1]) + def test_save_fit_background_pattern(self): + QtWidgets.QFileDialog.getSaveFileName = MagicMock(return_value=os.path.join(data_path, "test_bg.xy")) + self.model.calibration_model.create_file_header = MagicMock(return_value="None") + click_button(self.widget.qa_bkg_pattern_btn) + click_button(self.widget.bkg_pattern_save_btn) + + self.assertTrue(os.path.exists(os.path.join(data_path, "test_bg.xy"))) diff --git a/dioptas/tests/controller_tests/test_CalibrationController.py b/dioptas/tests/controller_tests/test_CalibrationController.py index e1970a854..8d3d1a89d 100644 --- a/dioptas/tests/controller_tests/test_CalibrationController.py +++ b/dioptas/tests/controller_tests/test_CalibrationController.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -25,11 +27,10 @@ from qtpy import QtWidgets, QtCore from qtpy.QtTest import QTest -from ..utility import QtTest, unittest_data_path +from ..utility import QtTest, unittest_data_path, click_button from ...model.DioptasModel import DioptasModel from ...controller.CalibrationController import CalibrationController from ...widgets.CalibrationWidget import CalibrationWidget -from ... import calibrants_path # mocking the functions which will block the unittest for some reason... QtWidgets.QApplication.processEvents = MagicMock() @@ -39,9 +40,6 @@ class TestCalibrationController(QtTest): def setUp(self): self.model = DioptasModel() - self.model.calibration_model._calibrants_working_dir = os.path.join(unittest_data_path, 'calibrants') - self.model.calibration_model.integrate_1d = MagicMock(return_value=([np.linspace(0, 100), np.linspace(0, 100)])) - self.model.calibration_model.integrate_2d = MagicMock() self.calibration_widget = CalibrationWidget() self.calibration_controller = CalibrationController(widget=self.calibration_widget, @@ -51,7 +49,12 @@ def tearDown(self): del self.model gc.collect() + def mock_integrate_functions(self): + self.model.calibration_model.integrate_1d = MagicMock(return_value=([np.linspace(0, 100), np.linspace(0, 100)])) + self.model.calibration_model.integrate_2d = MagicMock() + def test_automatic_calibration(self): + self.mock_integrate_functions() QtWidgets.QFileDialog.getOpenFileName = MagicMock( return_value=os.path.join(unittest_data_path, 'LaB6_40keV_MarCCD.tif')) QTest.mouseClick(self.calibration_widget.load_img_btn, QtCore.Qt.LeftButton) @@ -73,7 +76,21 @@ def test_automatic_calibration(self): calibration_parameter = self.model.calibration_model.get_calibration_parameter()[0] self.assertAlmostEqual(calibration_parameter['dist'], .1967, places=4) + def test_splines(self): + self.mock_integrate_functions() + QtWidgets.QFileDialog.getOpenFileName = MagicMock( + return_value=os.path.join(unittest_data_path, 'distortion', 'f4mnew.spline')) + click_button(self.calibration_widget.load_spline_btn) + + self.assertIsNotNone(self.model.calibration_model.distortion_spline_filename) + self.assertEqual(self.calibration_widget.spline_filename_txt.text(), 'f4mnew.spline') + # + click_button(self.calibration_widget.spline_reset_btn) + self.assertIsNone(self.model.calibration_model.distortion_spline_filename) + self.assertEqual(self.calibration_widget.spline_filename_txt.text(), 'None') + def test_loading_and_saving_of_calibration_files(self): + self.mock_integrate_functions() QtWidgets.QFileDialog.getOpenFileName = MagicMock( return_value=os.path.join(unittest_data_path, 'LaB6_40keV_MarCCD.poni')) QTest.mouseClick(self.calibration_widget.load_calibration_btn, QtCore.Qt.LeftButton) @@ -84,6 +101,7 @@ def test_loading_and_saving_of_calibration_files(self): os.remove(os.path.join(unittest_data_path, 'calibration.poni')) def test_selecting_configuration_updates_parameter_display(self): + self.mock_integrate_functions() calibration1 = { 'dist': 0.2, 'poni1': 0.08, @@ -119,6 +137,8 @@ def test_selecting_configuration_updates_parameter_display(self): del model_calibration['detector'] if 'splineFile' in model_calibration.keys(): del model_calibration['splineFile'] + if 'max_shape' in model_calibration.keys(): + del model_calibration['max_shape'] current_displayed_calibration = self.calibration_widget.get_pyFAI_parameter() del current_displayed_calibration['polarization_factor'] self.assertEqual(model_calibration, current_displayed_calibration) @@ -128,8 +148,28 @@ def test_selecting_configuration_updates_parameter_display(self): del model_calibration['detector'] if 'splineFile' in model_calibration.keys(): del model_calibration['splineFile'] + if 'max_shape' in model_calibration.keys(): + del model_calibration['max_shape'] current_displayed_calibration = self.calibration_widget.get_pyFAI_parameter() del current_displayed_calibration['polarization_factor'] self.assertEqual(model_calibration, current_displayed_calibration) - current_displayed_calibration = self.calibration_widget.get_pyFAI_parameter() + self.calibration_widget.get_pyFAI_parameter() + + def test_calibrant_with_small_set_of_d_spacings(self): + self.mock_integrate_functions() + QtWidgets.QFileDialog.getOpenFileName = MagicMock( + return_value=os.path.join(unittest_data_path, 'LaB6_40keV_MarCCD.tif')) + QTest.mouseClick(self.calibration_widget.load_img_btn, QtCore.Qt.LeftButton) + self.calibration_controller.search_peaks(1179.6, 1129.4) + self.calibration_controller.search_peaks(1268.5, 1119.8) + calibrant_index = self.calibration_widget.calibrant_cb.findText('CuO') + self.calibration_controller.widget.calibrant_cb.setCurrentIndex(calibrant_index) + QtWidgets.QMessageBox.critical = MagicMock() + QTest.mouseClick(self.calibration_widget.calibrate_btn, QtCore.Qt.LeftButton) + QtWidgets.QMessageBox.critical.assert_called_once() + + def test_loading_calibration_without_an_image_before(self): + QtWidgets.QFileDialog.getOpenFileName = MagicMock( + return_value=os.path.join(unittest_data_path, 'LaB6_40keV_MarCCD.poni')) + QTest.mouseClick(self.calibration_widget.load_calibration_btn, QtCore.Qt.LeftButton) diff --git a/dioptas/tests/controller_tests/test_ConfigurationController.py b/dioptas/tests/controller_tests/test_ConfigurationController.py index efb2b0692..b8619d1d0 100644 --- a/dioptas/tests/controller_tests/test_ConfigurationController.py +++ b/dioptas/tests/controller_tests/test_ConfigurationController.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/tests/controller_tests/test_CorrectionController.py b/dioptas/tests/controller_tests/test_CorrectionController.py new file mode 100644 index 000000000..2eeba73af --- /dev/null +++ b/dioptas/tests/controller_tests/test_CorrectionController.py @@ -0,0 +1,206 @@ +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from ..utility import QtTest +import os +import gc +import numpy as np + +from qtpy import QtWidgets +from mock import MagicMock +import mock + +from ..utility import click_button, unittest_data_path + +from ...controller.integration.CorrectionController import CorrectionController +from ...model.DioptasModel import DioptasModel +from ...widgets.integration import IntegrationWidget + + +class CorrectionControllerTest(QtTest): + def setUp(self): + self.widget = IntegrationWidget() + self.correction_widget = self.widget.integration_control_widget.corrections_control_widget + self.model = DioptasModel() + + self.correction_controller = CorrectionController(self.widget, self.model) + + self.original_filename = os.path.join(unittest_data_path, 'TransferCorrection', 'original.tif') + self.response_filename = os.path.join(unittest_data_path, 'TransferCorrection', 'response.tif') + + def tearDown(self): + del self.correction_controller + del self.widget + del self.model + gc.collect() + + def load_original_img(self): + QtWidgets.QFileDialog.getOpenFileName = MagicMock(return_value=self.original_filename) + click_button(self.widget.transfer_load_original_btn) + + def load_response_img(self): + QtWidgets.QFileDialog.getOpenFileName = MagicMock(return_value=self.response_filename) + click_button(self.widget.transfer_load_response_btn) + + def test_filenames_are_displayed_in_widget(self): + self.correction_widget.transfer_gb.setChecked(True) + + self.model.img_model.load(self.response_filename) + + self.load_original_img() + self.assertEqual(self.correction_widget.transfer_original_filename_lbl.text(), + os.path.basename(self.original_filename)) + self.load_response_img() + self.assertEqual(self.correction_widget.transfer_response_filename_lbl.text(), + os.path.basename(self.response_filename)) + + def test_correction_loaded(self): + self.correction_widget.transfer_gb.setChecked(True) + + self.model.img_model.load(self.response_filename) + + self.load_original_img() + self.assertFalse(self.model.img_model.has_corrections()) + self.load_response_img() + self.assertTrue(self.model.img_model.has_corrections()) + + def test_disable_transfer_correction(self): + self.correction_widget.transfer_gb.setChecked(True) + self.model.img_model.load(self.response_filename) + self.load_original_img() + self.load_response_img() + self.assertTrue(self.model.img_model.has_corrections()) + self.correction_widget.transfer_gb.setChecked(False) + self.assertFalse(self.model.img_model.has_corrections()) + + def test_load_img_with_different_shape(self): + QtWidgets.QMessageBox.critical = MagicMock() + self.correction_widget.transfer_gb.setChecked(True) + self.model.img_model.load(self.response_filename) + self.load_original_img() + self.load_response_img() + self.assertTrue(self.model.img_model.has_corrections()) + + self.model.img_model.load(os.path.join(unittest_data_path, 'image_001.tif')) + self.assertFalse(self.model.img_model.has_corrections()) + + self.assertIsNone(self.model.img_model.transfer_correction.response_filename) + self.assertFalse(self.widget.transfer_load_original_btn.isVisible()) + + self.assertEqual(self.widget.transfer_original_filename_lbl.text(), 'None') + self.assertEqual(self.widget.transfer_response_filename_lbl.text(), 'None') + + def test_load_img_with_different_shape_and_calibration(self): + QtWidgets.QMessageBox.critical = MagicMock() + self.correction_widget.transfer_gb.setChecked(True) + self.model.img_model.load(self.response_filename) + self.model.calibration_model.load(os.path.join(unittest_data_path, 'CeO2_Pilatus1M_2.poni')) + + self.load_original_img() + self.load_response_img() + self.assertTrue(self.model.img_model.has_corrections()) + + self.model.img_model.load(os.path.join(unittest_data_path, 'image_001.tif')) + self.assertFalse(self.model.img_model.has_corrections()) + + self.assertIsNone(self.model.img_model.transfer_correction.response_filename) + self.assertFalse(self.widget.transfer_load_original_btn.isVisible()) + + self.assertEqual(self.widget.transfer_original_filename_lbl.text(), 'None') + self.assertEqual(self.widget.transfer_response_filename_lbl.text(), 'None') + + def test_image_enable_and_disable(self): + self.correction_widget.transfer_gb.setChecked(True) + self.model.img_model.load(self.response_filename) + before_data = self.model.img_data.copy() + self.load_original_img() + self.load_response_img() + + self.assertNotAlmostEqual(np.sum(before_data - self.model.img_data), 0) + + self.correction_widget.transfer_gb.setChecked(False) + self.assertAlmostEqual(np.sum(before_data - self.model.img_data), 0) + + def test_image_enable_and_disable_with_calibration(self): + self.correction_widget.transfer_gb.setChecked(True) + self.model.img_model.load(self.response_filename) + self.model.calibration_model.load(os.path.join(unittest_data_path, 'CeO2_Pilatus1M_2.poni')) + before_data = self.model.img_data.copy() + self.load_original_img() + self.load_response_img() + + self.assertNotAlmostEqual(np.sum(before_data - self.model.img_data), 0) + + self.correction_widget.transfer_gb.setChecked(False) + self.assertAlmostEqual(np.sum(before_data - self.model.img_data), 0) + + self.correction_widget.transfer_gb.setChecked(True) + self.assertNotAlmostEqual(np.sum(before_data - self.model.img_data), 0) + + def test_show_correction_in_img_widget_and_back(self): + self.correction_widget.transfer_gb.setChecked(True) + self.model.img_model.load(self.response_filename) + self.load_original_img() + self.load_response_img() + + click_button(self.correction_widget.transfer_plot_btn) + transfer_data = self.model.img_model.transfer_correction.transfer_data + img_data = self.widget.img_widget.img_data + self.assertAlmostEqual(np.sum(transfer_data - img_data), 0) + self.assertTrue(self.correction_widget.transfer_plot_btn.isChecked()) + self.assertEqual(self.correction_widget.transfer_plot_btn.text(), 'Back') + + click_button(self.correction_widget.transfer_plot_btn) + + self.assertFalse(self.correction_widget.transfer_plot_btn.isChecked()) + self.assertEqual(self.correction_widget.transfer_plot_btn.text(), 'Plot') + + def test_transfer_correction_is_applied_correctly(self): + self.model.calibration_model.load(os.path.join(unittest_data_path, 'TransferCorrection', 'transfer.poni')) + self.model.img_model.load(self.original_filename) + y_original = self.model.pattern.y + self.model.img_model.load(self.response_filename) + + self.correction_widget.transfer_gb.setChecked(True) + self.load_original_img() + self.load_response_img() + y_response_with_transfer = self.model.pattern.y + + self.assertAlmostEqual(np.sum(y_original-y_response_with_transfer), 0) + + def test_changing_transfer_function_several_times(self): + self.model.img_model.load(self.original_filename) + self.correction_widget.transfer_gb.setChecked(True) + self.load_original_img() + self.load_response_img() + + img_1 = self.model.img_data.copy() + + QtWidgets.QFileDialog.getOpenFileName = MagicMock(return_value=self.original_filename) + click_button(self.widget.transfer_load_response_btn) + + img_2 = self.model.img_data.copy() + + self.assertFalse(np.array_equal(img_1, img_2)) + + + + + diff --git a/dioptas/tests/controller_tests/test_EpicsController.py b/dioptas/tests/controller_tests/test_EpicsController.py index b75f55c7e..590ac6f36 100644 --- a/dioptas/tests/controller_tests/test_EpicsController.py +++ b/dioptas/tests/controller_tests/test_EpicsController.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/tests/controller_tests/test_ImageController.py b/dioptas/tests/controller_tests/test_ImageController.py index e995a29cf..65b8ed843 100644 --- a/dioptas/tests/controller_tests/test_ImageController.py +++ b/dioptas/tests/controller_tests/test_ImageController.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/tests/controller_tests/test_IntegrationBackgroundController.py b/dioptas/tests/controller_tests/test_IntegrationBackgroundController.py index e9c103676..7b2223b61 100644 --- a/dioptas/tests/controller_tests/test_IntegrationBackgroundController.py +++ b/dioptas/tests/controller_tests/test_IntegrationBackgroundController.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/tests/controller_tests/test_IntegrationController.py b/dioptas/tests/controller_tests/test_IntegrationController.py index 45f0c4501..1c8eb948d 100644 --- a/dioptas/tests/controller_tests/test_IntegrationController.py +++ b/dioptas/tests/controller_tests/test_IntegrationController.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -19,7 +21,7 @@ import os import gc import unittest -from ..utility import QtTest, click_button, click_checkbox +from ..utility import QtTest, click_button, click_checkbox, delete_if_exists import mock from mock import MagicMock @@ -116,7 +118,7 @@ def test_switching_to_cake_mode_without_having_clicked_the_image_before(self): QTest.mouseClick(self.widget.img_mode_btn, QtCore.Qt.LeftButton) def test_shift_cake_azimuth(self): - shift = 300 + shift = 30 QTest.mouseClick(self.widget.img_mode_btn, QtCore.Qt.LeftButton) self.assertEqual(self.widget.cake_shift_azimuth_sl.minimum(), -len(self.model.cake_azi) / 2) self.assertEqual(self.widget.cake_shift_azimuth_sl.maximum(), len(self.model.cake_azi) / 2) @@ -158,3 +160,51 @@ def test_cake_zoom_changes_axes_scale(self): # print(self.widget.integration_image_widget.img_view.img_view_box.viewRange()) # print(self.widget.integration_image_widget.img_view.img_view_box.viewRect()) self.assertEqual(self.widget.integration_image_widget.img_view.img_view_box.viewRect(), rect) + + def test_save_cake_as_text_data(self): + output_file_name = "test.txt" + self.widget.integration_image_widget.mode_btn.click() # change to cake mode + QtWidgets.QFileDialog.getSaveFileName = MagicMock(return_value=os.path.join(data_path, output_file_name)) + + cake_tth = np.copy(self.model.cake_tth) # make sure nothing is changed + + click_button(self.widget.qa_save_img_btn) + self.assertTrue(os.path.exists(os.path.join(data_path, output_file_name))) + delete_if_exists(os.path.join(data_path, "test.txt")) + + self.assertEqual(len(cake_tth), len(self.model.cake_tth)) + + def test_switch_to_alternate_view_mode_and_back(self): + self.assertTrue(self.helper_is_item_in_splitter(self.widget.integration_pattern_widget, + self.widget.vertical_splitter)) + + self.widget.change_view_btn.click() # switch to alternative view + self.assertFalse(self.helper_is_item_in_splitter(self.widget.integration_pattern_widget, + self.widget.vertical_splitter)) + self.assertTrue(self.helper_is_item_in_splitter(self.widget.integration_pattern_widget, + self.widget.vertical_splitter_left)) + + self.widget.change_view_btn.click() # switch back + self.assertTrue(self.helper_is_item_in_splitter(self.widget.integration_pattern_widget, + self.widget.vertical_splitter)) + + self.assertFalse(self.helper_is_item_in_splitter(self.widget.integration_pattern_widget, + self.widget.vertical_splitter_left)) + + def test_undock_in_alternate_view(self): + self.widget.change_view_btn.click() # switch to alternative view + self.assertTrue(self.helper_is_item_in_splitter(self.widget.img_frame, + self.widget.vertical_splitter_left)) + self.widget.img_dock_btn.click() + self.assertFalse(self.helper_is_item_in_splitter(self.widget.img_frame, + self.widget.vertical_splitter_left)) + self.widget.img_dock_btn.click() + self.assertTrue(self.helper_is_item_in_splitter(self.widget.img_frame, + self.widget.vertical_splitter_left)) + + def helper_is_item_in_splitter(self, item, splitter): + for ind in range(0, splitter.count()): + if splitter.widget(ind) == item: + return True + return False + diff --git a/dioptas/tests/controller_tests/test_MaskController.py b/dioptas/tests/controller_tests/test_MaskController.py index fcb74d562..42b16a532 100644 --- a/dioptas/tests/controller_tests/test_MaskController.py +++ b/dioptas/tests/controller_tests/test_MaskController.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -140,3 +142,6 @@ def test_select_configuration_updating_mask_transparency(self): self.model.select_configuration(1) self.assertTrue(self.mask_widget.transparent_rb.isChecked()) self.assertTrue(np.array_equal(self.mask_widget.img_widget.mask_img_item.lut, transparent_color)) + + def test_apply_cosmic_removal(self): + click_button(self.mask_widget.cosmic_btn) \ No newline at end of file diff --git a/dioptas/tests/controller_tests/test_OptionsController.py b/dioptas/tests/controller_tests/test_OptionsController.py new file mode 100644 index 000000000..8713c3863 --- /dev/null +++ b/dioptas/tests/controller_tests/test_OptionsController.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from ..utility import QtTest +import os +import gc + +from qtpy import QtWidgets +from mock import MagicMock + +from ..utility import enter_value_into_text_field, click_button + +from ...controller.integration import OptionsController +from ...model.DioptasModel import DioptasModel +from ...widgets.integration import IntegrationWidget + +unittest_path = os.path.dirname(__file__) +data_path = os.path.join(unittest_path, '../data') + +QtWidgets.QApplication.processEvents = MagicMock() + + +class OptionsControllerTest(QtTest): + def setUp(self): + self.widget = IntegrationWidget() + self.options_widget = self.widget.integration_control_widget.integration_options_widget + self.model = DioptasModel() + + self.options_controller = OptionsController(self.widget, self.model) + + def tearDown(self): + del self.options_controller + del self.widget + del self.model + gc.collect() + + def test_change_azimuth_bins(self): + enter_value_into_text_field(self.options_widget.cake_azimuth_points_sb.lineEdit(), 100) + self.assertEqual(self.model.current_configuration.cake_azimuth_points, 100) + + def test_change_azimuth_range(self): + click_button(self.options_widget.cake_full_toggle_btn) + enter_value_into_text_field(self.options_widget.cake_azimuth_min_txt, -100) + self.assertEqual(self.model.current_configuration.cake_azimuth_range[0], -100) + + enter_value_into_text_field(self.options_widget.cake_azimuth_max_txt, 200) + self.assertEqual(self.model.current_configuration.cake_azimuth_range[1], 200) + + + diff --git a/dioptas/tests/controller_tests/test_OverlayController.py b/dioptas/tests/controller_tests/test_OverlayController.py index ef0b296fd..fcaf3a09f 100644 --- a/dioptas/tests/controller_tests/test_OverlayController.py +++ b/dioptas/tests/controller_tests/test_OverlayController.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -16,7 +18,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from ..utility import QtTest, click_button +from ..utility import QtTest, click_button, click_checkbox import os import gc from mock import MagicMock @@ -118,29 +120,27 @@ def test_automatic_deleting_overlays(self): def test_change_scaling_in_view(self): self.load_overlays() - self.overlay_widget.select_overlay(2) - - self.integration_widget.overlay_scale_sb.setValue(2.0) - self.app.processEvents() - self.assertEqual(self.model.overlay_model.get_overlay_scaling(2), 2) + for ind in [0, 3, 4]: + self.overlay_widget.scale_sbs[ind].setValue(2.0) + self.assertEqual(self.model.overlay_model.get_overlay_scaling(ind), 2) - # tests if overlay is updated in pattern - x, y = self.model.overlay_model.overlays[2].data - x_spec, y_spec = self.integration_widget.pattern_widget.overlays[2].getData() + # tests if overlay is updated in pattern + x, y = self.model.overlay_model.overlays[ind].data + x_spec, y_spec = self.integration_widget.pattern_widget.overlays[ind].getData() - self.assertAlmostEqual(np.sum(y - y_spec), 0) + self.assertAlmostEqual(np.sum(y - y_spec), 0) def test_change_offset_in_view(self): self.load_overlays() - self.overlay_widget.select_overlay(3) - self.integration_widget.overlay_offset_sb.setValue(100) - self.assertEqual(self.model.overlay_model.get_overlay_offset(3), 100) + for ind in [0, 3, 4]: + self.overlay_widget.offset_sbs[ind].setValue(100) + self.assertEqual(self.model.overlay_model.get_overlay_offset(ind), 100) - x, y = self.model.overlay_model.overlays[3].data - x_spec, y_spec = self.integration_widget.pattern_widget.overlays[3].getData() + x, y = self.model.overlay_model.overlays[ind].data + x_spec, y_spec = self.integration_widget.pattern_widget.overlays[ind].getData() - self.assertAlmostEqual(np.sum(y - y_spec), 0) + self.assertAlmostEqual(np.sum(y - y_spec), 0) def test_scaling_auto_step_change(self): self.load_overlays() @@ -155,6 +155,26 @@ def test_scaling_auto_step_change(self): new_scale_step = self.overlay_widget.scale_step_msb.value() self.assertAlmostEqual(new_scale_step, 0.2, places=5) + def test_scalestep_spinbox_changes_scale_spinboxes(self): + self.load_overlays() + for ind in range(6): + self.assertEqual(self.overlay_widget.scale_sbs[ind].singleStep(), 0.01) + + new_step = 5 + self.overlay_widget.scale_step_msb.setValue(new_step) + for ind in range(6): + self.assertEqual(self.overlay_widget.scale_sbs[ind].singleStep(), new_step) + + def test_offsetstep_spinbox_changes_offset_spinboxes(self): + self.load_overlays() + for ind in range(6): + self.assertEqual(self.overlay_widget.offset_sbs[ind].singleStep(), 100) + + new_step = 5 + self.overlay_widget.offset_step_msb.setValue(new_step) + for ind in range(6): + self.assertEqual(self.overlay_widget.offset_sbs[ind].singleStep(), new_step) + def test_offset_auto_step_change(self): self.load_overlays() self.overlay_widget.offset_step_msb.setValue(10.0) @@ -183,10 +203,14 @@ def test_setting_overlay_as_bkg(self): def test_setting_overlay_as_bkg_and_changing_scale(self): self.load_overlays() self.model.pattern_model.load_pattern(os.path.join(data_path, 'pattern_001.xy')) - self.overlay_widget.select_overlay(0) + + ind = 2 + + self.overlay_widget.select_overlay(ind) QTest.mouseClick(self.integration_widget.overlay_set_as_bkg_btn, QtCore.Qt.LeftButton) - self.integration_widget.overlay_scale_sb.setValue(2) + self.overlay_widget.scale_sbs[ind].setValue(2) + _, y = self.model.pattern.data _, y_original = self.model.pattern.data self.assertEqual(np.sum(y - y_original), 0) @@ -194,24 +218,29 @@ def test_setting_overlay_as_bkg_and_changing_scale(self): def test_setting_overlay_as_bkg_and_changing_offset(self): self.load_overlays() self.model.pattern_model.load_pattern(os.path.join(data_path, 'pattern_001.xy')) - self.overlay_widget.select_overlay(0) + + ind = 2 + self.overlay_widget.select_overlay(2) QTest.mouseClick(self.integration_widget.overlay_set_as_bkg_btn, QtCore.Qt.LeftButton) - self.integration_widget.overlay_offset_sb.setValue(100) + self.overlay_widget.offset_sbs[ind].setValue(100) _, y = self.model.pattern.data self.assertEqual(np.sum(y), -100 * y.size) def test_setting_overlay_as_bkg_and_then_change_to_new_overlay_as_bkg(self): self.load_overlays() self.model.pattern_model.load_pattern(os.path.join(data_path, 'pattern_001.xy')) - self.overlay_widget.select_overlay(0) + + ind = 2 + self.overlay_widget.select_overlay(ind) QTest.mouseClick(self.integration_widget.overlay_set_as_bkg_btn, QtCore.Qt.LeftButton) _, y = self.model.pattern.data self.assertEqual(np.sum(y), 0) - self.overlay_widget.select_overlay(1) - self.integration_widget.overlay_scale_sb.setValue(2) + new_ind = 1 + self.overlay_widget.select_overlay(new_ind) + self.overlay_widget.scale_sbs[new_ind].setValue(2) QTest.mouseClick(self.integration_widget.overlay_set_as_bkg_btn, QtCore.Qt.LeftButton) _, y = self.model.pattern.data @@ -244,16 +273,24 @@ def test_having_overlay_as_bkg_and_deleting_it(self): def test_overlay_waterfall(self): self.load_overlays() self.overlay_widget.waterfall_separation_msb.setValue(10) - QTest.mouseClick(self.overlay_widget.waterfall_btn, QtCore.Qt.LeftButton) + click_button(self.overlay_widget.waterfall_btn) self.assertEqual(self.model.overlay_model.overlays[5].offset, -10) self.assertEqual(self.model.overlay_model.overlays[4].offset, -20) - QTest.mouseClick(self.integration_widget.reset_waterfall_btn, QtCore.Qt.LeftButton) + click_button(self.integration_widget.reset_waterfall_btn) self.assertEqual(self.model.overlay_model.overlays[5].offset, 0) self.assertEqual(self.model.overlay_model.overlays[5].offset, 0) + def test_overlay_waterfall_after_deleting_one_overlay(self): + self.load_overlays() + self.overlay_widget.select_overlay(4) + click_button(self.overlay_widget.delete_btn) + click_button(self.overlay_widget.delete_btn) + + click_button(self.overlay_widget.waterfall_btn) + def load_overlays(self): self.load_overlay('pattern_001.xy') self.load_overlay('pattern_001.xy') @@ -308,5 +345,20 @@ def test_move_single_overlay_one_step_down(self): self.assertEqual(self.model.overlay_model.overlays[3].name, 'pattern_001') self.assertEqual(self.integration_widget.overlay_tw.item(3, 2).text(), 'pattern_001') -if __name__ == '__main__': - unittest.main() + def test_bulk_change_visibility_of_overlays(self): + self.load_overlays() + for cb in self.overlay_widget.show_cbs: + self.assertTrue(cb.isChecked()) + + self.overlay_controller.overlay_tw_header_section_clicked(0) + for cb in self.overlay_widget.show_cbs: + self.assertFalse(cb.isChecked()) + + click_checkbox(self.overlay_widget.show_cbs[1]) + self.overlay_controller.overlay_tw_header_section_clicked(0) + for ind, cb in enumerate(self.overlay_widget.show_cbs): + self.assertFalse(cb.isChecked()) + + self.overlay_controller.overlay_tw_header_section_clicked(0) + for ind, cb in enumerate(self.overlay_widget.show_cbs): + self.assertTrue(cb.isChecked()) \ No newline at end of file diff --git a/dioptas/tests/controller_tests/test_PatternController.py b/dioptas/tests/controller_tests/test_PatternController.py index 242777336..743d655f6 100644 --- a/dioptas/tests/controller_tests/test_PatternController.py +++ b/dioptas/tests/controller_tests/test_PatternController.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/tests/controller_tests/test_PhaseController.py b/dioptas/tests/controller_tests/test_PhaseController.py index a98495974..8c1b1344e 100644 --- a/dioptas/tests/controller_tests/test_PhaseController.py +++ b/dioptas/tests/controller_tests/test_PhaseController.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -16,7 +18,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from ..utility import QtTest, click_button +from ..utility import QtTest, click_button, click_checkbox import os import gc @@ -30,6 +32,8 @@ from ...model.DioptasModel import DioptasModel from ...widgets.integration import IntegrationWidget +from ..utility import click_button + unittest_path = os.path.dirname(__file__) data_path = os.path.join(unittest_path, '../data') jcpds_path = os.path.join(data_path, 'jcpds') @@ -61,42 +65,41 @@ def tearDown(self): def test_manual_deleting_phases(self): self.load_phases() - QtWidgets.QApplication.processEvents() self.assertEqual(self.phase_tw.rowCount(), 6) self.assertEqual(len(self.model.phase_model.phases), 6) self.assertEqual(len(self.widget.pattern_widget.phases), 6) self.assertEqual(self.phase_tw.currentRow(), 5) - self.controller.remove_btn_click_callback() + click_button(self.phase_widget.delete_btn) self.assertEqual(self.phase_tw.rowCount(), 5) self.assertEqual(len(self.model.phase_model.phases), 5) self.assertEqual(len(self.widget.pattern_widget.phases), 5) self.assertEqual(self.phase_tw.currentRow(), 4) self.phase_widget.select_phase(1) - self.controller.remove_btn_click_callback() + click_button(self.phase_widget.delete_btn) self.assertEqual(self.phase_tw.rowCount(), 4) self.assertEqual(len(self.model.phase_model.phases), 4) self.assertEqual(len(self.widget.pattern_widget.phases), 4) self.assertEqual(self.phase_tw.currentRow(), 1) self.phase_widget.select_phase(0) - self.controller.remove_btn_click_callback() + click_button(self.phase_widget.delete_btn) self.assertEqual(self.phase_tw.rowCount(), 3) self.assertEqual(len(self.model.phase_model.phases), 3) self.assertEqual(len(self.widget.pattern_widget.phases), 3) self.assertEqual(self.phase_tw.currentRow(), 0) - self.controller.remove_btn_click_callback() - self.controller.remove_btn_click_callback() - self.controller.remove_btn_click_callback() + click_button(self.phase_widget.delete_btn) + click_button(self.phase_widget.delete_btn) + click_button(self.phase_widget.delete_btn) self.assertEqual(self.phase_tw.rowCount(), 0) self.assertEqual(len(self.model.phase_model.phases), 0) self.assertEqual(len(self.widget.pattern_widget.phases), 0) self.assertEqual(self.phase_tw.currentRow(), -1) - self.controller.remove_btn_click_callback() + click_button(self.phase_widget.delete_btn) self.assertEqual(self.phase_tw.rowCount(), 0) self.assertEqual(len(self.model.phase_model.phases), 0) self.assertEqual(len(self.widget.pattern_widget.phases), 0) @@ -105,10 +108,12 @@ def test_manual_deleting_phases(self): def test_automatic_deleting_phases(self): self.load_phases() self.load_phases() + self.assertEqual(self.phase_tw.rowCount(), 12) self.assertEqual(len(self.model.phase_model.phases), 12) self.assertEqual(len(self.widget.pattern_widget.phases), 12) - self.controller.clear_phases() + + click_button(self.phase_widget.clear_btn) self.assertEqual(self.phase_tw.rowCount(), 0) self.assertEqual(len(self.model.phase_model.phases), 0) self.assertEqual(len(self.widget.pattern_widget.phases), 0) @@ -119,45 +124,65 @@ def test_automatic_deleting_phases(self): self.load_phases() self.assertEqual(self.phase_tw.rowCount(), multiplier * 6) - self.controller.clear_phases() + + click_button(self.phase_widget.clear_btn) self.assertEqual(self.phase_tw.rowCount(), 0) self.assertEqual(len(self.model.phase_model.phases), 0) self.assertEqual(len(self.widget.pattern_widget.phases), 0) self.assertEqual(self.phase_tw.currentRow(), -1) - def test_pressure_step_change(self): + def test_pressurestep_spinbox_changes_pressure_spinboxes(self): self.load_phases() - old_pressure = self.widget.phase_pressure_sb.value() - self.widget.phase_pressure_sb.stepUp() - step = self.widget.phase_pressure_step_msb.value() - self.assertAlmostEqual(self.widget.phase_pressure_sb.value(), old_pressure + step, places=5) + for ind in range(6): + self.assertEqual(self.phase_widget.pressure_sbs[ind].singleStep(), 1) - def test_temperature_step_change(self): + new_step = 5 + self.phase_widget.pressure_step_msb.setValue(new_step) + for ind in range(6): + self.assertEqual(self.phase_widget.pressure_sbs[ind].singleStep(), new_step) + + def test_temperaturestep_spinbox_changes_temperature_spinboxes(self): self.load_phases() - old_temperature = self.widget.phase_temperature_sb.value() - self.widget.phase_temperature_sb.stepUp() - step = self.widget.phase_temperature_step_msb.value() - self.assertAlmostEqual(self.widget.phase_temperature_sb.value(), old_temperature + step, places=5) + for ind in range(6): + self.assertEqual(self.phase_widget.temperature_sbs[ind].singleStep(), 100) + + new_step = 5 + self.phase_widget.temperature_step_msb.setValue(new_step) + for ind in range(6): + self.assertEqual(self.phase_widget.temperature_sbs[ind].singleStep(), new_step) def test_pressure_change(self): self.load_phases() + click_checkbox(self.phase_widget.apply_to_all_cb) + pressure = 200 - self.widget.phase_pressure_sb.setValue(200) - for ind, phase in enumerate(self.model.phase_model.phases): - self.assertEqual(phase.params['pressure'], pressure) - self.assertEqual(self.phase_widget.get_phase_pressure(ind), pressure) + for ind in [0, 1, 3]: + self.phase_widget.pressure_sbs[ind].setValue(pressure) + self.assertEqual(self.model.phase_model.phases[ind].params['pressure'], pressure) + self.assertEqual(self.model.phase_model.phases[2].params['pressure'], 0) def test_temperature_change(self): self.load_phases() + click_checkbox(self.phase_widget.apply_to_all_cb) + temperature = 1500 - self.widget.phase_temperature_sb.setValue(temperature) - for ind, phase in enumerate(self.model.phase_model.phases): + + for ind in range(len(self.model.phase_model.phases)): + phase = self.model.phase_model.phases[ind] + temperature += ind + + self.assertEqual(self.phase_widget.temperature_sbs[ind].isEnabled(), + phase.has_thermal_expansion()) + + if self.phase_widget.temperature_sbs[ind].isEnabled(): + self.phase_widget.temperature_sbs[ind].setValue(temperature) + if phase.has_thermal_expansion(): self.assertEqual(phase.params['temperature'], temperature) self.assertEqual(self.phase_widget.get_phase_temperature(ind), temperature) else: self.assertEqual(phase.params['temperature'], 298) - self.assertEqual(self.phase_widget.get_phase_temperature(ind), None) + self.assertEqual(self.phase_widget.get_phase_temperature(ind), 298) def test_pressure_auto_step_change(self): self.load_phases() @@ -188,23 +213,21 @@ def test_temperature_auto_step_change(self): def test_apply_to_all_for_new_added_phase_in_table_widget(self): temperature = 1500 pressure = 200 - self.phase_widget.temperature_sb.setValue(temperature) - self.phase_widget.pressure_sb.setValue(pressure) self.load_phases() + self.phase_widget.temperature_sbs[0].setValue(temperature) + self.phase_widget.pressure_sbs[0].setValue(pressure) + self.load_phases() + for ind, phase in enumerate(self.model.phase_model.phases): + self.assertEqual(phase.params['pressure'], pressure) self.assertEqual(self.phase_widget.get_phase_pressure(ind), pressure) - if phase.has_thermal_expansion(): - self.assertEqual(phase.params['temperature'], temperature) - self.assertEqual(self.phase_widget.get_phase_temperature(ind), temperature) - else: - self.assertEqual(phase.params['temperature'], 298) - self.assertEqual(self.phase_widget.get_phase_temperature(ind), None) + def test_apply_to_all_for_new_added_phase_d_positions(self): pressure = 50 self.load_phase('au_Anderson.jcpds') - self.widget.phase_pressure_sb.setValue(pressure) + self.phase_widget.pressure_sbs[0].setValue(pressure) self.load_phase('au_Anderson.jcpds') reflections1 = self.model.phase_model.get_lines_d(0) @@ -276,8 +299,8 @@ def test_save_and_load_phase_lists(self): old_phase_list_data = [[0 for x in range(5)] for y in range(old_phase_list_length)] for row in range(self.widget.phase_tw.rowCount()): old_phase_list_data[row][2] = self.phase_tw.item(row, 2).text() - old_phase_list_data[row][3] = self.phase_tw.item(row, 3).text() - old_phase_list_data[row][4] = self.phase_tw.item(row, 4).text() + old_phase_list_data[row][3] = self.phase_widget.pressure_sbs[row].text() + old_phase_list_data[row][4] = self.phase_widget.temperature_sbs[row].text() # clear and load the saved list to make sure all phases have been loaded click_button(self.widget.phase_clear_btn) @@ -288,12 +311,30 @@ def test_save_and_load_phase_lists(self): for row in range(self.widget.phase_tw.rowCount()): self.assertEqual(self.phase_tw.item(row, 2).text(), old_phase_list_data[row][2]) - self.assertEqual(self.phase_tw.item(row, 3).text(), old_phase_list_data[row][3]) - self.assertEqual(self.phase_tw.item(row, 4).text(), old_phase_list_data[row][4]) + self.assertEqual(self.phase_widget.pressure_sbs[row].text(), old_phase_list_data[row][3]) + self.assertEqual(self.phase_widget.temperature_sbs[row].text(), old_phase_list_data[row][4]) # delete phase list file os.remove(os.path.join(data_path, phase_list_file_name)) + def test_bulk_change_visibility_of_phases(self): + self.load_phases() + for cb in self.phase_widget.phase_show_cbs: + self.assertTrue(cb.isChecked()) + + self.controller.phase_tw_header_section_clicked(0) + for cb in self.phase_widget.phase_show_cbs: + self.assertFalse(cb.isChecked()) + + click_checkbox(self.phase_widget.phase_show_cbs[1]) + self.controller.phase_tw_header_section_clicked(0) + for ind, cb in enumerate(self.phase_widget.phase_show_cbs): + self.assertFalse(cb.isChecked()) + + self.controller.phase_tw_header_section_clicked(0) + for ind, cb in enumerate(self.phase_widget.phase_show_cbs): + self.assertTrue(cb.isChecked()) + def load_phases(self): self.load_phase('ar.jcpds') self.load_phase('ag.jcpds') diff --git a/dioptas/tests/data/TransferCorrection/original.tif b/dioptas/tests/data/TransferCorrection/original.tif new file mode 100644 index 000000000..adaf6ac20 Binary files /dev/null and b/dioptas/tests/data/TransferCorrection/original.tif differ diff --git a/dioptas/tests/data/TransferCorrection/response.tif b/dioptas/tests/data/TransferCorrection/response.tif new file mode 100644 index 000000000..9b5e94a4b Binary files /dev/null and b/dioptas/tests/data/TransferCorrection/response.tif differ diff --git a/dioptas/tests/data/TransferCorrection/transfer.poni b/dioptas/tests/data/TransferCorrection/transfer.poni new file mode 100644 index 000000000..689270483 --- /dev/null +++ b/dioptas/tests/data/TransferCorrection/transfer.poni @@ -0,0 +1,12 @@ +# Nota: C-Order, 1 refers to the Y axis, 2 to the X axis +# Calibration done at Thu Dec 6 09:08:00 2018 +poni_version: 2 +Detector: Detector +Detector_config: {"pixel1": 0.00017199999999999998, "pixel2": 0.00017199999999999998, "max_shape": null} +Distance: 0.240882519989 +Poni1: 0.0187224660258 +Poni2: 0.12455739305 +Rot1: -0.489909991194 +Rot2: -0.00421057699617 +Rot3: -6.21382585287e-07 +Wavelength: 2.952e-11 diff --git a/dioptas/tests/data/calibrants/AgBh.D b/dioptas/tests/data/calibrants/AgBh.D deleted file mode 100644 index 28f5abe13..000000000 --- a/dioptas/tests/data/calibrants/AgBh.D +++ /dev/null @@ -1,10 +0,0 @@ -5.838000000000000256e+01 -2.919000000000000128e+01 -1.946000000000000085e+01 -1.459500000000000064e+01 -1.167600000000000016e+01 -9.730000000000000426e+00 -8.339999999999999858e+00 -7.297500000000000320e+00 -6.486666666666667247e+00 -5.838000000000000078e+00 diff --git a/dioptas/tests/data/calibrants/Au.D b/dioptas/tests/data/calibrants/Au.D deleted file mode 100644 index bfbdc4077..000000000 --- a/dioptas/tests/data/calibrants/Au.D +++ /dev/null @@ -1,7 +0,0 @@ -# Gold from American mineralogy database. CFC -#CELL PARAMETERS: 4.0782 4.0782 4.0782 90.000 90.000 90.000 - 2.3546 - 2.0391 - 1.4419 - 1.2296 - 1.1773 diff --git a/dioptas/tests/data/calibrants/C14H30O.D b/dioptas/tests/data/calibrants/C14H30O.D deleted file mode 100644 index e2c505fd4..000000000 --- a/dioptas/tests/data/calibrants/C14H30O.D +++ /dev/null @@ -1,13 +0,0 @@ -39.62137612 -19.69088301 -13.19213037 -9.911401875 -7.938421369 -6.619651252 -5.151512942 -3.986828585 -3.686212563 -2.969631573 -2.538129421 -2.336337449 -2.106523478 diff --git a/dioptas/tests/data/calibrants/CeO2.D b/dioptas/tests/data/calibrants/CeO2.D deleted file mode 100644 index 18f296b99..000000000 --- a/dioptas/tests/data/calibrants/CeO2.D +++ /dev/null @@ -1,40 +0,0 @@ -3.12404 -2.7055 -1.91308 -1.63148 -1.56202 -1.35275 -1.24137 -1.20994 -1.10452 -1.04135 -0.956539 -0.914626 -0.901833 -0.855554 -0.82517 -0.815739 -0.781011 -0.757692 -0.750371 -0.723075 -0.704452 -0.676375 -0.661059 -0.65618 -0.637692 -0.624808 -0.620684 -0.604968 -0.593934 -0.590389 -0.576815 -0.567227 -0.552258 -0.543826 -0.5411 -0.530592 -0.523101 -0.520674 -0.504578 -0.502399 diff --git a/dioptas/tests/data/calibrants/Cr2O3.D b/dioptas/tests/data/calibrants/Cr2O3.D deleted file mode 100644 index 3ac6a3607..000000000 --- a/dioptas/tests/data/calibrants/Cr2O3.D +++ /dev/null @@ -1,21 +0,0 @@ -3.6364 -2.6676 -2.4848 -2.2662 -2.1788 -2.0516 -1.8182 -1.6744 -1.5820 -1.5808 -1.4673 -1.4346 -1.2965 -1.2909 -1.2424 -1.2121 -1.2121 -1.1757 -1.1495 -1.1331 -1.1262 diff --git a/dioptas/tests/data/calibrants/CrOx.D b/dioptas/tests/data/calibrants/CrOx.D deleted file mode 100644 index 5a09814f4..000000000 --- a/dioptas/tests/data/calibrants/CrOx.D +++ /dev/null @@ -1,8 +0,0 @@ -3.645 -2.672 -2.487 -2.181 -1.819 -1.676 -1.467 -1.433 diff --git a/dioptas/tests/data/calibrants/LaB6.D b/dioptas/tests/data/calibrants/LaB6.D deleted file mode 100644 index 4e0ebae79..000000000 --- a/dioptas/tests/data/calibrants/LaB6.D +++ /dev/null @@ -1,61 +0,0 @@ -4.156950000000000145e+00 -2.939407534053418480e+00 -2.400016201507815250e+00 -2.078475000000000072e+00 -1.859044555813550215e+00 -1.697067731043755634e+00 -1.469703767026709240e+00 -1.385650000000000048e+00 -1.314543011943694495e+00 -1.253367583842534660e+00 -1.200008100753907625e+00 -1.152930490349233228e+00 -1.110991619567852640e+00 -1.039237500000000036e+00 -1.008208466494784350e+00 -9.798025113511397155e-01 -9.536697349132317036e-01 -9.295222779067751073e-01 -9.071208588165086129e-01 -8.862647178544548199e-01 -8.485338655218778170e-01 -8.313900000000000734e-01 -8.152449679626742052e-01 -8.000054005026049353e-01 -7.719262360350958030e-01 -7.589517618070334981e-01 -7.348518835133546201e-01 -7.236321119250381795e-01 -7.129110435081521535e-01 -7.026527958387414063e-01 -6.928250000000000242e-01 -6.833983702790050740e-01 -6.743463365695233636e-01 -6.572715059718472475e-01 -6.492065194828410268e-01 -6.414313106249179830e-01 -6.339289319441210324e-01 -6.266837919212673302e-01 -6.196815186045167012e-01 -6.129088396380996118e-01 -6.000040503769538125e-01 -5.938499999999999890e-01 -5.878815068106837405e-01 -5.820894295300235166e-01 -5.764652451746166140e-01 -5.710009963065029170e-01 -5.656892436812518410e-01 -5.554958097839263198e-01 -5.506014781701533689e-01 -5.458342760762237011e-01 -5.411887935017357654e-01 -5.322429080370519872e-01 -5.279331779334419661e-01 -5.237264720252357009e-01 -5.196187500000000181e-01 -5.156061899506101387e-01 -5.116851734265371876e-01 -5.078522717169430267e-01 -5.041042332473921750e-01 -5.004379719848882635e-01 -4.968505567572607529e-01 diff --git a/dioptas/tests/data/calibrants/PBBA.D b/dioptas/tests/data/calibrants/PBBA.D deleted file mode 100644 index 60d481347..000000000 --- a/dioptas/tests/data/calibrants/PBBA.D +++ /dev/null @@ -1,81 +0,0 @@ -# Para Bromo Benzoic Acid P21/a a=29.59(5) b=6.15(1) c=3.98(1) beta=95.5(1)° -# d-spacing h k l F^2 multiplicity - 14.7269 # 2 0 0 2551.99 2 -# 7.36344 # 4 0 0 654.242 2 -# 6.02017 # 1 1 0 118.776 4 -# 5.67503 # 2 1 0 451.579 4 - 5.2119 # 3 1 0 3035.68 4 -# 4.90896 # 6 0 0 95.0908 2 - 4.72021 # 4 1 0 15550.7 4 -# 4.25409 # 5 1 0 332.09 4 - 3.96168 # 0 0 1 3966.99 2 -# 3.92111 # 2 0 -1 353.984 2 - 3.83661 # 6 1 0 8845.26 4 - 3.73688 # 2 0 1 45891.7 2 - 3.68172 # 8 0 0 3125.32 2 -# 3.63727 # 4 0 -1 59.7457 2 -# 3.47269 # 7 1 0 724.891 4 - 3.35711 # 4 0 1 6562.23 2 - 3.33957 # 1 1 -1 4688.14 4 -# 3.33048 # 0 1 1 11.4192 4 - 3.30627 # 2 1 -1 27684.6 4 -# 3.28001 # 1 1 1 48.8491 4 - 3.23837 # 6 0 -1 1075.13 2 - 3.23423 # 3 1 -1 1544 4 -# 3.19356 # 2 1 1 211.973 4 - 3.15892 # 8 1 0 14249.9 4 - 3.13072 # 4 1 -1 6888.34 4 -# 3.07937 # 3 1 1 259.425 4 - 3.075 # 0 2 0 14283 2 -# 3.05838 # 1 2 0 4.71736 4 - 3.01008 # 2 2 0 6749.65 4 - 3.00478 # 5 1 -1 903.164 4 - 2.94795 # 6 0 1 2540.03 2 - 2.94668 # 4 1 1 3135.32 4 - 2.94538 # 10 0 0 3981.69 2 -# 2.93444 # 3 2 0 624.538 4 -# 2.88906 # 9 1 0 88.7212 4 - 2.8654 # 6 1 -1 9919.3 4 - 2.83752 # 4 2 0 4336.12 4 - 2.83586 # 8 0 -1 1506.38 2 -# 2.80402 # 5 1 1 130.113 4 -# 2.72595 # 5 2 0 1.36298 4 - 2.72027 # 7 1 -1 112.307 4 - 2.65833 # 6 1 1 3417.43 4 - 2.65644 # 10 1 0 3960.41 4 - 2.60595 # 6 2 0 284.354 4 - 2.57658 # 8 0 1 517.374 2 - 2.57526 # 8 1 -1 2224.29 4 - 2.51469 # 7 1 1 259.857 4 -# 2.48268 # 7 2 0 0.0766548 4 - 2.48026 # 10 0 -1 12651.5 2 - 2.45502 # 11 1 0 1544.61 4 - 2.45448 # 12 0 0 4552.52 2 - 2.43441 # 9 1 -1 1163.34 4 -# 2.43265 # 1 2 -1 1.52801 4 - 2.42913 # 0 2 1 4931.39 4 - 2.41969 # 2 2 -1 1495.41 4 -# 2.40934 # 1 2 1 185.417 4 -# 2.39102 # 3 2 -1 11.6816 4 - 2.37645 # 8 1 1 7838.23 4 - 2.37444 # 2 2 1 9476.07 4 -#14.73 #2 0 0# -#07.36 #4 0 0# -#06.02 #1 1 0# -#05.68 #2 1 0# -#05.21 #3 1 0# -#04.91 #6 0 0# -#04.72 #4 1 0# -#04.25 #5 1 0# -#03.84 #6 1 0# -#03.74 #2 0 1# -#03.68 #8 0 0# -#03.47 #7 1 0# -#03.36 #4 0 1# -#03.33 #0 1 1 -#03.28 #1 1 1 -#03.19 #2 1 1 -#03.16 #8 1 0 -#03.08 #3 1 1 -#03.06 #1 2 0 -#03.01 #2 2 0 diff --git a/dioptas/tests/data/calibrants/Si.D b/dioptas/tests/data/calibrants/Si.D deleted file mode 100644 index 317faed5e..000000000 --- a/dioptas/tests/data/calibrants/Si.D +++ /dev/null @@ -1,11 +0,0 @@ - 3.1354 - 1.9200 - 1.6374 - 1.3577 - 1.2459 - 1.1085 - 1.0452 - 0.9600 - 0.9180 - 0.8587 - 0.8282 diff --git a/dioptas/tests/data/calibrants/alpha_Al2O3.D b/dioptas/tests/data/calibrants/alpha_Al2O3.D deleted file mode 100644 index dc977fff2..000000000 --- a/dioptas/tests/data/calibrants/alpha_Al2O3.D +++ /dev/null @@ -1,30 +0,0 @@ -3.479 -2.55 -2.378 -2.165 #Weak -2.084 -1.963 #Weak -1.739 -1.601 -1.546 -1.514 -1.511 -1.404 -1.373 -1.335 -1.275 -1.239 -1.234 -1.193 -1.189 -1.160 -1.147 -1.138 -1.125 -1.124 -1.099 -1.082 -1.078 -1.046 -1.042 -1.017 diff --git a/dioptas/tests/data/distortion/CeO2_calib.edf b/dioptas/tests/data/distortion/CeO2_calib.edf new file mode 100644 index 000000000..0a4be8a7f Binary files /dev/null and b/dioptas/tests/data/distortion/CeO2_calib.edf differ diff --git a/dioptas/tests/data/distortion/f4mnew.spline b/dioptas/tests/data/distortion/f4mnew.spline new file mode 100644 index 000000000..ceea2bcff --- /dev/null +++ b/dioptas/tests/data/distortion/f4mnew.spline @@ -0,0 +1,120 @@ +SPATIAL DISTORTION SPLINE INTERPOLATION COEFFICIENTS + + VALID REGION + 0.0000000E+00 0.0000000E+00 0.2048000E+04 0.2048000E+04 + + GRID SPACING, X-PIXEL SIZE, Y-PIXEL SIZE + 0.2500000E+04 0.5000000E+02 0.5000000E+02 + + X-DISTORTION + 17 16 + 0.0000000E+00 0.0000000E+00 0.0000000E+00 0.0000000E+00 0.2418913E+03 + 0.4475310E+03 0.6782789E+03 0.8075235E+03 0.1007055E+04 0.1239804E+04 + 0.1429597E+04 0.1594739E+04 0.1826166E+04 0.2048000E+04 0.2048000E+04 + 0.2048000E+04 0.2048000E+04 + 0.0000000E+00 0.0000000E+00 0.0000000E+00 0.0000000E+00 0.2266119E+03 + 0.4592156E+03 0.7153538E+03 0.9272284E+03 0.1166317E+04 0.1445248E+04 + 0.1728452E+04 0.1854673E+04 0.2048000E+04 0.2048000E+04 0.2048000E+04 + 0.2048000E+04 +-0.5201186E+02-0.4632922E+02-0.3659042E+02-0.2617715E+02-0.2055044E+02 +-0.1816569E+02-0.1833720E+02-0.2464587E+02-0.3270218E+02-0.4417843E+02 +-0.5272483E+02-0.5698082E+02-0.4299451E+02-0.3769843E+02-0.2753066E+02 +-0.1770070E+02-0.1238515E+02-0.1120339E+02-0.1141512E+02-0.1511483E+02 +-0.2368953E+02-0.3363860E+02-0.4125049E+02-0.4511438E+02-0.2728195E+02 +-0.2234236E+02-0.1447735E+02-0.7427963E+01-0.4427287E+01-0.3083623E+01 +-0.2762622E+01-0.6096533E+01-0.1050874E+02-0.1929293E+02-0.2405182E+02 +-0.2878974E+02-0.1200905E+02-0.9141261E+01-0.3368401E+01-0.4425942E+00 + 0.1807091E+01 0.1770488E+01 0.2227225E+01 0.1156541E+01-0.2079139E+01 +-0.6696260E+01-0.1006073E+02-0.1370021E+02-0.4734754E+01-0.3412446E+01 +-0.7691700E+00 0.1087414E+01 0.1827055E+01 0.1588120E+01 0.2572252E+01 + 0.1880351E+01 0.3652128E+00-0.2641584E+01-0.4990768E+01-0.6473622E+01 +-0.6701069E+00-0.1797274E+00 0.1080743E+01 0.1440889E+01 0.1790087E+01 + 0.1519273E+01 0.1748704E+01 0.1592924E+01 0.9542685E+00-0.8752533E+00 +-0.1724366E+01-0.3584190E+01 0.1562799E+01 0.1694366E+01 0.8224127E+00 + 0.5737285E+00-0.9952993E-01 0.4258121E+00 0.8908537E-01 0.4486451E+00 + 0.3816212E+00 0.2933783E+00-0.2795915E+00-0.7440553E+00 0.1990556E+01 + 0.4143248E+00-0.3716868E+00-0.2341895E+01-0.2584696E+01-0.2980217E+01 +-0.2444303E+01-0.1977985E+01-0.1428718E+01-0.1145486E+01-0.7660698E+00 +-0.4903854E+00 0.3452271E+01 0.1787123E+01-0.1411178E+01-0.3498115E+01 +-0.4973338E+01-0.4888458E+01-0.4478839E+01-0.3327857E+01-0.1895608E+01 +-0.4307023E+00 0.9922206E+00 0.1012953E+01 0.8061293E+01 0.4954515E+01 + 0.5688176E+00-0.4039334E+01-0.5451169E+01-0.6051292E+01-0.5060211E+01 +-0.3338642E+01-0.6182896E+00 0.2139060E+01 0.3942676E+01 0.6273694E+01 + 0.1768721E+02 0.1334404E+02 0.6380904E+01-0.1227076E-01-0.3448512E+01 +-0.4090919E+01-0.3259585E+01-0.3285005E+00 0.3720122E+01 0.9512928E+01 + 0.1307105E+02 0.1503928E+02 0.3122856E+02 0.2671659E+02 0.1786632E+02 + 0.8171778E+01 0.3098663E+01 0.1640701E+01 0.2565387E+01 0.7130813E+01 + 0.1327358E+02 0.2050599E+02 0.2441765E+02 0.2797728E+02 0.4177501E+02 + 0.3579494E+02 0.2419088E+02 0.1493757E+02 0.8873123E+01 0.6112015E+01 + 0.7850461E+01 0.1198786E+02 0.1914062E+02 0.2716549E+02 0.3198692E+02 + 0.3701125E+02 + + Y-DISTORTION + 20 22 + 0.0000000E+00 0.0000000E+00 0.0000000E+00 0.0000000E+00 0.2043579E+03 + 0.3492441E+03 0.4024646E+03 0.4540469E+03 0.5744396E+03 0.8545580E+03 + 0.9737934E+03 0.1095663E+04 0.1221194E+04 0.1338507E+04 0.1576779E+04 + 0.1800904E+04 0.2048000E+04 0.2048000E+04 0.2048000E+04 0.2048000E+04 + 0.0000000E+00 0.0000000E+00 0.0000000E+00 0.0000000E+00 0.1152642E+03 + 0.2249289E+03 0.3213147E+03 0.4248349E+03 0.5359870E+03 0.6550099E+03 + 0.7619839E+03 0.8868854E+03 0.1011955E+04 0.1056239E+04 0.1107808E+04 + 0.1260051E+04 0.1430448E+04 0.1722168E+04 0.2048000E+04 0.2048000E+04 + 0.2048000E+04 0.2048000E+04 +-0.5945474E+02-0.5072061E+02-0.4284021E+02-0.3358655E+02-0.2633997E+02 +-0.2081924E+02-0.1484850E+02-0.1070098E+02-0.7315581E+01-0.3600893E+01 +-0.9924139E+00 0.1429608E+01 0.3731673E+01 0.7445563E+01 0.1385084E+02 + 0.2816854E+02 0.4715980E+02 0.6093507E+02-0.5318327E+02-0.4526072E+02 +-0.3879781E+02-0.3040670E+02-0.2316829E+02-0.1795274E+02-0.1241689E+02 +-0.9471337E+01-0.5613902E+01-0.3043698E+01-0.1468989E+01 0.1518077E+01 + 0.3322921E+01 0.6208187E+01 0.1254984E+02 0.2512542E+02 0.4234255E+02 + 0.5653271E+02-0.4038531E+02-0.3906926E+02-0.3224828E+02-0.2455687E+02 +-0.1887620E+02-0.1397250E+02-0.1088123E+02-0.6301063E+01-0.4856787E+01 +-0.1929207E+01-0.6956266E+00 0.1400922E+01 0.2523445E+01 0.5511462E+01 + 0.9715003E+01 0.1995859E+02 0.3683191E+02 0.4900761E+02-0.3529956E+02 +-0.3285453E+02-0.2655235E+02-0.1992601E+02-0.1496329E+02-0.1081308E+02 +-0.7530067E+01-0.5599672E+01-0.3249732E+01-0.1492528E+01-0.9779863E+00 + 0.1305801E+01 0.2459862E+01 0.3806675E+01 0.7637379E+01 0.1625609E+02 + 0.3025987E+02 0.4155986E+02-0.3213951E+02-0.2889303E+02-0.2363780E+02 +-0.1683462E+02-0.1310348E+02-0.9614949E+01-0.6125175E+01-0.4647862E+01 +-0.2922698E+01-0.1163170E+01-0.9138898E+00 0.1335463E+01 0.2011345E+01 + 0.4114643E+01 0.6775389E+01 0.1355921E+02 0.2683530E+02 0.3811830E+02 +-0.2951514E+02-0.2672780E+02-0.2113238E+02-0.1538376E+02-0.1107524E+02 +-0.7923703E+01-0.5763533E+01-0.3673053E+01-0.2572707E+01-0.6880887E+00 +-0.6997524E+00 0.1147373E+01 0.2094500E+01 0.3047910E+01 0.5911966E+01 + 0.1194592E+02 0.2412055E+02 0.3468470E+02-0.2466121E+02-0.2182657E+02 +-0.1710677E+02-0.1201129E+02-0.8130177E+01-0.6115394E+01-0.3579694E+01 +-0.2557990E+01-0.1754875E+01-0.4529223E+00-0.7767681E+00 0.1389353E+01 + 0.1542376E+01 0.2304399E+01 0.4632015E+01 0.9071615E+01 0.1926565E+02 + 0.2810112E+02-0.2130017E+02-0.1975521E+02-0.1400348E+02-0.9899141E+01 +-0.7303840E+01-0.4083793E+01-0.3375714E+01-0.1881242E+01-0.7786947E+00 + 0.6073860E-01-0.5983245E+00 0.1191399E+01 0.1235920E+01 0.2315969E+01 + 0.3093900E+01 0.7386682E+01 0.1552314E+02 0.2423882E+02-0.2132743E+02 +-0.1709353E+02-0.1390471E+02-0.9101821E+01-0.5562990E+01-0.4256365E+01 +-0.2119341E+01-0.1463529E+01-0.8794067E+00-0.7576630E-01-0.6598123E+00 + 0.1230344E+01 0.1339024E+01 0.1719924E+01 0.3032930E+01 0.6196914E+01 + 0.1405724E+02 0.2175341E+02-0.2038570E+02-0.1878391E+02-0.1369556E+02 +-0.8841237E+01-0.6076307E+01-0.3769681E+01-0.2082396E+01-0.1321203E+01 +-0.7231995E+00 0.8555128E-01-0.4580436E+00 0.9452130E+00 0.1009681E+01 + 0.1846175E+01 0.2513440E+01 0.5956625E+01 0.1330991E+02 0.2125992E+02 +-0.2143649E+02-0.1900654E+02-0.1326153E+02-0.9230311E+01-0.6014440E+01 +-0.3562411E+01-0.2444701E+01-0.1035424E+01-0.5656492E+00-0.2383011E+00 +-0.4776030E+00 0.1204508E+01 0.1238095E+01 0.1449259E+01 0.2880404E+01 + 0.5820508E+01 0.1387772E+02 0.2117304E+02-0.2389377E+02-0.2055486E+02 +-0.1615096E+02-0.9680655E+01-0.6663705E+01-0.4392826E+01-0.2335671E+01 +-0.1197933E+01-0.6970004E+00-0.3020044E+00-0.7496802E+00 0.8270651E+00 + 0.1185197E+01 0.1759859E+01 0.2779980E+01 0.6642985E+01 0.1467102E+02 + 0.2311983E+02-0.2804659E+02-0.2549255E+02-0.1826835E+02-0.1346295E+02 +-0.8540470E+01-0.5533566E+01-0.3172450E+01-0.1894693E+01-0.1104339E+01 +-0.6525576E+00-0.4742335E+00 0.1246633E+01 0.1063987E+01 0.1649360E+01 + 0.3694115E+01 0.8224239E+01 0.1820715E+02 0.2653784E+02-0.3733800E+02 +-0.3372348E+02-0.2738339E+02-0.1906237E+02-0.1349040E+02-0.9222338E+01 +-0.6313040E+01-0.3651978E+01-0.3142033E+01-0.1165016E+01-0.1575816E+01 + 0.3240710E+00 0.1397794E+01 0.2695992E+01 0.5373291E+01 0.1199238E+02 + 0.2509657E+02 0.3601522E+02-0.4820952E+02-0.4368536E+02-0.3523262E+02 +-0.2698876E+02-0.2027069E+02-0.1405958E+02-0.1005852E+02-0.7106818E+01 +-0.3803500E+01-0.2744409E+01-0.1634626E+01 0.1271196E+01 0.1612557E+01 + 0.3114590E+01 0.7501362E+01 0.1742124E+02 0.3276107E+02 0.4355239E+02 +-0.4987164E+02-0.4912157E+02-0.4101380E+02-0.3079878E+02-0.2328437E+02 +-0.1795016E+02-0.1247761E+02-0.8115891E+01-0.6372280E+01-0.2623033E+01 +-0.2048201E+01 0.5066016E+00 0.1814694E+01 0.4533741E+01 0.9318826E+01 + 0.2076617E+02 0.3722160E+02 0.5068682E+02 diff --git a/dioptas/tests/ehook.py b/dioptas/tests/ehook.py index 965aaad13..76b563195 100644 --- a/dioptas/tests/ehook.py +++ b/dioptas/tests/ehook.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/tests/functional_tests/__init__.py b/dioptas/tests/functional_tests/__init__.py index 3978ecf85..ea6703229 100644 --- a/dioptas/tests/functional_tests/__init__.py +++ b/dioptas/tests/functional_tests/__init__.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/tests/functional_tests/test_functional_JcpdsEditor.py b/dioptas/tests/functional_tests/test_functional_JcpdsEditor.py index 70f9bf675..475959b26 100644 --- a/dioptas/tests/functional_tests/test_functional_JcpdsEditor.py +++ b/dioptas/tests/functional_tests/test_functional_JcpdsEditor.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -41,6 +43,10 @@ def calculate_cubic_d_spacing(h, k, l, a): return np.sqrt(1. / d_squared_inv) +def calculate_cubic_q_value(h, k, l, a): + return 2.0 * np.pi / calculate_cubic_d_spacing(h, k, l, a) + + class JcpdsEditorFunctionalTest(QtTest): def setUp(self): self.model = DioptasModel() @@ -316,6 +322,11 @@ def test_reflection_editing_and_saving_of_files(self): calculate_cubic_d_spacing(1, 1, 3, 4.0786), delta=0.0001) + # he also sees that there are awesome Q values calculated as well! + self.assertAlmostEqual(self.get_reflection_table_value(12, 8), + calculate_cubic_q_value(1, 1, 3, 4.0786), + delta=0.0001) + # then she decides that everybody should screw with the table and clears it: QTest.mouseClick(self.jcpds_widget.reflections_clear_btn, QtCore.Qt.LeftButton) @@ -374,7 +385,7 @@ def test_connection_between_main_gui_and_jcpds_editor_lattice_and_eos_parameter( self.main_controller.calibration_controller.set_calibrant(7) self.main_controller.model.img_model.load(os.path.join(data_path, 'LaB6_40keV_MarCCD.tif')) self.main_controller.widget.tabWidget.setCurrentIndex(2) - self.main_controller.widget.integration_widget.tabWidget.setCurrentIndex(3) + # self.main_controller.widget.integration_widget.tabWidget.setCurrentIndex(3) QtWidgets.QFileDialog.getOpenFileNames = MagicMock(return_value= [os.path.join(jcpds_path, 'au_Anderson.jcpds'), @@ -401,9 +412,9 @@ def test_connection_between_main_gui_and_jcpds_editor_lattice_and_eos_parameter( self.assertNotAlmostEqual(self.phase_controller.model.phase_model.phases[0].params['a0'], 10.4) - # Now he selects one phase in the phase table and starts the JCPDS editor and realizes he wanted to click another - # phase -- so he just selects it without closing and reopening the editor - # and magically the new parameters show up + # Now he selects one phase in the phase table and starts the JCPDS editor and realizes he wanted to click + # another phase -- so he just selects it without closing and reopening the editor and magically the new + # parameters show up self.phase_controller.phase_widget.phase_tw.selectRow(1) QTest.mouseClick(self.phase_controller.phase_widget.edit_btn, QtCore.Qt.LeftButton) @@ -450,7 +461,7 @@ def test_connection_between_main_gui_and_jcpds_editor_lattice_and_eos_parameter( # then he increases the pressure and sees the line moving, but he realizes that the equation of state may # be wrong so he decides to change the parameters in the jcpds-editor - self.main_controller.integration_controller.widget.phase_pressure_sb.setValue(10) + self.main_controller.integration_controller.widget.phase_widget.pressure_sbs[0].setValue(10) prev_line_pos = self.compare_line_position(prev_line_pos, 2, 0) self.enter_value_into_text_field(self.jcpds_widget.eos_K_txt, 120) @@ -463,7 +474,7 @@ def test_connection_between_main_gui_and_jcpds_editor_lattice_and_eos_parameter( self.enter_value_into_text_field(self.jcpds_widget.eos_alphaT_txt, 6.234e-5) self.assertEqual(self.phase_controller.model.phase_model.phases[2].params['alpha_t0'], 6.234e-5) - self.main_controller.integration_controller.widget.phase_temperature_sb.setValue(1300) + self.main_controller.integration_controller.widget.phase_widget.temperature_sbs[0].setValue(1300) prev_line_pos = self.compare_line_position(prev_line_pos, 2, 0) self.enter_value_into_text_field(self.jcpds_widget.eos_alphaT_txt, 10.234e-5) @@ -489,7 +500,6 @@ def test_connection_between_main_gui_and_jcpds_editor_reflections(self): self.main_controller.calibration_controller.set_calibrant(7) self.main_controller.model.img_model.load(os.path.join(data_path, 'LaB6_40keV_MarCCD.tif')) self.main_controller.widget.tabWidget.setCurrentIndex(2) - self.main_controller.widget.integration_widget.tabWidget.setCurrentIndex(3) QtWidgets.QFileDialog.getOpenFileNames = MagicMock(return_value= [os.path.join(jcpds_path, 'au_Anderson.jcpds'), @@ -571,7 +581,6 @@ def test_phase_name_difference_after_modified(self): self.main_controller.calibration_controller.set_calibrant(7) self.main_controller.model.img_model.load(os.path.join(data_path, 'LaB6_40keV_MarCCD.tif')) self.main_controller.widget.tabWidget.setCurrentIndex(2) - self.main_controller.widget.integration_widget.tabWidget.setCurrentIndex(3) QtWidgets.QFileDialog.getOpenFileNames = MagicMock( return_value=[os.path.join(jcpds_path, 'au_Anderson.jcpds')]) @@ -602,7 +611,6 @@ def test_high_pressure_values_are_shown_in_jcpds_editor(self): self.main_controller.calibration_controller.set_calibrant(7) self.main_controller.model.img_model.load(os.path.join(data_path, 'LaB6_40keV_MarCCD.tif')) self.main_controller.widget.tabWidget.setCurrentIndex(2) - self.main_controller.widget.integration_widget.tabWidget.setCurrentIndex(3) QtWidgets.QFileDialog.getOpenFileNames = MagicMock( return_value=[os.path.join(jcpds_path, 'au_Anderson.jcpds')]) @@ -621,10 +629,10 @@ def test_high_pressure_values_are_shown_in_jcpds_editor(self): QtWidgets.QApplication.processEvents() # he looks at the jcpds_editor and sees that there are not only hkl and intensity values for each reflection but - # also d0, d, two_theta0 and two_theta + # also d0, d, two_theta0, two_theta, q0 and q # however, the zero values and non-zero values are all the same - self.assertEqual(8, self.jcpds_widget.reflection_table.columnCount()) + self.assertEqual(10, self.jcpds_widget.reflection_table.columnCount()) for row_ind in range(13): self.assertEqual(self.get_reflection_table_value(row_ind, 4), self.get_reflection_table_value(row_ind, 6)) self.assertAlmostEqual(self.get_reflection_table_value(row_ind, 5), @@ -641,10 +649,10 @@ def test_high_pressure_values_are_shown_in_jcpds_editor(self): self.assertEqual(float(self.jcpds_widget.lattice_eos_volume_txt.text()), float(self.jcpds_widget.lattice_volume_txt.text())) - # then he decides to increase pressure in the main_view and sees that the non "0" values resemble the high pressure - # values + # then he decides to increase pressure in the main_view and sees that the non "0" values resemble the high + # pressure values - self.phase_controller.phase_widget.pressure_sb.setValue(30) + self.phase_controller.phase_widget.pressure_sbs[0].setValue(30) for row_ind in range(13): self.assertNotEqual(self.get_reflection_table_value(row_ind, 4), self.get_reflection_table_value(row_ind, 5)) diff --git a/dioptas/tests/functional_tests/test_functional_integration.py b/dioptas/tests/functional_tests/test_functional_integration.py index a60c1f428..acb6d59d3 100644 --- a/dioptas/tests/functional_tests/test_functional_integration.py +++ b/dioptas/tests/functional_tests/test_functional_integration.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -304,15 +306,15 @@ def test_changing_integration_unit(self): def test_configuration_selected_changes_img_mode(self): click_button(self.integration_widget.img_mode_btn) - self.assertEqual(self.integration_image_controller.img_mode, "Cake") + self.assertEqual(self.integration_widget.img_mode, "Cake") self.assertTrue(self.model.current_configuration.auto_integrate_cake) self.model.add_configuration() self.model.select_configuration(0) - self.assertEqual(self.integration_image_controller.img_mode, "Cake") + self.assertEqual(self.integration_widget.img_mode, "Cake") self.model.select_configuration(1) self.assertFalse(self.model.current_configuration.auto_integrate_cake) - self.assertEqual(self.integration_image_controller.img_mode, "Image") + self.assertEqual(self.integration_widget.img_mode, "Image") def test_configuration_selected_changes_green_line_position_in_image_mode(self): self.integration_image_controller.img_mouse_click(0, 500) diff --git a/dioptas/tests/functional_tests/test_img_bg.py b/dioptas/tests/functional_tests/test_img_bg.py index b880943ca..772fb2f60 100644 --- a/dioptas/tests/functional_tests/test_img_bg.py +++ b/dioptas/tests/functional_tests/test_img_bg.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/tests/functional_tests/test_save_and_load_project.py b/dioptas/tests/functional_tests/test_save_and_load_project.py index c355cb368..25b33151a 100644 --- a/dioptas/tests/functional_tests/test_save_and_load_project.py +++ b/dioptas/tests/functional_tests/test_save_and_load_project.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -18,6 +20,7 @@ import os import gc +from collections import OrderedDict import numpy as np @@ -30,7 +33,9 @@ from ...model.CalibrationModel import CalibrationModel from ...model.util.HelperModule import rotate_matrix_m90, rotate_matrix_p90 from ...controller.MainController import MainController -from ..utility import QtTest, click_button, delete_if_exists +from ..utility import QtTest, click_button, delete_if_exists, enter_value_into_text_field + +from ... import calibrants_path unittest_path = os.path.dirname(__file__) data_path = os.path.join(unittest_path, '../data') @@ -60,18 +65,22 @@ test_calibration_file = os.path.join(data_path, 'CeO2_Pilatus1M.poni') test_calibration_file_2 = os.path.join(data_path, 'a_CeO2_Pilatus1M.poni') poly_order = 55 -pyfai_params = {'detector': 'Detector', - 'dist': 0.196711580484, - 'poni1': 0.0813975852141, - 'poni2': 0.0820662115429, - 'rot1': 0.00615439716514, - 'rot2': -0.00156720465515, - 'rot3': 1.68707221612e-06, - 'pixel1': 7.9e-05, - 'pixel2': 7.9e-05, - 'wavelength': 3.1e-11, - 'polarization_factor': 0.99 - } +x_min = 1.0 +x_max = 8.0 +pyfai_params = OrderedDict({ + 'detector': 'Detector', + 'pixel1': 7.9e-05, + 'pixel2': 7.9e-05, + 'max_shape': None, + 'dist': 0.196711580484, + 'poni1': 0.0813975852141, + 'poni2': 0.0820662115429, + 'rot1': 0.00615439716514, + 'rot2': -0.00156720465515, + 'rot3': 1.68707221612e-06, + 'wavelength': 3.1e-11, + 'polarization_factor': 0.99 +}) pressure = 12.0 @@ -128,7 +137,6 @@ def load_image(self, file_name): self.raw_img_data = self.model.current_configuration.img_model.raw_img_data def save_and_load_configuration(self, prepare_function, intermediate_function=None, mock_1d_integration=True): - if mock_1d_integration: with patch.object(CalibrationModel, 'integrate_1d', return_value=(np.linspace(0, 20, 1001), np.ones((1001,)))): @@ -139,7 +147,7 @@ def save_and_load_configuration(self, prepare_function, intermediate_function=No self.model.reset() self.model.working_directories = {'calibration': '', 'mask': '', 'image': os.path.expanduser("~"), 'pattern': '', 'overlay': '', 'phase': ''} - + self.setUp() if intermediate_function: intermediate_function() @@ -153,7 +161,6 @@ def save_and_load_configuration(self, prepare_function, intermediate_function=No self.model.working_directories = {'calibration': '', 'mask': '', 'image': os.path.expanduser("~"), 'pattern': '', 'overlay': '', 'phase': ''} self.setUp() - if intermediate_function: intermediate_function() @@ -161,7 +168,7 @@ def save_and_load_configuration(self, prepare_function, intermediate_function=No delete_if_exists(config_file_path) - def existing_files_intermediate_settings(self): + def disable_calibration_check(self): self.check_calibration = False def save_configuration(self): @@ -179,6 +186,7 @@ def load_configuration(self): saved_pyfai_params, _ = self.model.calibration_model.get_calibration_parameter() if 'splineFile' in saved_pyfai_params: del saved_pyfai_params['splineFile'] + print(saved_pyfai_params) self.assertDictEqual(saved_pyfai_params, pyfai_params) #################################################################################################################### @@ -278,13 +286,14 @@ def roi_settings(self): #################################################################################################################### def test_with_cbn_correction(self): self.save_and_load_configuration(self.cbn_correction_settings, mock_1d_integration=False) - self.assertEqual(self.model.current_configuration.img_model.img_corrections. - corrections["cbn"]._diamond_thickness, 1.9) + print(self.model.current_configuration.img_model.img_corrections.corrections) + self.assertEqual(self.model.current_configuration.img_model.img_corrections.corrections["cbn"]. \ + _diamond_thickness, 1.9) def cbn_correction_settings(self): self.controller.widget.integration_widget.cbn_groupbox.setChecked(True) - self.controller.widget.integration_widget.cbn_diamond_thickness_txt.setText('1.9') - self.controller.integration_controller.image_controller.cbn_groupbox_changed() + self.controller.widget.integration_widget.cbn_param_tw.cellWidget(0, 1).setText('1.9') + self.controller.integration_controller.correction_controller.cbn_groupbox_changed() #################################################################################################################### def test_with_oiadac_correction(self): @@ -296,9 +305,39 @@ def test_with_oiadac_correction(self): def oiadac_correction_settings(self): self.controller.widget.integration_widget.oiadac_groupbox.setChecked(True) - self.controller.widget.integration_widget.oiadac_thickness_txt.setText('30') - self.controller.widget.integration_widget.oiadac_abs_length_txt.setText('450') - self.controller.integration_controller.image_controller.oiadac_groupbox_changed() + self.controller.widget.integration_widget.oiadac_param_tw.cellWidget(0, 1).setText('30') + self.controller.widget.integration_widget.oiadac_param_tw.cellWidget(1, 1).setText('450') + self.controller.integration_controller.correction_controller.oiadac_groupbox_changed() + + #################################################################################################################### + def test_with_transfer_correction(self): + self.save_and_load_configuration(self.transfer_correction_settings) + + # test model + self.assertEqual(self.model.img_model.transfer_correction.original_filename, self.original_filename) + self.assertEqual(self.model.img_model.transfer_correction.response_filename, self.response_filename) + self.assertTrue(self.model.img_model.has_corrections()) + + # test widget + correction_widget = self.widget.integration_widget.integration_control_widget.corrections_control_widget + self.assertTrue(correction_widget.transfer_gb.isChecked()) + self.assertEqual(correction_widget.transfer_original_filename_lbl.text(), + os.path.basename(self.original_filename)) + self.assertEqual(correction_widget.transfer_original_filename_lbl.text(), + os.path.basename(self.original_filename)) + + def transfer_correction_settings(self): + self.original_filename = os.path.join(data_path, 'TransferCorrection', 'original.tif') + self.response_filename = os.path.join(data_path, 'TransferCorrection', 'response.tif') + correction_widget = self.widget.integration_widget.integration_control_widget.corrections_control_widget + + correction_widget.transfer_gb.setChecked(True) + QtWidgets.QFileDialog.getOpenFileNames = MagicMock(return_value=[self.original_filename]) + click_button(self.widget.integration_widget.load_img_btn) + QtWidgets.QFileDialog.getOpenFileName = MagicMock(return_value=self.original_filename) + click_button(correction_widget.transfer_load_original_btn) + QtWidgets.QFileDialog.getOpenFileName = MagicMock(return_value=self.response_filename) + click_button(correction_widget.transfer_load_response_btn) #################################################################################################################### def test_configuration_in_cake_mode(self): @@ -313,6 +352,13 @@ def test_with_fit_bg(self): self.save_and_load_configuration(self.fit_bg_settings) self.assertEqual(self.widget.integration_widget.bkg_pattern_poly_order_sb.value(), poly_order) + def test_with_q_and_fit_bg(self): + self.save_and_load_configuration(self.q_and_fit_bg_settings) + self.assertAlmostEqual(float(self.controller.integration_controller.widget.bkg_pattern_x_min_txt.text()), x_min, + 1) + self.assertAlmostEqual(float(self.controller.integration_controller.widget.bkg_pattern_x_max_txt.text()), x_max, + 1) + #################################################################################################################### def test_multiple_configurations(self): self.save_and_load_configuration(self.add_configuration) @@ -325,6 +371,14 @@ def fit_bg_settings(self): self.controller.integration_controller.widget.qa_bkg_pattern_btn.click() self.controller.integration_controller.widget.bkg_pattern_poly_order_sb.setValue(poly_order) + def q_and_fit_bg_settings(self): + self.controller.integration_controller.widget.pattern_q_btn.click() + self.controller.integration_controller.widget.qa_bkg_pattern_btn.click() + self.controller.integration_controller.widget.bkg_pattern_poly_order_sb.setValue(poly_order) + + enter_value_into_text_field(self.controller.integration_controller.widget.bkg_pattern_x_min_txt, str(x_min)) + enter_value_into_text_field(self.controller.integration_controller.widget.bkg_pattern_x_max_txt, str(x_max)) + #################################################################################################################### def test_with_background_image(self): self.save_and_load_configuration(self.add_background_image) @@ -337,6 +391,19 @@ def add_background_image(self): QtWidgets.QFileDialog.getOpenFileName = MagicMock(return_value=test_image_file_name) click_button(self.controller.integration_controller.widget.bkg_image_load_btn) + #################################################################################################################### + def test_with_automatic_background_subtraction(self): + self.save_and_load_configuration(self.activate_automatic_background_subtraction, mock_1d_integration=True) + self.assertGreater(self.model.pattern.auto_background_subtraction_roi[0], 9.) + self.assertTrue(self.widget.integration_widget.qa_bkg_pattern_btn.isChecked()) + self.assertGreater(float(self.widget.integration_widget.bkg_pattern_x_min_txt.text()), 9) + + def activate_automatic_background_subtraction(self): + self.model.pattern.load(os.path.join(data_path, 'pattern_001.xy')) + click_button(self.widget.integration_widget.qa_bkg_pattern_btn) + enter_value_into_text_field(self.widget.integration_widget.bkg_pattern_x_min_txt, '9') + self.assertGreater(self.model.pattern.auto_background_subtraction_roi[0], 9) + #################################################################################################################### def test_save_settings_on_closing(self): with patch.object(CalibrationModel, 'integrate_1d', return_value=(np.linspace(0, 20, 1001), @@ -357,3 +424,27 @@ def test_file_browsing(self): def prepare_file_browsing(self): self.load_image(os.path.join(data_path, 'image_001.tif')) + + #################################################################################################################### + def test_distortion_correction(self): + self.check_calibration = False + self.save_and_load_configuration(self.prepare_distortion_correction_test, + intermediate_function=self.disable_calibration_check) + self.assertIsNotNone(self.model.calibration_model.distortion_spline_filename) + self.assertEqual(self.widget.calibration_widget.spline_filename_txt.text(), + 'f4mnew.spline') + + def prepare_distortion_correction_test(self): + self.model.img_model.load(os.path.join(data_path, 'distortion', 'CeO2_calib.edf')) + + self.model.calibration_model.find_peaks_automatic(1025.1, 1226.8, 0) + self.model.calibration_model.set_calibrant(os.path.join(calibrants_path, 'CeO2.D')) + self.model.calibration_model.start_values['dist'] = 300e-3 + self.model.calibration_model.start_values['pixel_height'] = 50e-6 + self.model.calibration_model.start_values['pixel_width'] = 50e-6 + self.model.calibration_model.start_values['wavelength'] = 0.1e-10 + + self.model.calibration_model.load_distortion(os.path.join(data_path, 'distortion', 'f4mnew.spline')) + self.model.calibration_model.calibrate() + + _, y1 = self.model.calibration_model.integrate_1d() diff --git a/dioptas/tests/functional_tests/test_userinterface.py b/dioptas/tests/functional_tests/test_userinterface.py index b1316f2c6..2fc66d8a5 100644 --- a/dioptas/tests/functional_tests/test_userinterface.py +++ b/dioptas/tests/functional_tests/test_userinterface.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/tests/run_tests.py b/dioptas/tests/run_tests.py index c95bd5f71..0658ae709 100644 --- a/dioptas/tests/run_tests.py +++ b/dioptas/tests/run_tests.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/tests/unit_tests/__init__.py b/dioptas/tests/unit_tests/__init__.py index 76b3deb76..6257ce947 100644 --- a/dioptas/tests/unit_tests/__init__.py +++ b/dioptas/tests/unit_tests/__init__.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/tests/unit_tests/test_CalibrationModel.py b/dioptas/tests/unit_tests/test_CalibrationModel.py index 489dc7a71..067ed2ebf 100644 --- a/dioptas/tests/unit_tests/test_CalibrationModel.py +++ b/dioptas/tests/unit_tests/test_CalibrationModel.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -16,7 +18,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import unittest import os import numpy as np @@ -146,8 +147,8 @@ def test_get_pixel_ind(self): azi_array = self.calibration_model.pattern_geometry.chia for i in range(100): - ind1 = np.random.random_integers(0, 2023) - ind2 = np.random.random_integers(0, 2023) + ind1 = np.random.randint(0, 2024) + ind2 = np.random.randint(0, 2024) tth = tth_array[ind1, ind2] azi = azi_array[ind1, ind2] @@ -177,5 +178,70 @@ def test_correct_solid_angle(self): _, y2 = self.calibration_model.integrate_1d() self.assertNotEqual(np.sum(y1), np.sum(y2)) + def test_distortion_correction(self): + self.img_model.load(os.path.join(data_path, 'distortion', 'CeO2_calib.edf')) + + self.calibration_model.find_peaks_automatic(1025.1, 1226.8, 0) + self.calibration_model.set_calibrant(os.path.join(calibrants_path, 'CeO2.D')) + self.calibration_model.start_values['dist'] = 300e-3 + self.calibration_model.start_values['pixel_height'] = 50e-6 + self.calibration_model.start_values['pixel_width'] = 50e-6 + self.calibration_model.start_values['wavelength'] = 0.1e-10 + + self.calibration_model.calibrate() + + _, y1 = self.calibration_model.integrate_1d() + + self.calibration_model.load_distortion(os.path.join(data_path, 'distortion', 'f4mnew.spline')) + self.calibration_model.calibrate() + + _, y2 = self.calibration_model.integrate_1d() + self.assertNotAlmostEqual(y1[100], y2[100]) + + def test_get_two_theta_img_with_distortion(self): + self.img_model.load(os.path.join(data_path, 'distortion', 'CeO2_calib.edf')) + + self.calibration_model.find_peaks_automatic(1025.1, 1226.8, 0) + self.calibration_model.set_calibrant(os.path.join(calibrants_path, 'CeO2.D')) + self.calibration_model.start_values['dist'] = 300e-3 + self.calibration_model.start_values['pixel_height'] = 50e-6 + self.calibration_model.start_values['pixel_width'] = 50e-6 + self.calibration_model.start_values['wavelength'] = 0.1e-10 + self.calibration_model.calibrate() + + x, y = np.array((100,)), np.array((100,)) + self.calibration_model.get_two_theta_img(x, y) + self.calibration_model.load_distortion(os.path.join(data_path, 'distortion', 'f4mnew.spline')) + self.calibration_model.get_two_theta_img(x, y) + + + def test_cake_integration_with_small_azimuth_range(self): + self.img_model.load(os.path.join(data_path, 'CeO2_Pilatus1M.tif')) + self.calibration_model.load(os.path.join(data_path, 'CeO2_Pilatus1M.poni')) + + full_cake = self.calibration_model.integrate_2d() + small_cake = self.calibration_model.integrate_2d(azimuth_range=(40, 130)) + self.assertFalse(np.array_equal(full_cake, small_cake)) + + def test_cake_integration_with_off_azimuth_range(self): + self.img_model.load(os.path.join(data_path, 'CeO2_Pilatus1M.tif')) + self.calibration_model.load(os.path.join(data_path, 'CeO2_Pilatus1M.poni')) + self.calibration_model.integrate_2d(azimuth_range=(150, -130)) + + self.assertGreater(np.min(self.calibration_model.cake_azi), 150) + self.assertLess(np.max(self.calibration_model.cake_azi), 230) + + def test_cake_integration_with_different_num_points(self): + self.img_model.load(os.path.join(data_path, 'CeO2_Pilatus1M.tif')) + self.calibration_model.load(os.path.join(data_path, 'CeO2_Pilatus1M.poni')) + + self.calibration_model.integrate_2d(rad_points=200) + self.assertEqual(len(self.calibration_model.cake_tth), 200) + + self.calibration_model.integrate_2d(azimuth_points=200) + self.assertEqual(len(self.calibration_model.cake_azi), 200) + + if __name__ == '__main__': unittest.main() + diff --git a/dioptas/tests/unit_tests/test_DioptasModel.py b/dioptas/tests/unit_tests/test_DioptasModel.py index 479a553a7..e2fa30ce8 100644 --- a/dioptas/tests/unit_tests/test_DioptasModel.py +++ b/dioptas/tests/unit_tests/test_DioptasModel.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -115,6 +117,38 @@ def test_integrate_cake_with_mask(self): cake_img2 = self.model.current_configuration.cake_img self.assertFalse(np.array_equal(cake_img1, cake_img2)) + def test_integrate_cake_with_different_azimuth_points(self): + self.model.calibration_model.load(os.path.join(data_path, 'CeO2_Pilatus1M.poni')) + self.model.current_configuration.auto_integrate_cake = True + self.model.img_model.load(os.path.join(data_path, 'CeO2_Pilatus1M.tif')) + + self.assertEqual(self.model.current_configuration.cake_img.shape[0], 360) + self.model.current_configuration.cake_azimuth_points = 720 + self.assertEqual(self.model.current_configuration.cake_img.shape[0], 720) + + def test_integrate_cake_with_different_rad_points(self): + self.model.calibration_model.load(os.path.join(data_path, 'CeO2_Pilatus1M.poni')) + self.model.current_configuration.auto_integrate_cake = True + self.model.img_model.load(os.path.join(data_path, 'CeO2_Pilatus1M.tif')) + + self.assertGreater(self.model.current_configuration.cake_img.shape[1], 360) + self.model.current_configuration.integration_rad_points = 720 + self.assertEqual(self.model.current_configuration.cake_img.shape[1], 720) + + def test_change_cake_azimuth_range(self): + self.model.calibration_model.load(os.path.join(data_path, 'CeO2_Pilatus1M.poni')) + self.model.current_configuration.auto_integrate_cake = True + self.model.img_model.load(os.path.join(data_path, 'CeO2_Pilatus1M.tif')) + + self.model.current_configuration.cake_azimuth_range = [-180, 180] + + self.assertAlmostEqual(self.model.current_configuration.calibration_model.cake_azi[0], -179.5, places=4) + self.assertAlmostEqual(self.model.current_configuration.calibration_model.cake_azi[-1], 179.5, places=4) + + self.model.current_configuration.cake_azimuth_range = [-100, 100] + self.assertGreater(self.model.current_configuration.calibration_model.cake_azi[0], -100) + self.assertLess(self.model.current_configuration.calibration_model.cake_azi[-1], 100) + def test_combine_patterns(self): x1 = np.linspace(0, 10) y1 = np.ones(x1.shape) @@ -223,4 +257,3 @@ def test_clear_model(self): self.model.add_configuration() self.model.reset() - diff --git a/dioptas/tests/unit_tests/test_FileNameIterator.py b/dioptas/tests/unit_tests/test_FileNameIterator.py index eee5fca99..f9663645b 100644 --- a/dioptas/tests/unit_tests/test_FileNameIterator.py +++ b/dioptas/tests/unit_tests/test_FileNameIterator.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/tests/unit_tests/test_HelperModule.py b/dioptas/tests/unit_tests/test_HelperModule.py index 3a4156d13..8764eb91d 100644 --- a/dioptas/tests/unit_tests/test_HelperModule.py +++ b/dioptas/tests/unit_tests/test_HelperModule.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/tests/unit_tests/test_ImgCorrections.py b/dioptas/tests/unit_tests/test_ImgCorrections.py index 410b5359a..5b3937d05 100644 --- a/dioptas/tests/unit_tests/test_ImgCorrections.py +++ b/dioptas/tests/unit_tests/test_ImgCorrections.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -23,6 +25,8 @@ from ...model.util.ImgCorrection import ImgCorrectionManager, ImgCorrectionInterface, \ ObliqueAngleDetectorAbsorptionCorrection +from ...model.util.ImgCorrection import TransferFunctionCorrection, load_image +from ..utility import unittest_data_path class DummyCorrection(ImgCorrectionInterface): @@ -95,14 +99,6 @@ def test_delete_corrections_with_names(self): self.corrections.delete() self.assertEqual(np.mean(self.corrections.get_data()), 5) - def test_set_shape(self): - self.corrections.add(DummyCorrection((2048, 2048), 3), "cbn Correction") - self.corrections.add(DummyCorrection((2048, 2048), 5), "oblique angle Correction") - - # setting it to a different shape should remove the existent corrections - self.corrections.set_shape((2048, 1024)) - self.assertEqual(self.corrections.get_data(), None) - from pyFAI.azimuthalIntegrator import AzimuthalIntegrator from ...model.util.ImgCorrection import CbnCorrection @@ -185,11 +181,11 @@ def setUp(self): self.azi_array = self.calibration_data.pattern_geometry.chiArray((2048, 2048)) self.oiadac_correction = ObliqueAngleDetectorAbsorptionCorrection( - self.tth_array, self.azi_array, - detector_thickness=40, - absorption_length=465.5, - tilt=detector_tilt, - rotation=detector_tilt_rotation, + self.tth_array, self.azi_array, + detector_thickness=40, + absorption_length=465.5, + tilt=detector_tilt, + rotation=detector_tilt_rotation, ) self.img_data.add_img_correction(self.oiadac_correction, "oiadac") @@ -214,15 +210,15 @@ def test_the_world(self): def fcn2min(params): cbn_correction = CbnCorrection( - tth_array=self.tth_array, - azi_array=self.azi_array, - diamond_thickness=params['diamond_thickness'].value, - seat_thickness=params['seat_thickness'].value, - small_cbn_seat_radius=params['small_cbn_seat_radius'].value, - large_cbn_seat_radius=params['large_cbn_seat_radius'].value, - tilt=params['tilt'].value, - tilt_rotation=params['tilt_rotation'].value, - cbn_abs_length=params["cbn_abs_length"].value + tth_array=self.tth_array, + azi_array=self.azi_array, + diamond_thickness=params['diamond_thickness'].value, + seat_thickness=params['seat_thickness'].value, + small_cbn_seat_radius=params['small_cbn_seat_radius'].value, + large_cbn_seat_radius=params['large_cbn_seat_radius'].value, + tilt=params['tilt'].value, + tilt_rotation=params['tilt_rotation'].value, + cbn_abs_length=params["cbn_abs_length"].value ) self.img_data.add_img_correction(cbn_correction, "cbn") tth, int = self.calibration_data.integrate_1d(mask=self.mask_data.get_mask()) @@ -239,15 +235,15 @@ def output_values(param1, iteration, residual): # plotting result: cbn_correction = CbnCorrection( - tth_array=self.tth_array, - azi_array=self.azi_array, - diamond_thickness=params['diamond_thickness'].value, - seat_thickness=params['seat_thickness'].value, - small_cbn_seat_radius=params['small_cbn_seat_radius'].value, - large_cbn_seat_radius=params['large_cbn_seat_radius'].value, - tilt=params['tilt'].value, - tilt_rotation=params['tilt_rotation'].value, - cbn_abs_length=params['cbn_abs_length'].value + tth_array=self.tth_array, + azi_array=self.azi_array, + diamond_thickness=params['diamond_thickness'].value, + seat_thickness=params['seat_thickness'].value, + small_cbn_seat_radius=params['small_cbn_seat_radius'].value, + large_cbn_seat_radius=params['large_cbn_seat_radius'].value, + tilt=params['tilt'].value, + tilt_rotation=params['tilt_rotation'].value, + cbn_abs_length=params['cbn_abs_length'].value ) self.img_data.add_img_correction(cbn_correction, "cbn") tth, int = self.calibration_data.integrate_1d(mask=self.mask_data.get_mask()) @@ -312,12 +308,12 @@ def tearDown(self): def test_that_it_is_correctly_calculating(self): oblique_correction = ObliqueAngleDetectorAbsorptionCorrection( - tth_array=self.tth_array, - azi_array=self.azi_array, - detector_thickness=40, - absorption_length=465.5, - tilt=self.tilt, - rotation=self.rotation + tth_array=self.tth_array, + azi_array=self.azi_array, + detector_thickness=40, + absorption_length=465.5, + tilt=self.tilt, + rotation=self.rotation ) oblique_correction_data = oblique_correction.get_data() self.assertGreater(np.sum(oblique_correction_data), 0) @@ -325,5 +321,31 @@ def test_that_it_is_correctly_calculating(self): del oblique_correction +class TransferFunctionCorrectionTest(unittest.TestCase): + def setUp(self): + self.original_image_filename = os.path.join(unittest_data_path, 'TransferCorrection', 'original.tif') + self.response_image_filename = os.path.join(unittest_data_path, 'TransferCorrection', 'response.tif') + self.original_data = load_image(self.original_image_filename) + self.response_data = load_image(self.response_image_filename) + + self.transfer_correction = TransferFunctionCorrection(self.original_image_filename, + self.response_image_filename) + + def test_general_behavior(self): + transfer_data = self.transfer_correction.get_data() + self.assertEqual(transfer_data.shape, self.original_data.shape) + self.assertEqual(transfer_data.shape, self.response_data.shape) + self.assertAlmostEqual(np.sum(transfer_data - self.response_data / self.original_data), 0) + + def test_apply_img_transformations(self): + from ...model.util.HelperModule import rotate_matrix_m90 + img_transformations = [np.fliplr, rotate_matrix_m90] + self.transfer_correction.set_img_transformations(img_transformations) + + transfer_data = self.transfer_correction.get_data() + self.assertNotEqual(transfer_data, self.original_data) + self.assertNotEqual(transfer_data, self.response_data) + + if __name__ == '__main__': unittest.main() diff --git a/dioptas/tests/unit_tests/test_ImgModel.py b/dioptas/tests/unit_tests/test_ImgModel.py index fb2c36e6b..4529ce166 100644 --- a/dioptas/tests/unit_tests/test_ImgModel.py +++ b/dioptas/tests/unit_tests/test_ImgModel.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/tests/unit_tests/test_Jcpds.py b/dioptas/tests/unit_tests/test_Jcpds.py index eeb925bb1..f97726ee6 100644 --- a/dioptas/tests/unit_tests/test_Jcpds.py +++ b/dioptas/tests/unit_tests/test_Jcpds.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/tests/unit_tests/test_MaskModel.py b/dioptas/tests/unit_tests/test_MaskModel.py index 300c3c8f4..d0a42a1c8 100644 --- a/dioptas/tests/unit_tests/test_MaskModel.py +++ b/dioptas/tests/unit_tests/test_MaskModel.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/tests/unit_tests/test_NewFileInDirectoryWatcher.py b/dioptas/tests/unit_tests/test_NewFileInDirectoryWatcher.py index 9d96d2775..25d240775 100644 --- a/dioptas/tests/unit_tests/test_NewFileInDirectoryWatcher.py +++ b/dioptas/tests/unit_tests/test_NewFileInDirectoryWatcher.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/tests/unit_tests/test_OverlayModel.py b/dioptas/tests/unit_tests/test_OverlayModel.py index 1e5e5c091..6a716a243 100644 --- a/dioptas/tests/unit_tests/test_OverlayModel.py +++ b/dioptas/tests/unit_tests/test_OverlayModel.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/tests/unit_tests/test_Pattern.py b/dioptas/tests/unit_tests/test_Pattern.py index af411f15f..5a98e0786 100644 --- a/dioptas/tests/unit_tests/test_Pattern.py +++ b/dioptas/tests/unit_tests/test_Pattern.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/tests/unit_tests/test_PatternModel.py b/dioptas/tests/unit_tests/test_PatternModel.py index ca4983c27..f8440e060 100644 --- a/dioptas/tests/unit_tests/test_PatternModel.py +++ b/dioptas/tests/unit_tests/test_PatternModel.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/tests/unit_tests/test_PhaseModel.py b/dioptas/tests/unit_tests/test_PhaseModel.py index e0aab4b12..af7227cfe 100644 --- a/dioptas/tests/unit_tests/test_PhaseModel.py +++ b/dioptas/tests/unit_tests/test_PhaseModel.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/tests/unit_tests/test_background_extraction.py b/dioptas/tests/unit_tests/test_background_extraction.py index 2834cb605..ba593dc0b 100644 --- a/dioptas/tests/unit_tests/test_background_extraction.py +++ b/dioptas/tests/unit_tests/test_background_extraction.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/tests/unit_tests/test_cif.py b/dioptas/tests/unit_tests/test_cif.py index 7c9fa70b9..67ed240e5 100644 --- a/dioptas/tests/unit_tests/test_cif.py +++ b/dioptas/tests/unit_tests/test_cif.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/tests/utility.py b/dioptas/tests/utility.py index 1c7f8275a..df856c607 100644 --- a/dioptas/tests/utility.py +++ b/dioptas/tests/utility.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/tests/widget_tests/__init__.py b/dioptas/tests/widget_tests/__init__.py index 76b3deb76..6257ce947 100644 --- a/dioptas/tests/widget_tests/__init__.py +++ b/dioptas/tests/widget_tests/__init__.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/tests/widget_tests/test_ConfigurationWidget.py b/dioptas/tests/widget_tests/test_ConfigurationWidget.py index fe5da0644..e6c60c617 100644 --- a/dioptas/tests/widget_tests/test_ConfigurationWidget.py +++ b/dioptas/tests/widget_tests/test_ConfigurationWidget.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/tests/widget_tests/test_JcpdsEditorWidget.py b/dioptas/tests/widget_tests/test_JcpdsEditorWidget.py index 2b70b50e4..ed6dbce5d 100644 --- a/dioptas/tests/widget_tests/test_JcpdsEditorWidget.py +++ b/dioptas/tests/widget_tests/test_JcpdsEditorWidget.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/widgets/CalibrationWidget.py b/dioptas/widgets/CalibrationWidget.py index 5fd94460c..93dca2a9e 100644 --- a/dioptas/widgets/CalibrationWidget.py +++ b/dioptas/widgets/CalibrationWidget.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -18,7 +20,7 @@ import os -from qtpy import QtWidgets +from qtpy import QtWidgets, QtGui, QtCore from pyqtgraph import GraphicsLayoutWidget from ..widgets.plot_widgets import MaskImgWidget, CalibrationCakeWidget @@ -27,6 +29,8 @@ from .CustomWidgets import NumberTextField, LabelAlignRight, CleanLooksComboBox, SpinBoxAlignRight, \ DoubleSpinBoxAlignRight, FlatButton +from .. import icons_path + class CalibrationWidget(QtWidgets.QWidget): """ @@ -101,6 +105,7 @@ def create_shortcuts(self): self.search_size_sb = peak_selection_gb.search_size_sb self.automatic_peak_num_inc_cb = peak_selection_gb.automatic_peak_num_inc_cb self.clear_peaks_btn = peak_selection_gb.clear_peaks_btn + self.undo_peaks_btn = peak_selection_gb.undo_peaks_btn self.f2_update_btn = self.calibration_control_widget.fit2d_parameters_widget.update_btn self.pf_update_btn = self.calibration_control_widget.pyfai_parameters_widget.update_btn @@ -111,6 +116,17 @@ def create_shortcuts(self): self.f2_distance_cb = self.calibration_control_widget.fit2d_parameters_widget.distance_cb self.pf_distance_cb = self.calibration_control_widget.pyfai_parameters_widget.distance_cb + self.pf_poni1_cb = self.calibration_control_widget.pyfai_parameters_widget.poni1_cb + self.pf_poni2_cb = self.calibration_control_widget.pyfai_parameters_widget.poni2_cb + self.pf_rot1_cb = self.calibration_control_widget.pyfai_parameters_widget.rotation1_cb + self.pf_rot2_cb = self.calibration_control_widget.pyfai_parameters_widget.rotation2_cb + self.pf_rot3_cb = self.calibration_control_widget.pyfai_parameters_widget.rotation3_cb + + distortion_gb = self.calibration_control_widget.calibration_parameters_widget.distortion_correction_gb + self.load_spline_btn = distortion_gb.spline_load_btn + self.spline_filename_txt = distortion_gb.spline_filename_txt + self.spline_reset_btn = distortion_gb.spline_reset_btn + self.img_widget = self.calibration_display_widget.img_widget self.cake_widget = self.calibration_display_widget.cake_widget self.pattern_widget = self.calibration_display_widget.pattern_widget @@ -343,10 +359,12 @@ def __init__(self, *args, **kwargs): self.start_values_gb = StartValuesGroupBox(self) self.peak_selection_gb = PeakSelectionGroupBox() self.refinement_options_gb = RefinementOptionsGroupBox() + self.distortion_correction_gb = DistortionCorrectionGroupBox() self._layout.addWidget(self.start_values_gb) self._layout.addWidget(self.peak_selection_gb) self._layout.addWidget(self.refinement_options_gb) + self._layout.addWidget(self.distortion_correction_gb) self._layout.addSpacerItem(QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)) @@ -449,8 +467,13 @@ def __init__(self): self._layout.addItem(QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum), 4, 2, 1, 2) + self.undo_peaks_btn = FlatButton("Undo") self.clear_peaks_btn = FlatButton("Clear All Peaks") - self._layout.addWidget(self.clear_peaks_btn, 5, 0, 1, 4) + + self._peak_btn_layout = QtWidgets.QHBoxLayout() + self._peak_btn_layout.addWidget(self.undo_peaks_btn) + self._peak_btn_layout.addWidget(self.clear_peaks_btn) + self._layout.addLayout(self._peak_btn_layout, 5, 0, 1, 4) self.setLayout(self._layout) @@ -498,6 +521,26 @@ def __init__(self): self.setLayout(self._layout) +class DistortionCorrectionGroupBox(QtWidgets.QGroupBox): + def __init__(self): + super(DistortionCorrectionGroupBox, self).__init__('Distortion Correction') + + self._layout = QtWidgets.QGridLayout() + self.spline_load_btn = FlatButton('Load Splinefile') + self.spline_filename_txt = QtWidgets.QLabel('None') + self.spline_reset_btn = FlatButton() + self.spline_reset_btn.setIcon(QtGui.QIcon(os.path.join(icons_path, 'reset.ico'))) + self.spline_reset_btn.setIconSize(QtCore.QSize(13, 13)) + self.spline_reset_btn.setMaximumWidth(21) + self.spline_reset_btn.setToolTip('Reset distortion correction') + + self._layout.addWidget(self.spline_load_btn, 0, 0) + self._layout.addWidget(self.spline_filename_txt, 1, 0, 1, 2) + self._layout.addWidget(self.spline_reset_btn, 0, 1) + + self.setLayout(self._layout) + + class PyfaiParametersWidget(QtWidgets.QWidget): def __init__(self, *args, **kwargs): super(PyfaiParametersWidget, self).__init__(*args, **kwargs) @@ -525,23 +568,38 @@ def __init__(self, *args, **kwargs): self._layout.addWidget(LabelAlignRight('PONI:'), 3, 0) self.poni1_txt = NumberTextField() + self.poni1_cb = QtWidgets.QCheckBox() + self.poni1_cb.setChecked(True) self._layout.addWidget(self.poni1_txt, 3, 1) self._layout.addWidget(QtWidgets.QLabel('m'), 3, 2) + self._layout.addWidget(self.poni1_cb, 3, 3) self.poni2_txt = NumberTextField() + self.poni2_cb = QtWidgets.QCheckBox() + self.poni2_cb.setChecked(True) self._layout.addWidget(self.poni2_txt, 4, 1) self._layout.addWidget(QtWidgets.QLabel('m'), 4, 2) + self._layout.addWidget(self.poni2_cb, 4, 3) self._layout.addWidget(LabelAlignRight('Rotations'), 5, 0) self.rotation1_txt = NumberTextField() self.rotation2_txt = NumberTextField() self.rotation3_txt = NumberTextField() + self.rotation1_cb = QtWidgets.QCheckBox() + self.rotation2_cb = QtWidgets.QCheckBox() + self.rotation3_cb = QtWidgets.QCheckBox() + self.rotation1_cb.setChecked(True) + self.rotation2_cb.setChecked(True) + self.rotation3_cb.setChecked(True) self._layout.addWidget(self.rotation1_txt, 5, 1) self._layout.addWidget(self.rotation2_txt, 6, 1) self._layout.addWidget(self.rotation3_txt, 7, 1) self._layout.addWidget(QtWidgets.QLabel('rad'), 5, 2) self._layout.addWidget(QtWidgets.QLabel('rad'), 6, 2) self._layout.addWidget(QtWidgets.QLabel('rad'), 7, 2) + self._layout.addWidget(self.rotation1_cb, 5, 3) + self._layout.addWidget(self.rotation2_cb, 6, 3) + self._layout.addWidget(self.rotation3_cb, 7, 3) self._layout.addWidget(LabelAlignRight('Pixel width:'), 8, 0) self.pixel_width_txt = NumberTextField() diff --git a/dioptas/widgets/ConfigurationWidget.py b/dioptas/widgets/ConfigurationWidget.py index 90c60acfe..4ee8be9cf 100644 --- a/dioptas/widgets/ConfigurationWidget.py +++ b/dioptas/widgets/ConfigurationWidget.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/widgets/CustomWidgets.py b/dioptas/widgets/CustomWidgets.py index 4fb8b27f4..59752fb64 100644 --- a/dioptas/widgets/CustomWidgets.py +++ b/dioptas/widgets/CustomWidgets.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- +# -*- coding: utf-8 -*- # Dioptas - GUI program for fast processing of 2D X-ray diffraction data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2013-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -94,6 +96,49 @@ def calc_new_step(self, value, p_int): return pow10floor * 5.0 +class ConservativeSpinBox(QtWidgets.QSpinBox): + """ + This is a modification of the QSpinBox class. The ConservativeSpinbox does not emit the valueChanged signal for + every keypress in the lineedit. The signal is only emitted for the following occasions: + - pressing enter + - the spinbox loses focus + - pressing the up or down arrows + Also the wheel events are disabled. + + This Spinbox is intended for usage with applications were the change in the spinbox value causes long calculations + and does a valueChanged signal on every keypress results in a strange behavior. + """ + valueChanged = QtCore.Signal() + + def __init__(self): + super(QtWidgets.QSpinBox, self).__init__() + + self.lineEdit().editingFinished.connect(self.valueChanged) + self.lineEdit().setAlignment(QtCore.Qt.AlignRight) + + def mousePressEvent(self, e: QtGui.QMouseEvent): + opt = QtWidgets.QStyleOptionSpinBox() + self.initStyleOption(opt) + + if self.style().subControlRect(QtWidgets.QStyle.CC_SpinBox, opt, QtWidgets.QStyle.SC_SpinBoxUp).contains( + e.pos()): + self.setValue(self.value() + 1) + self.valueChanged.emit() + elif self.style().subControlRect(QtWidgets.QStyle.CC_SpinBox, opt, QtWidgets.QStyle.SC_SpinBoxDown).contains( + e.pos()): + self.setValue(self.value() - 1) + self.valueChanged.emit() + + def wheelEvent(self, e: QtGui.QWheelEvent): + pass + + def keyPressEvent(self, e: QtGui.QKeyEvent): + self.lineEdit().keyPressEvent(e) + + def keyReleaseEvent(self, e: QtGui.QKeyEvent): + self.lineEdit().keyReleaseEvent(e) + + class FlatButton(QtWidgets.QPushButton): def __init__(self, *args): super(FlatButton, self).__init__(*args) @@ -195,4 +240,4 @@ def HorizontalSpacerItem(minimum_width=0): def VerticalSpacerItem(): - return QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + return QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding) diff --git a/dioptas/widgets/EpicsWidgets.py b/dioptas/widgets/EpicsWidgets.py index 8da1d51dc..251050187 100644 --- a/dioptas/widgets/EpicsWidgets.py +++ b/dioptas/widgets/EpicsWidgets.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/widgets/MainWidget.py b/dioptas/widgets/MainWidget.py index 974c6c5f9..38cb1b707 100644 --- a/dioptas/widgets/MainWidget.py +++ b/dioptas/widgets/MainWidget.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/widgets/MaskWidget.py b/dioptas/widgets/MaskWidget.py index 73c8af56b..4c9604b46 100644 --- a/dioptas/widgets/MaskWidget.py +++ b/dioptas/widgets/MaskWidget.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- +# -*- coding: utf-8 -*- # Dioptas - GUI program for fast processing of 2D X-ray diffraction data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/widgets/UtilityWidgets.py b/dioptas/widgets/UtilityWidgets.py index ff01f73b3..1bfc41fe9 100644 --- a/dioptas/widgets/UtilityWidgets.py +++ b/dioptas/widgets/UtilityWidgets.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/widgets/__init__.py b/dioptas/widgets/__init__.py index 22767dfec..5afc72098 100644 --- a/dioptas/widgets/__init__.py +++ b/dioptas/widgets/__init__.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/widgets/integration/CustomWidgets.py b/dioptas/widgets/integration/CustomWidgets.py index 99fcc54f2..cdaa2b073 100644 --- a/dioptas/widgets/integration/CustomWidgets.py +++ b/dioptas/widgets/integration/CustomWidgets.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -18,7 +20,7 @@ from qtpy import QtWidgets, QtGui -from ..CustomWidgets import LabelAlignRight, FlatButton +from ..CustomWidgets import LabelAlignRight, FlatButton, HorizontalSpacerItem, HorizontalLine class MouseCurrentAndClickedWidget(QtWidgets.QWidget): @@ -123,14 +125,13 @@ def style_widgets(self, color): self.azi_lbl.setStyleSheet(style_str) -class BrowseFileWidget(QtWidgets.QWidget): +class BrowseFileWidget(QtWidgets.QGroupBox): def __init__(self, files, checkbox_text): super(BrowseFileWidget, self).__init__() self._layout = QtWidgets.QGridLayout() - self._layout.setContentsMargins(0, 0, 0, 0) - self._layout.setVerticalSpacing(6) - self._layout.setHorizontalSpacing(12) + self._layout.setContentsMargins(5, 8, 5, 7) + self._layout.setSpacing(5) self.load_btn = FlatButton('Load {}(s)'.format(files)) self.file_cb = QtWidgets.QCheckBox(checkbox_text) @@ -158,9 +159,13 @@ def __init__(self, files, checkbox_text): self._layout.addWidget(self.browse_by_name_rb, 0, 3) self._layout.addWidget(self.browse_by_time_rb, 1, 3) - self._layout.addWidget(self.file_txt, 0, 4, 1, 2) - self._layout.addWidget(self.directory_txt, 1, 4) - self._layout.addWidget(self.directory_btn, 1, 5) + self._layout.addWidget(self.file_txt, 2, 0, 1, 5) + self._directory_layout = QtWidgets.QHBoxLayout() + self._directory_layout.addWidget(self.directory_txt) + self._directory_layout.addWidget(self.directory_btn) + self._layout.addLayout(self._directory_layout, 3, 0, 1, 5) + self._layout.addItem(QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Maximum, + QtWidgets.QSizePolicy.Minimum), 1, 5) self.setLayout(self._layout) diff --git a/dioptas/widgets/integration/JcpdsEditorWidget.py b/dioptas/widgets/integration/JcpdsEditorWidget.py index 53bc4cfff..dbf458b77 100644 --- a/dioptas/widgets/integration/JcpdsEditorWidget.py +++ b/dioptas/widgets/integration/JcpdsEditorWidget.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -17,6 +19,7 @@ # along with this program. If not, see . from qtpy import QtWidgets, QtCore +import numpy as np from ...widgets.CustomWidgets import NumberTextField, LabelAlignRight, DoubleSpinBoxAlignRight, HorizontalSpacerItem, \ VerticalSpacerItem, FlatButton, CleanLooksComboBox @@ -48,7 +51,7 @@ def __init__(self, parent=None): self._symmetry_layout = QtWidgets.QHBoxLayout() self._symmetry_layout.addWidget(LabelAlignRight('Symmetry')) self.symmetry_cb = CleanLooksComboBox() - self.symmetries = ['cubic', 'tetragonal', 'hexagonal', 'rhombohedral', + self.symmetries = ['cubic', 'tetragonal', 'hexagonal', 'trigonal', 'rhombohedral', 'orthorhombic', 'monoclinic', 'triclinic'] self.symmetry_cb.addItems(self.symmetries) self._symmetry_layout.addWidget(self.symmetry_cb) @@ -143,7 +146,7 @@ def __init__(self, parent=None): self.reflections_gb = QtWidgets.QGroupBox('Reflections') self._reflection_layout = QtWidgets.QGridLayout() self.reflection_table = QtWidgets.QTableWidget() - self.reflection_table.setColumnCount(8) + self.reflection_table.setColumnCount(10) self.reflections_add_btn = FlatButton('Add') self.reflections_delete_btn = FlatButton('Delete') self.reflections_clear_btn = FlatButton('Clear') @@ -185,7 +188,7 @@ def style_widgets(self): self.lattice_ratio_step_txt.setMaximumWidth(60) self.reflection_table.setHorizontalHeaderLabels( - ['h', 'k', 'l', 'Intensity', 'd0', u"2θ_0", 'd', u"2θ"] + ['h', 'k', 'l', 'Intensity', 'd0', u"2θ_0", 'd', u"2θ", 'Q0', 'Q'] ) self.reflection_table.setItemDelegate(TextDoubleDelegate(self)) self.reflection_table.setShowGrid(False) @@ -287,13 +290,15 @@ def show_jcpds(self, jcpds_phase, wavelength=None): else: two_theta0 = convert_d_to_two_theta(reflection.d0, wavelength) two_theta = convert_d_to_two_theta(reflection.d, wavelength) + q0 = 2.0*np.pi/reflection.d0 + q = 2.0*np.pi/reflection.d self.add_reflection_to_table(reflection.h, reflection.k, reflection.l, reflection.intensity, reflection.d0, reflection.d, - two_theta0, two_theta) + two_theta0, two_theta, q0, q) if wavelength is None: self.reflection_table.setColumnCount(6) else: - self.reflection_table.setColumnCount(8) + self.reflection_table.setColumnCount(10) self.blockAllSignals(False) @@ -352,7 +357,7 @@ def update_spinbox_enable(self, symmetry): self.lattice_ca_sb.setEnabled(True) self.lattice_cb_sb.setEnabled(True) - elif symmetry == 'HEXAGONAL': + elif symmetry == 'HEXAGONAL' or symmetry == 'TRIGONAL': self.lattice_a_sb.setEnabled(True) self.lattice_b_sb.setEnabled(False) self.lattice_c_sb.setEnabled(True) @@ -418,7 +423,7 @@ def get_selected_reflections(self): return row def add_reflection_to_table(self, h=0., k=0., l=0., intensity=0., d0=0., d=0., two_theta_0=None, - two_theta=None): + two_theta=None, q0=None, q=None): self.reflection_table.blockSignals(True) new_row_ind = int(self.reflection_table.rowCount()) self.reflection_table.setRowCount(new_row_ind + 1) @@ -440,6 +445,14 @@ def add_reflection_to_table(self, h=0., k=0., l=0., intensity=0., d0=0., d=0., t self.reflection_table.setItem(new_row_ind, 7, CenteredNonEditableQTableWidgetItem(str('{0:.4f}'.format(two_theta)))) + if q0 is None or q is None: + pass + else: + self.reflection_table.setItem(new_row_ind, 8, + CenteredNonEditableQTableWidgetItem(str('{0:.4f}'.format(q0)))) + self.reflection_table.setItem(new_row_ind, 9, + CenteredNonEditableQTableWidgetItem(str('{0:.4f}'.format(q)))) + self.reflection_table.resizeColumnsToContents() self.reflection_table.verticalHeader().setResizeMode(QtWidgets.QHeaderView.Fixed) self.reflection_table.blockSignals(False) diff --git a/dioptas/widgets/integration/__init__.py b/dioptas/widgets/integration/__init__.py index 36765e999..ec2c20bb8 100644 --- a/dioptas/widgets/integration/__init__.py +++ b/dioptas/widgets/integration/__init__.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -57,10 +59,17 @@ def __init__(self, *args, **kwargs): self.vertical_splitter.addWidget(self.integration_pattern_widget) self.vertical_splitter.setStretchFactor(1, 99999) + + self.vertical_splitter_left = QtWidgets.QSplitter(self) + self.vertical_splitter_left.setOrientation(QtCore.Qt.Vertical) + self.vertical_splitter_left.addWidget(self.integration_image_widget) + self.horizontal_splitter = QtWidgets.QSplitter() self.horizontal_splitter.setOrientation(QtCore.Qt.Horizontal) - self.horizontal_splitter.addWidget(self.integration_image_widget) + self.horizontal_splitter.addWidget(self.vertical_splitter_left) self.horizontal_splitter.addWidget(self.vertical_splitter) + self.horizontal_splitter.addWidget(self.vertical_splitter) + self._layout.addWidget(self.horizontal_splitter, 10) self._layout.addWidget(self.integration_status_widget, 0) self.setLayout(self._layout) @@ -80,6 +89,8 @@ def __init__(self, *args, **kwargs): self.img_frame_size = QtCore.QSize(400, 500) self.img_frame_position = QtCore.QPoint(0, 0) + self.img_mode = 'Image' + def create_shortcuts(self): img_file_widget = self.integration_control_widget.img_control_widget.file_widget self.load_img_btn = img_file_widget.load_btn @@ -123,12 +134,9 @@ def create_shortcuts(self): self.phase_save_list_btn = phase_control_widget.save_list_btn self.phase_load_list_btn = phase_control_widget.load_list_btn self.phase_tw = phase_control_widget.phase_tw - self.phase_pressure_sb = phase_control_widget.pressure_sb self.phase_pressure_step_msb = phase_control_widget.pressure_step_msb - self.phase_temperature_sb = phase_control_widget.temperature_sb self.phase_temperature_step_msb = phase_control_widget.temperature_step_msb self.phase_apply_to_all_cb = phase_control_widget.apply_to_all_cb - self.phase_show_parameter_in_pattern_cb = phase_control_widget.show_in_pattern_cb overlay_control_widget = self.integration_control_widget.overlay_control_widget self.overlay_widget = overlay_control_widget @@ -138,9 +146,7 @@ def create_shortcuts(self): self.overlay_move_up_btn = overlay_control_widget.move_up_btn self.overlay_move_down_btn = overlay_control_widget.move_down_btn self.overlay_tw = overlay_control_widget.overlay_tw - self.overlay_scale_sb = overlay_control_widget.scale_sb self.overlay_scale_step_msb = overlay_control_widget.scale_step_msb - self.overlay_offset_sb = overlay_control_widget.offset_sb self.overlay_offset_step_msb = overlay_control_widget.offset_step_msb self.waterfall_separation_msb = overlay_control_widget.waterfall_separation_msb self.waterfall_btn = overlay_control_widget.waterfall_btn @@ -149,21 +155,17 @@ def create_shortcuts(self): corrections_control_widget = self.integration_control_widget.corrections_control_widget self.cbn_groupbox = corrections_control_widget.cbn_seat_gb - self.cbn_diamond_thickness_txt = corrections_control_widget.anvil_thickness_txt - self.cbn_seat_thickness_txt = corrections_control_widget.seat_thickness_txt - self.cbn_inner_seat_radius_txt = corrections_control_widget.seat_inner_radius_txt - self.cbn_outer_seat_radius_txt = corrections_control_widget.seat_outer_radius_txt - self.cbn_cell_tilt_txt = corrections_control_widget.cell_tilt_txt - self.cbn_tilt_rotation_txt = corrections_control_widget.cell_tilt_rotation_txt - self.cbn_center_offset_txt = corrections_control_widget.center_offset_txt - self.cbn_center_offset_angle_txt = corrections_control_widget.center_offset_angle_txt - self.cbn_anvil_al_txt = corrections_control_widget.anvil_absorption_length_txt - self.cbn_seat_al_txt = corrections_control_widget.seat_absorption_length_txt - self.cbn_plot_correction_btn = corrections_control_widget.cbn_seat_plot_btn + self.cbn_param_tw = corrections_control_widget.cbn_param_tw + self.cbn_plot_btn = corrections_control_widget.cbn_seat_plot_btn self.oiadac_groupbox = corrections_control_widget.oiadac_gb - self.oiadac_thickness_txt = corrections_control_widget.detector_thickness_txt - self.oiadac_abs_length_txt = corrections_control_widget.detector_absorption_length_txt + self.oiadac_param_tw = corrections_control_widget.oiadac_param_tw self.oiadac_plot_btn = corrections_control_widget.oiadac_plot_btn + self.transfer_gb = corrections_control_widget.transfer_gb + self.transfer_load_original_btn = corrections_control_widget.transfer_load_original_btn + self.transfer_load_response_btn = corrections_control_widget.transfer_load_response_btn + self.transfer_plot_btn = corrections_control_widget.transfer_plot_btn + self.transfer_original_filename_lbl = corrections_control_widget.transfer_original_filename_lbl + self.transfer_response_filename_lbl = corrections_control_widget.transfer_response_filename_lbl background_control_widget = self.integration_control_widget.background_control_widget self.bkg_image_load_btn = background_control_widget.load_image_btn @@ -180,6 +182,8 @@ def create_shortcuts(self): self.bkg_pattern_x_min_txt = background_control_widget.x_range_min_txt self.bkg_pattern_x_max_txt = background_control_widget.x_range_max_txt self.bkg_pattern_inspect_btn = background_control_widget.inspect_btn + self.bkg_pattern_save_btn = background_control_widget.save_btn + self.bkg_pattern_as_overlay_btn = background_control_widget.as_overlay options_control_widget = self.integration_control_widget.integration_options_widget self.bin_count_txt = options_control_widget.bin_count_txt @@ -204,7 +208,6 @@ def create_shortcuts(self): self.bkg_name_lbl = self.integration_status_widget.bkg_name_lbl pattern_widget = self.integration_pattern_widget - self.qa_save_img_btn = pattern_widget.save_image_btn self.qa_save_pattern_btn = pattern_widget.save_pattern_btn self.qa_set_as_overlay_btn = pattern_widget.as_overlay_btn self.qa_set_as_background_btn = pattern_widget.as_bkg_btn @@ -220,6 +223,7 @@ def create_shortcuts(self): self.pattern_widget = pattern_widget.pattern_view image_widget = self.integration_image_widget + self.qa_save_img_btn = image_widget.save_image_btn self.img_frame = image_widget self.img_roi_btn = image_widget.roi_btn self.img_mode_btn = image_widget.mode_btn @@ -249,6 +253,7 @@ def create_shortcuts(self): self.img_widget_click_azi_lbl = self.integration_image_widget.mouse_unit_widget.clicked_unit_widget.azi_lbl self.footer_img_mouse_position_widget = self.integration_status_widget.mouse_pos_widget + self.change_view_btn = self.integration_status_widget.change_view_btn def switch_to_cake(self): self.img_widget.img_view_box.setAspectLocked(False) @@ -264,6 +269,7 @@ def dock_img(self, bool_value): # save current splitter state self.horizontal_splitter_state = self.horizontal_splitter.saveState() + self.vertical_splitter_left_state = self.vertical_splitter_left.saveState() # splitter_handle = self.horizontal_splitter.handle(1) # splitter_handle.setEnabled(False) @@ -288,13 +294,14 @@ def dock_img(self, bool_value): self.frame_img_positions_widget.hide() # remove all widgets/frames from horizontal splitter to be able to arrange them in the correct order - self.img_frame.setParent(self.horizontal_splitter) + self.img_frame.setParent(self.vertical_splitter_left) - self.horizontal_splitter.addWidget(self.img_frame) - self.horizontal_splitter.addWidget(self.vertical_splitter) + self.vertical_splitter_left.insertWidget(1, self.img_frame) + # self.horizontal_splitter.addWidget(self.vertical_splitter) # restore the previously used size when image was undocked self.horizontal_splitter.restoreState(self.horizontal_splitter_state) + self.vertical_splitter_left.restoreState(self.vertical_splitter_left_state) def get_progress_dialog(self, message, abort_text, num_points): progress_dialog = QtWidgets.QProgressDialog(message, abort_text, 0, @@ -318,40 +325,3 @@ def show_error_msg(self, msg): msg_box.setStandardButtons(QtWidgets.QMessageBox.Ok) msg_box.setDefaultButton(QtWidgets.QMessageBox.Ok) msg_box.exec_() - - ############################################ - ## background parameter stuff - - def get_bkg_pattern_parameters(self): - smooth_width = float(self.bkg_pattern_smooth_width_sb.value()) - iterations = int(self.bkg_pattern_iterations_sb.value()) - polynomial_order = int(self.bkg_pattern_poly_order_sb.value()) - return smooth_width, iterations, polynomial_order - - def set_bkg_pattern_parameters(self, bkg_pattern_parameters): - self.bkg_pattern_smooth_width_sb.blockSignals(True) - self.bkg_pattern_iterations_sb.blockSignals(True) - self.bkg_pattern_poly_order_sb.blockSignals(True) - - self.bkg_pattern_smooth_width_sb.setValue(bkg_pattern_parameters[0]) - self.bkg_pattern_iterations_sb.setValue(bkg_pattern_parameters[1]) - self.bkg_pattern_poly_order_sb.setValue(bkg_pattern_parameters[2]) - - self.bkg_pattern_smooth_width_sb.blockSignals(False) - self.bkg_pattern_iterations_sb.blockSignals(False) - self.bkg_pattern_poly_order_sb.blockSignals(False) - - def get_bkg_pattern_roi(self): - x_min = float(str(self.bkg_pattern_x_min_txt.text())) - x_max = float(str(self.bkg_pattern_x_max_txt.text())) - return x_min, x_max - - def set_bkg_pattern_roi(self, roi): - self.bkg_pattern_x_max_txt.blockSignals(True) - self.bkg_pattern_x_min_txt.blockSignals(True) - - self.bkg_pattern_x_min_txt.setText('{:.3f}'.format(roi[0])) - self.bkg_pattern_x_max_txt.setText('{:.3f}'.format(roi[1])) - - self.bkg_pattern_x_max_txt.blockSignals(False) - self.bkg_pattern_x_min_txt.blockSignals(False) diff --git a/dioptas/widgets/integration/control/BackgroundWidget.py b/dioptas/widgets/integration/control/BackgroundWidget.py index 7d3898879..c931fb85a 100644 --- a/dioptas/widgets/integration/control/BackgroundWidget.py +++ b/dioptas/widgets/integration/control/BackgroundWidget.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -16,22 +18,27 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from qtpy import QtWidgets +import os +from qtpy import QtWidgets, QtCore, QtGui from ...CustomWidgets import NumberTextField, LabelAlignRight, SpinBoxAlignRight, FlatButton, \ - CheckableFlatButton, DoubleSpinBoxAlignRight, VerticalSpacerItem, HorizontalSpacerItem, \ + CheckableFlatButton, DoubleSpinBoxAlignRight, HorizontalSpacerItem, \ DoubleMultiplySpinBoxAlignRight +from .... import icons_path class BackgroundWidget(QtWidgets.QWidget): def __init__(self): super(BackgroundWidget, self).__init__() - self._layout = QtWidgets.QVBoxLayout() + self._layout = QtWidgets.QHBoxLayout() + self._layout.setContentsMargins(5, 5 , 5, 5) + self._layout.setSpacing(5) self.image_background_gb = QtWidgets.QGroupBox('Image Background', self) self._image_background_gb_layout = QtWidgets.QGridLayout(self.image_background_gb) - self._image_background_gb_layout.setSpacing(6) + self._image_background_gb_layout.setContentsMargins(5, 8, 5, 7) + self._image_background_gb_layout.setSpacing(5) self.load_image_btn = FlatButton('Load') self.filename_lbl = QtWidgets.QLabel('None') @@ -49,19 +56,19 @@ def __init__(self): self._image_background_gb_layout.addWidget(self.scale_sb, 1, 2) self._image_background_gb_layout.addWidget(self.scale_step_msb, 1, 3) self._image_background_gb_layout.addItem(HorizontalSpacerItem(), 1, 4) - self._image_background_gb_layout.addWidget(LabelAlignRight('Offset:'), 1, 5) - self._image_background_gb_layout.addWidget(self.offset_sb, 1, 6) - self._image_background_gb_layout.addWidget(self.offset_step_msb, 1, 7) - self._image_background_gb_layout.addItem(HorizontalSpacerItem(), 1, 8) + self._image_background_gb_layout.addWidget(LabelAlignRight('Offset:'), 2, 1) + self._image_background_gb_layout.addWidget(self.offset_sb, 2, 2) + self._image_background_gb_layout.addWidget(self.offset_step_msb, 2, 3) + self._image_background_gb_layout.addItem(HorizontalSpacerItem(), 2, 4) self.image_background_gb.setLayout(self._image_background_gb_layout) - self._layout.addWidget(self.image_background_gb) - self.setLayout(self._layout) self.pattern_background_gb = QtWidgets.QGroupBox('Pattern Background') - self._pattern_background_gb = QtWidgets.QGridLayout() + self._pattern_bkg_layout = QtWidgets.QGridLayout() + self._pattern_bkg_layout.setContentsMargins(5, 8, 5, 7) + self._pattern_bkg_layout.setSpacing(5) self.smooth_with_sb = DoubleSpinBoxAlignRight() self.iterations_sb = SpinBoxAlignRight() @@ -69,32 +76,39 @@ def __init__(self): self.x_range_min_txt = NumberTextField('0') self.x_range_max_txt = NumberTextField('50') self.inspect_btn = CheckableFlatButton('Inspect') - - self._smooth_layout = QtWidgets.QHBoxLayout() - self._smooth_layout.addWidget(LabelAlignRight('Smooth Width:')) - self._smooth_layout.addWidget(self.smooth_with_sb) - self._smooth_layout.addWidget(LabelAlignRight('Iterations:')) - self._smooth_layout.addWidget(self.iterations_sb) - self._smooth_layout.addWidget(LabelAlignRight('Poly Order:')) - self._smooth_layout.addWidget(self.poly_order_sb) - + self.save_btn = FlatButton() + self.as_overlay = FlatButton('As Overlay') + + self._pattern_bkg_layout.addWidget(LabelAlignRight('Smooth Width:'), 0, 0) + self._pattern_bkg_layout.addWidget(self.smooth_with_sb, 0, 1) + self._pattern_bkg_layout.addWidget(LabelAlignRight('Iterations:'), 0, 2) + self._pattern_bkg_layout.addWidget(self.iterations_sb, 0, 3) + self._pattern_bkg_layout.addItem(HorizontalSpacerItem(), 0, 4) + self._pattern_bkg_layout.addWidget(LabelAlignRight('Order:'), 0, 5) + self._pattern_bkg_layout.addWidget(self.poly_order_sb, 0, 6) + + self._pattern_bkg_layout.addWidget(LabelAlignRight('X-Range:'), 1, 0) self._range_layout = QtWidgets.QHBoxLayout() - self._range_layout.addWidget(QtWidgets.QLabel('X-Range:')) self._range_layout.addWidget(self.x_range_min_txt) self._range_layout.addWidget(QtWidgets.QLabel('-')) self._range_layout.addWidget(self.x_range_max_txt) - self._range_layout.addSpacerItem(HorizontalSpacerItem()) - - self._pattern_background_gb.addLayout(self._smooth_layout, 0, 0) - self._pattern_background_gb.addLayout(self._range_layout, 1, 0) - - self._pattern_background_gb.addWidget(self.inspect_btn, 0, 2, 2, 1) - self._pattern_background_gb.addItem(HorizontalSpacerItem(), 0, 3, 2, 1) - - self.pattern_background_gb.setLayout(self._pattern_background_gb) - - self._layout.addWidget(self.pattern_background_gb) - self._layout.addSpacerItem(VerticalSpacerItem()) + self._range_layout.addItem(HorizontalSpacerItem()) + self._pattern_bkg_layout.addLayout(self._range_layout, 1, 1, 1, 3) + self._button_layout = QtWidgets.QHBoxLayout() + self._button_layout.addStretch(1) + self._button_layout.addWidget(self.inspect_btn) + self._button_layout.addWidget(self.as_overlay) + self._button_layout.addWidget(self.save_btn) + self._pattern_bkg_layout.addLayout(self._button_layout, 2, 0, 1, 7) + + self.pattern_background_gb.setLayout(self._pattern_bkg_layout) + + self._left_layout = QtWidgets.QVBoxLayout() + self._left_layout.addWidget(self.image_background_gb) + self._left_layout.addWidget(self.pattern_background_gb) + self._left_layout.addStretch(1) + self._layout.addLayout(self._left_layout) + self._layout.addStretch(1) self.setLayout(self._layout) self.style_widgets() @@ -104,17 +118,6 @@ def style_widgets(self): self.style_pattern_background_widgets() def style_image_background_widgets(self): - step_txt_width = 70 - self.scale_step_msb.setMaximumWidth(step_txt_width) - self.scale_step_msb.setMinimumWidth(step_txt_width) - self.offset_step_msb.setMaximumWidth(step_txt_width) - - sb_width = 110 - self.scale_sb.setMaximumWidth(sb_width) - self.scale_sb.setMinimumWidth(sb_width) - self.offset_sb.setMaximumWidth(sb_width) - self.offset_sb.setMinimumWidth(sb_width) - self.scale_sb.setMinimum(-9999999) self.scale_sb.setMaximum(9999999) self.scale_sb.setValue(1) @@ -134,6 +137,13 @@ def style_image_background_widgets(self): self.offset_sb.setMaximum(999999998) self.offset_sb.setMinimum(-99999999) + self.setStyleSheet(""" + QSpinBox, QDoubleSpinBox,QLineEdit { + min-width: 50px; + max-width: 50px; + } + """) + def style_pattern_background_widgets(self): self.smooth_with_sb.setValue(0.100) self.smooth_with_sb.setMinimum(0) @@ -148,8 +158,48 @@ def style_pattern_background_widgets(self): self.poly_order_sb.setMaximum(999999) self.poly_order_sb.setMinimum(1) self.poly_order_sb.setValue(50) + self.poly_order_sb.setToolTip('Set the Polynomial order') self.x_range_min_txt.setMaximumWidth(70) self.x_range_max_txt.setMaximumWidth(70) self.inspect_btn.setMaximumHeight(150) + + self.save_btn.setToolTip("Save generated background pattern") + self.save_btn.setIcon(QtGui.QIcon(os.path.join(icons_path, 'save.ico'))) + self.save_btn.setIconSize(QtCore.QSize(13, 13)) + self.save_btn.setMaximumWidth(25) + + def get_bkg_pattern_parameters(self): + smooth_width = float(self.smooth_with_sb.value()) + iterations = int(self.iterations_sb.value()) + polynomial_order = int(self.poly_order_sb.value()) + return smooth_width, iterations, polynomial_order + + def set_bkg_pattern_parameters(self, bkg_pattern_parameters): + self.smooth_with_sb.blockSignals(True) + self.iterations_sb.blockSignals(True) + self.poly_order_sb.blockSignals(True) + + self.smooth_with_sb.setValue(bkg_pattern_parameters[0]) + self.iterations_sb.setValue(bkg_pattern_parameters[1]) + self.poly_order_sb.setValue(bkg_pattern_parameters[2]) + + self.smooth_with_sb.blockSignals(False) + self.iterations_sb.blockSignals(False) + self.poly_order_sb.blockSignals(False) + + def get_bkg_pattern_roi(self): + x_min = float(str(self.x_range_min_txt.text())) + x_max = float(str(self.x_range_max_txt.text())) + return x_min, x_max + + def set_bkg_pattern_roi(self, roi): + self.x_range_min_txt.blockSignals(True) + self.x_range_max_txt.blockSignals(True) + + self.x_range_min_txt.setText('{:.3f}'.format(roi[0])) + self.x_range_max_txt.setText('{:.3f}'.format(roi[1])) + + self.x_range_min_txt.blockSignals(False) + self.x_range_max_txt.blockSignals(False) diff --git a/dioptas/widgets/integration/control/CorrectionsWidget.py b/dioptas/widgets/integration/control/CorrectionsWidget.py index 60a66aa74..9cabdf052 100644 --- a/dioptas/widgets/integration/control/CorrectionsWidget.py +++ b/dioptas/widgets/integration/control/CorrectionsWidget.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -16,10 +18,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from qtpy import QtWidgets +from qtpy import QtWidgets, QtCore -from ...CustomWidgets import NumberTextField, LabelAlignRight, CheckableFlatButton, VerticalSpacerItem, \ - HorizontalSpacerItem +from ...CustomWidgets import NumberTextField, CheckableFlatButton, ListTableWidget, FlatButton class CorrectionsWidget(QtWidgets.QWidget): @@ -27,107 +28,244 @@ def __init__(self, *args, **kwargs): super(CorrectionsWidget, self).__init__(*args, **kwargs) self._layout = QtWidgets.QVBoxLayout() + self._layout.setContentsMargins(5, 5, 5, 5) - self.cbn_seat_gb = QtWidgets.QGroupBox('cBN Seat Correction') - self._cbn_seat_layout = QtWidgets.QGridLayout(self) - self._cbn_seat_layout.setSpacing(6) - - self.anvil_thickness_txt = NumberTextField('2.3') - self.seat_thickness_txt = NumberTextField('5.3') - self.seat_inner_radius_txt = NumberTextField('0.4') - self.seat_outer_radius_txt = NumberTextField('1.95') - self.cell_tilt_txt = NumberTextField('0.0') - self.cell_tilt_rotation_txt = NumberTextField('0.0') - self.center_offset_txt = NumberTextField('0.0') - self.center_offset_angle_txt = NumberTextField('0.0') - self.anvil_absorption_length_txt = NumberTextField('13.7') - self.seat_absorption_length_txt = NumberTextField('21.1') + self.create_cbn_correction_widgets() + self.create_cbn_correction_layout() + + self.create_oiadac_widgets() + self.create_oiadac_layout() + + self.create_transfer_widgets() + self.create_transfer_layout() + + vertical_layout_1 = QtWidgets.QHBoxLayout() + vertical_layout_1.addWidget(self.cbn_seat_gb) + vertical_layout_1.addStretch(1) + self._layout.addLayout(vertical_layout_1, 2) + + vertical_layout_2 = QtWidgets.QHBoxLayout() + vertical_layout_2.addWidget(self.oiadac_gb) + vertical_layout_2.addStretch(1) + self._layout.addLayout(vertical_layout_2, 2) + + vertical_layout_3 = QtWidgets.QHBoxLayout() + vertical_layout_3.addWidget(self.transfer_gb) + vertical_layout_3.addStretch(1) + self._layout.addLayout(vertical_layout_3, 2) + + self._layout.addStretch(1) + self.setLayout(self._layout) + self.style_widgets() + + self.hide_cbn_widgets() + self.hide_oiadac_widgets() + self.hide_transfer_widgets() + + def create_cbn_correction_widgets(self): + self.cbn_seat_gb = QtWidgets.QGroupBox('cBN Seat Correction') self.cbn_seat_plot_btn = CheckableFlatButton('Plot') - self._cbn_seat_layout.addWidget(LabelAlignRight('Anvil d:'), 0, 0) - self._cbn_seat_layout.addWidget(LabelAlignRight('Seat r1:'), 0, 4) - self._cbn_seat_layout.addWidget(LabelAlignRight('Cell Tilt:'), 0, 8) - self._cbn_seat_layout.addWidget(LabelAlignRight('Offset:'), 0, 12) - self._cbn_seat_layout.addWidget(LabelAlignRight('Anvil AL:'), 0, 16) - - self._cbn_seat_layout.addWidget(LabelAlignRight('Seat d:'), 1, 0) - self._cbn_seat_layout.addWidget(LabelAlignRight('Seat r2:'), 1, 4) - self._cbn_seat_layout.addWidget(LabelAlignRight('Tilt Rot:'), 1, 8) - self._cbn_seat_layout.addWidget(LabelAlignRight(u"Offs. 2θ :"), 1, 12) - self._cbn_seat_layout.addWidget(LabelAlignRight('Seat AL:'), 1, 16) - - self._cbn_seat_layout.addWidget(QtWidgets.QLabel('mm'), 0, 2) - self._cbn_seat_layout.addWidget(QtWidgets.QLabel('mm'), 0, 6) - self._cbn_seat_layout.addWidget(QtWidgets.QLabel('mm'), 0, 14) - self._cbn_seat_layout.addWidget(QtWidgets.QLabel('mm'), 1, 2) - self._cbn_seat_layout.addWidget(QtWidgets.QLabel('mm'), 1, 6) - self._cbn_seat_layout.addWidget(QtWidgets.QLabel(u'°'), 0, 10) - self._cbn_seat_layout.addWidget(QtWidgets.QLabel(u'°'), 1, 10) - self._cbn_seat_layout.addWidget(QtWidgets.QLabel(u'°'), 1, 14) - - self._cbn_seat_layout.addItem(HorizontalSpacerItem(3), 0, 3) - self._cbn_seat_layout.addItem(HorizontalSpacerItem(3), 0, 7) - self._cbn_seat_layout.addItem(HorizontalSpacerItem(3), 0, 11) - self._cbn_seat_layout.addItem(HorizontalSpacerItem(3), 0, 15) - - self._cbn_seat_layout.addWidget(self.anvil_thickness_txt, 0, 1) - self._cbn_seat_layout.addWidget(self.seat_thickness_txt, 1, 1) - self._cbn_seat_layout.addWidget(self.seat_inner_radius_txt, 0, 5) - self._cbn_seat_layout.addWidget(self.seat_outer_radius_txt, 1, 5) - self._cbn_seat_layout.addWidget(self.cell_tilt_txt, 0, 9) - self._cbn_seat_layout.addWidget(self.cell_tilt_rotation_txt, 1, 9) - self._cbn_seat_layout.addWidget(self.center_offset_txt, 0, 13) - self._cbn_seat_layout.addWidget(self.center_offset_angle_txt, 1, 13) - self._cbn_seat_layout.addWidget(self.anvil_absorption_length_txt, 0, 17) - self._cbn_seat_layout.addWidget(self.seat_absorption_length_txt, 1, 17) - - self._cbn_seat_layout.addWidget(self.cbn_seat_plot_btn, 0, 18, 2, 1) + self.cbn_param_tw = ListTableWidget() + self.cbn_param_tw.setColumnCount(3) + + self.cbn_param_tw.horizontalHeader().setResizeMode(1, QtWidgets.QHeaderView.Stretch) + self.cbn_param_tw.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) + + cbn_parameters = [ + ['Anvil thickness', 2.3, 'mm'], + ['Seat thickness', 5.3, 'mm'], + ['Inner seat radius', 0.4, 'mm'], + ['Outer seat radius', 1.95, 'mm'], + ['Cell tilt', 0.0, u'°'], + ['Cell tilt rotation', 0, u'°'], + ['Center offset', 0, 'mm'], + ['Center offset rotation', 0, u'°'], + ['Anvil absorption length', 13.7, 'mm'], + ['Seat absorption length', 12, 'mm'], + ] + + for cbn_parameter in cbn_parameters: + self.add_param_to_tw(self.cbn_param_tw, *cbn_parameter) + + @staticmethod + def add_param_to_tw(tw, name, value, unit): + tw.blockSignals(True) + new_row_ind = int(tw.rowCount()) + tw.setRowCount(new_row_ind + 1) + + name_item = QtWidgets.QTableWidgetItem(name + ':') + name_item.setFlags(name_item.flags() & ~QtCore.Qt.ItemIsEditable) + name_item.setTextAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + tw.setItem(new_row_ind, 0, name_item) + + value_item = NumberTextField('{:g}'.format(value)) + tw.setCellWidget(new_row_ind, 1, value_item) + + unit_item = QtWidgets.QTableWidgetItem(unit) + unit_item.setFlags(name_item.flags() & ~QtCore.Qt.ItemIsEditable) + unit_item.setTextAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter) + tw.setItem(new_row_ind, 2, unit_item) + + tw.resizeColumnToContents(0) + tw.resizeColumnToContents(2) + + tw.blockSignals(False) + + def create_cbn_correction_layout(self): + self._cbn_seat_layout = QtWidgets.QHBoxLayout() + self._cbn_seat_layout.setSpacing(5) + + self._cbn_seat_layout.addWidget(self.cbn_param_tw) + + self._cbn_seat_right_layout = QtWidgets.QVBoxLayout() + self._cbn_seat_right_layout.addWidget(self.cbn_seat_plot_btn) + self._cbn_seat_right_layout.addStretch() + self._cbn_seat_layout.addLayout(self._cbn_seat_right_layout) self.cbn_seat_gb.setLayout(self._cbn_seat_layout) - self.oiadac_gb = QtWidgets.QGroupBox('Oblique Incidence Angle Detector Absorption Correction') - self._oiadac_layout = QtWidgets.QHBoxLayout() + def create_oiadac_widgets(self): + self.oiadac_gb = QtWidgets.QGroupBox('Detector Incidence Absorption Correction') + + self.oiadac_param_tw = ListTableWidget() + self.oiadac_param_tw.setColumnCount(3) + + self.oiadac_param_tw.horizontalHeader().setResizeMode(1, QtWidgets.QHeaderView.Stretch) + self.oiadac_param_tw.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) self.detector_thickness_txt = NumberTextField('40') self.detector_absorption_length_txt = NumberTextField('465.5') + + oiadac_parameters = [ + ['Detector thickness', 40, 'mm'], + ['Detector absorption length', 465.5, 'um'], + ] + + for param in oiadac_parameters: + self.add_param_to_tw(self.oiadac_param_tw, *param) + self.oiadac_plot_btn = CheckableFlatButton('Plot') - self._oiadac_layout.addWidget(LabelAlignRight('Det. Thickness:')) - self._oiadac_layout.addWidget(self.detector_thickness_txt) - self._oiadac_layout.addWidget(QtWidgets.QLabel('mm')) - self._oiadac_layout.addSpacing(10) - self._oiadac_layout.addWidget(LabelAlignRight('Abs. Length:')) - self._oiadac_layout.addWidget(self.detector_absorption_length_txt) - self._oiadac_layout.addWidget(QtWidgets.QLabel('um')) - self._oiadac_layout.addWidget(self.oiadac_plot_btn) - self._oiadac_layout.addSpacerItem(HorizontalSpacerItem()) + def create_oiadac_layout(self): + self._oiadac_layout = QtWidgets.QHBoxLayout() + self._oiadac_layout.setSpacing(5) + + self._oiadac_layout.addWidget(self.oiadac_param_tw) + + self._oiadac_right_layout = QtWidgets.QVBoxLayout() + self._oiadac_right_layout.addWidget(self.oiadac_plot_btn) + self._oiadac_right_layout.addStretch() + self._oiadac_layout.addLayout(self._oiadac_right_layout) self.oiadac_gb.setLayout(self._oiadac_layout) - self._layout.addWidget(self.cbn_seat_gb) - self._layout.addWidget(self.oiadac_gb) - self._layout.addSpacerItem(VerticalSpacerItem()) + def create_transfer_widgets(self): + self.transfer_gb = QtWidgets.QGroupBox('Transfer Correction') + self.transfer_load_original_btn = FlatButton('Load Original') + self.transfer_load_response_btn = FlatButton('Load Response') + self.transfer_original_filename_lbl = QtWidgets.QLabel('None') + self.transfer_response_filename_lbl = QtWidgets.QLabel('None') + self.transfer_plot_btn = CheckableFlatButton('Plot') - self.setLayout(self._layout) - self.style_widgets() + def create_transfer_layout(self): + self._transfer_layout = QtWidgets.QGridLayout() + self._transfer_layout.setSpacing(5) + self._transfer_layout.addWidget(self.transfer_load_original_btn, 0, 0) + self._transfer_layout.addWidget(self.transfer_load_response_btn, 1, 0) + self._transfer_layout.addWidget(self.transfer_original_filename_lbl, 0, 1) + self._transfer_layout.addWidget(self.transfer_response_filename_lbl, 1, 1) + self._transfer_layout.addWidget(self.transfer_plot_btn, 0, 2) + self.transfer_gb.setLayout(self._transfer_layout) def style_widgets(self): self.cbn_seat_gb.setCheckable(True) self.cbn_seat_gb.setChecked(False) - - self.setStyleSheet(""" - QLineEdit { - min-width: 50 px; - max-width: 60 px; - } - """) - - self.cbn_seat_plot_btn.setMaximumHeight(150) - self.oiadac_plot_btn.setMaximumHeight(150) self.oiadac_gb.setCheckable(True) self.oiadac_gb.setChecked(False) - self.detector_thickness_txt.setMinimumWidth(60) - self.detector_thickness_txt.setMaximumWidth(60) - self.detector_absorption_length_txt.setMinimumWidth(60) - self.detector_absorption_length_txt.setMaximumWidth(60) + self.transfer_gb.setCheckable(True) + self.transfer_gb.setChecked(False) + + self.setStyleSheet(""" + QLineEdit { + min-width: 50 px; + min-height: 26 px; + max-height: 26 px; + } + + QPushButton { + min-width: 50 px; + max-width: 90 px; + min-height: 30 px; + max-height: 30 px; + } + """) + + self.oiadac_param_tw.setMinimumWidth(280) + self.cbn_param_tw.setMinimumWidth(280) + + self.oiadac_param_tw.setMinimumHeight(65) + self.oiadac_param_tw.setMaximumHeight(80) + + self.cbn_param_tw.setMaximumHeight(500) + + self.cbn_seat_gb.setMinimumWidth(380) + self.oiadac_gb.setMinimumWidth(380) + self.transfer_gb.setMinimumWidth(380) + self.transfer_plot_btn.setMinimumWidth(35) + self.transfer_plot_btn.setMaximumWidth(35) + + def hide_cbn_widgets(self): + self.cbn_seat_plot_btn.hide() + self.cbn_param_tw.hide() + self.cbn_seat_gb.setMaximumHeight(20) + + def show_cbn_widgets(self): + self.cbn_seat_plot_btn.show() + self.cbn_param_tw.show() + self.cbn_seat_gb.setMaximumHeight(999999) + + def hide_oiadac_widgets(self): + self.oiadac_plot_btn.hide() + self.oiadac_param_tw.hide() + self.oiadac_gb.setMaximumHeight(20) + + def show_oiadac_widgets(self): + self.oiadac_plot_btn.show() + self.oiadac_param_tw.show() + self.oiadac_gb.setMaximumHeight(999999) + + def hide_transfer_widgets(self): + self.transfer_plot_btn.hide() + self.transfer_original_filename_lbl.hide() + self.transfer_load_original_btn.hide() + self.transfer_response_filename_lbl.hide() + self.transfer_load_response_btn.hide() + self.transfer_gb.setMaximumHeight(20) + + def show_transfer_widgets(self): + self.transfer_plot_btn.show() + self.transfer_original_filename_lbl.show() + self.transfer_load_original_btn.show() + self.transfer_response_filename_lbl.show() + self.transfer_load_response_btn.show() + self.transfer_gb.setMaximumHeight(99999) + + def toggle_cbn_widget_visibility(self, flag): + if flag: + self.show_cbn_widgets() + else: + self.hide_cbn_widgets() + + def toggle_oiadac_widget_visibility(self, flag): + if flag: + self.show_oiadac_widgets() + else: + self.hide_oiadac_widgets() + + def toggle_transfer_widget_visibility(self, flag): + if flag: + self.show_transfer_widgets() + else: + self.hide_transfer_widgets() diff --git a/dioptas/widgets/integration/control/ImageWidget.py b/dioptas/widgets/integration/control/ImageWidget.py index 6cbb224ab..bfe5b0b25 100644 --- a/dioptas/widgets/integration/control/ImageWidget.py +++ b/dioptas/widgets/integration/control/ImageWidget.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -44,24 +46,26 @@ def _create_widgets(self): def _create_layout(self): self._layout = QtWidgets.QVBoxLayout() + self._layout.setContentsMargins(5, 0, 5, 5) + self._layout.setSpacing(5) self._layout.addWidget(self.file_widget) - self._layout.addWidget(HorizontalLine()) - - self._file_info_layout = QtWidgets.QHBoxLayout() - self._file_info_layout.addWidget(self.file_info_btn) - self._file_info_layout.addWidget(self.move_btn) - self._file_info_layout.addSpacerItem(HorizontalSpacerItem()) self._batch_layout = QtWidgets.QHBoxLayout() - self._batch_layout.addWidget(self.batch_mode_lbl) self._batch_layout.addWidget(self.batch_mode_integrate_rb) self._batch_layout.addWidget(self.batch_mode_add_rb) self._batch_layout.addWidget(self.batch_mode_image_save_rb) + self._batch_layout.addItem(HorizontalSpacerItem()) self.batch_mode_widget.setLayout(self._batch_layout) + self._layout.addWidget(self.batch_mode_widget) - self._file_info_layout.addWidget(self.batch_mode_widget) + self._layout.addWidget(HorizontalLine()) + + self._file_info_layout = QtWidgets.QHBoxLayout() + self._file_info_layout.addWidget(self.file_info_btn) + self._file_info_layout.addWidget(self.move_btn) + self._file_info_layout.addSpacerItem(HorizontalSpacerItem()) self._layout.addLayout(self._file_info_layout) self._layout.addSpacerItem(VerticalSpacerItem()) diff --git a/dioptas/widgets/integration/control/OptionsWidget.py b/dioptas/widgets/integration/control/OptionsWidget.py index ba7cfceab..3931960a4 100644 --- a/dioptas/widgets/integration/control/OptionsWidget.py +++ b/dioptas/widgets/integration/control/OptionsWidget.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -16,20 +18,39 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from qtpy import QtWidgets +from qtpy import QtWidgets, QtGui, QtCore -from ...CustomWidgets import IntegerTextField, LabelAlignRight, SpinBoxAlignRight, VerticalSpacerItem, \ - HorizontalSpacerItem +from ...CustomWidgets import IntegerTextField, NumberTextField, LabelAlignRight, SpinBoxAlignRight, VerticalSpacerItem, \ + HorizontalSpacerItem, ConservativeSpinBox, CheckableFlatButton class OptionsWidget(QtWidgets.QWidget): def __init__(self): super(OptionsWidget, self).__init__() - self._layout = QtWidgets.QVBoxLayout() + self.create_integration_gb() + self.create_cake_gb() - self.integration_gb = QtWidgets.QGroupBox('Integration') + self.style_integration_widgets() + self.style_cake_widgets() + + self._layout = QtWidgets.QHBoxLayout() + self._layout.setContentsMargins(5, 5, 5, 5) + self._layout.setSpacing(5) + + self._left_layout = QtWidgets.QVBoxLayout() + self._left_layout.addWidget(self.integration_gb) + self._left_layout.addWidget(self.cake_gb) + self._left_layout.addStretch(1) + self._layout.addLayout(self._left_layout) + self._layout.addStretch(1) + self.setLayout(self._layout) + + def create_integration_gb(self): + self.integration_gb = QtWidgets.QGroupBox('1D integration') self._integration_gb_layout = QtWidgets.QGridLayout() + self._integration_gb_layout.setContentsMargins(5, 8, 5, 7) + self._integration_gb_layout.setSpacing(5) self.bin_count_txt = IntegerTextField('0') self.bin_count_cb = QtWidgets.QCheckBox('auto') @@ -37,7 +58,7 @@ def __init__(self): self.correct_solid_angle_cb = QtWidgets.QCheckBox('correct Solid Angle') self.correct_solid_angle_cb.setChecked(True) - self._integration_gb_layout.addWidget(LabelAlignRight('Number of Bins:'), 0, 0) + self._integration_gb_layout.addWidget(LabelAlignRight('Radial bins:'), 0, 0) self._integration_gb_layout.addWidget(LabelAlignRight('Supersampling:'), 1, 0) self._integration_gb_layout.addWidget(self.bin_count_txt, 0, 1) @@ -47,17 +68,30 @@ def __init__(self): self.integration_gb.setLayout(self._integration_gb_layout) - self._integration_layout = QtWidgets.QHBoxLayout() - self._integration_layout.addWidget(self.integration_gb) - self._integration_layout.addSpacerItem(HorizontalSpacerItem()) - - self._layout.addLayout(self._integration_layout) - self._layout.addItem(VerticalSpacerItem()) - - self.setLayout(self._layout) - self.style_widgets() - - def style_widgets(self): + def create_cake_gb(self): + self.cake_gb = QtWidgets.QGroupBox('2D (Cake-) integration') + self._cake_gb_layout = QtWidgets.QGridLayout() + self._cake_gb_layout.setContentsMargins(5, 8, 5, 7) + self._cake_gb_layout.setSpacing(5) + + self.cake_azimuth_points_sb = ConservativeSpinBox() + self.cake_azimuth_min_txt = NumberTextField('-180') + self.cake_azimuth_max_txt = NumberTextField('180') + self.cake_full_toggle_btn = CheckableFlatButton('Full available range') + + self._cake_gb_layout.addWidget(LabelAlignRight('Azimuth bins:'), 0, 0) + self._cake_gb_layout.addWidget(self.cake_azimuth_points_sb, 0, 1) + self._cake_gb_layout.addWidget(LabelAlignRight('Azimuth range:'), 1, 0) + self._azi_range_layout = QtWidgets.QHBoxLayout() + self._azi_range_layout.addWidget(self.cake_azimuth_min_txt) + self._azi_range_separater_lbl = LabelAlignRight('-') + self._azi_range_layout.addWidget(self._azi_range_separater_lbl) + self._azi_range_layout.addWidget(self.cake_azimuth_max_txt) + self._cake_gb_layout.addLayout(self._azi_range_layout, 1, 1) + self._cake_gb_layout.addWidget(self.cake_full_toggle_btn, 2, 1) + self.cake_gb.setLayout(self._cake_gb_layout) + + def style_integration_widgets(self): max_width = 110 self.bin_count_txt.setMaximumWidth(max_width) self.supersampling_sb.setMaximumWidth(max_width) @@ -68,3 +102,22 @@ def style_widgets(self): self.bin_count_txt.setEnabled(False) self.bin_count_cb.setChecked(True) + + def style_cake_widgets(self): + self.cake_azimuth_points_sb.setMaximumWidth(115) + self.cake_azimuth_points_sb.setMinimum(1) + self.cake_azimuth_points_sb.setMaximum(10000) + self.cake_azimuth_points_sb.setSingleStep(100) + + self.cake_azimuth_min_txt.setMinimumWidth(50) + self.cake_azimuth_min_txt.setMaximumWidth(50) + self.cake_azimuth_max_txt.setMinimumWidth(50) + self.cake_azimuth_max_txt.setMaximumWidth(50) + self._azi_range_separater_lbl.setMaximumWidth(5) + self._azi_range_layout.setSpacing(0) + self._azi_range_layout.setContentsMargins(0, 0, 0, 0) + + self.cake_full_toggle_btn.setChecked(True) + self.cake_azimuth_min_txt.setDisabled(True) + self.cake_azimuth_max_txt.setDisabled(True) + diff --git a/dioptas/widgets/integration/control/OverlayWidget.py b/dioptas/widgets/integration/control/OverlayWidget.py index da3c94f1e..cb501abbc 100644 --- a/dioptas/widgets/integration/control/OverlayWidget.py +++ b/dioptas/widgets/integration/control/OverlayWidget.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -17,112 +19,172 @@ # along with this program. If not, see . from functools import partial +import os -from qtpy import QtWidgets, QtCore +from qtpy import QtWidgets, QtCore, QtGui from ...CustomWidgets import LabelAlignRight, FlatButton, CheckableFlatButton, DoubleSpinBoxAlignRight, \ - VerticalSpacerItem, HorizontalSpacerItem, ListTableWidget, DoubleMultiplySpinBoxAlignRight + VerticalSpacerItem, HorizontalSpacerItem, ListTableWidget, DoubleMultiplySpinBoxAlignRight, HorizontalLine from ...CustomWidgets import NoRectDelegate +from .... import icons_path class OverlayWidget(QtWidgets.QWidget): color_btn_clicked = QtCore.Signal(int, QtWidgets.QWidget) show_cb_state_changed = QtCore.Signal(int, bool) name_changed = QtCore.Signal(int, str) + scale_sb_value_changed = QtCore.Signal(int, float) + offset_sb_value_changed = QtCore.Signal(int, float) def __init__(self): super(OverlayWidget, self).__init__() - self._layout = QtWidgets.QVBoxLayout() + self._layout = QtWidgets.QHBoxLayout() + self._layout.setContentsMargins(5, 5, 5, 5) + self._layout.setSpacing(5) self.button_widget = QtWidgets.QWidget(self) self.button_widget.setObjectName('overlay_control_widget') - self._button_layout = QtWidgets.QHBoxLayout(self.button_widget) + self._button_layout = QtWidgets.QVBoxLayout(self.button_widget) self._button_layout.setContentsMargins(0, 0, 0, 0) self._button_layout.setSpacing(6) - self.add_btn = FlatButton('Add') - self.delete_btn = FlatButton('Delete') - self.clear_btn = FlatButton('Clear') - self.move_up_btn = FlatButton('Move Up') - self.move_down_btn = FlatButton('Move Down') + self.add_btn = FlatButton() + self.delete_btn = FlatButton() + self.clear_btn = FlatButton() + self.move_up_btn = FlatButton() + self.move_down_btn = FlatButton() self._button_layout.addWidget(self.add_btn) self._button_layout.addWidget(self.delete_btn) + self._button_layout.addWidget(HorizontalLine()) + self._button_layout.addWidget(HorizontalLine()) + self._button_layout.addSpacerItem(VerticalSpacerItem()) self._button_layout.addWidget(self.clear_btn) + self._button_layout.addSpacerItem(VerticalSpacerItem()) + self._button_layout.addWidget(HorizontalLine()) + self._button_layout.addWidget(HorizontalLine()) self._button_layout.addWidget(self.move_up_btn) self._button_layout.addWidget(self.move_down_btn) - self._button_layout.addSpacerItem(HorizontalSpacerItem()) self._layout.addWidget(self.button_widget) self.parameter_widget = QtWidgets.QWidget(self) - self._parameter_layout = QtWidgets.QGridLayout(self.parameter_widget) - self._parameter_layout.setSpacing(6) + self._parameter_layout = QtWidgets.QVBoxLayout() + self._parameter_layout.setContentsMargins(0, 0, 0, 0) + self._parameter_layout.setSpacing(5) - self.scale_sb = DoubleSpinBoxAlignRight() - self.offset_sb = DoubleSpinBoxAlignRight() self.scale_step_msb = DoubleMultiplySpinBoxAlignRight() self.offset_step_msb = DoubleMultiplySpinBoxAlignRight() + + self._step_gb = QtWidgets.QWidget() + self._step_layout = QtWidgets.QVBoxLayout() + self._step_layout.setContentsMargins(0, 0, 0, 0) + self._step_layout.setSpacing(4) + self._step_layout.addWidget(QtWidgets.QLabel('Scale Step')) + self._step_layout.addWidget(self.scale_step_msb) + self._step_layout.addWidget(QtWidgets.QLabel('Offset Step')) + self._step_layout.addWidget(self.offset_step_msb) + self._step_gb.setLayout(self._step_layout) + self._parameter_layout.addWidget(self._step_gb) + self._parameter_layout.addWidget(HorizontalLine()) + + self._waterfall_gb = QtWidgets.QWidget() self.waterfall_separation_msb = DoubleMultiplySpinBoxAlignRight() self.waterfall_btn = FlatButton('Waterfall') self.waterfall_reset_btn = FlatButton('Reset') - self.set_as_bkg_btn = CheckableFlatButton('Set as Background') - - self._parameter_layout.addWidget(QtWidgets.QLabel('Step'), 0, 2) - self._parameter_layout.addWidget(LabelAlignRight('Scale:'), 1, 0) - self._parameter_layout.addWidget(LabelAlignRight('Offset:'), 2, 0) - - self._parameter_layout.addWidget(self.scale_sb, 1, 1) - self._parameter_layout.addWidget(self.scale_step_msb, 1, 2) - self._parameter_layout.addWidget(self.offset_sb, 2, 1) - self._parameter_layout.addWidget(self.offset_step_msb, 2, 2) - - self._parameter_layout.addItem(VerticalSpacerItem(), 3, 0, 1, 3) - self._waterfall_layout = QtWidgets.QHBoxLayout() + self._waterfall_layout = QtWidgets.QVBoxLayout() + self._waterfall_layout.setContentsMargins(0, 0, 0, 0) + self._waterfall_layout.setSpacing(4) self._waterfall_layout.addWidget(self.waterfall_btn) self._waterfall_layout.addWidget(self.waterfall_separation_msb) self._waterfall_layout.addWidget(self.waterfall_reset_btn) - self._parameter_layout.addLayout(self._waterfall_layout, 4, 0, 1, 3) - self._parameter_layout.addItem(VerticalSpacerItem(), 5, 0, 1, 3) + self._waterfall_gb.setLayout(self._waterfall_layout) + self._parameter_layout.addWidget(self._waterfall_gb) + self._parameter_layout.addWidget(HorizontalLine()) + self._parameter_layout.addItem(VerticalSpacerItem()) + + self.set_as_bkg_btn = CheckableFlatButton('Set as\nBackground') self._background_layout = QtWidgets.QHBoxLayout() + self._background_layout.setContentsMargins(0, 0, 0, 0) self._background_layout.addSpacerItem(HorizontalSpacerItem()) self._background_layout.addWidget(self.set_as_bkg_btn) - self._parameter_layout.addLayout(self._background_layout, 6, 0, 1, 3) + self._parameter_layout.addLayout(self._background_layout) + self.parameter_widget.setLayout(self._parameter_layout) - self._body_layout = QtWidgets.QHBoxLayout() - self.overlay_tw = ListTableWidget(columns=3) - self._body_layout.addWidget(self.overlay_tw, 10) - self._body_layout.addWidget(self.parameter_widget, 0) + self.overlay_tw = ListTableWidget(columns=5) + self.overlay_tw.setObjectName('overlay_table_widget') + self.overlay_tw.setHorizontalHeaderLabels(['', '', 'Name', 'Scale', 'Offset']) + self.overlay_tw.horizontalHeader().setVisible(True) + self.overlay_tw.horizontalHeader().setStretchLastSection(False) + self.overlay_tw.horizontalHeader().setResizeMode(2, QtWidgets.QHeaderView.Stretch) + self.overlay_tw.horizontalHeader().setResizeMode(3, QtWidgets.QHeaderView.ResizeToContents) + self.overlay_tw.horizontalHeader().setResizeMode(4, QtWidgets.QHeaderView.ResizeToContents) - self._layout.addLayout(self._body_layout) + self.overlay_tw.setColumnWidth(0, 20) + self.overlay_tw.setColumnWidth(1, 25) + self.overlay_tw.cellChanged.connect(self.label_editingFinished) + self.overlay_tw.setItemDelegate(NoRectDelegate()) - self.setLayout(self._layout) + self._layout.addWidget(self.overlay_tw, 10) + self._layout.addWidget(self.parameter_widget, 0) + + # label for alternative view: + self.overlay_header_btn = FlatButton('Overlay') + self.overlay_header_btn.setEnabled(False) + self.overlay_header_btn.setVisible(False) + self._main_layout = QtWidgets.QVBoxLayout() + self._main_layout.setContentsMargins(0, 0, 0, 0) + self._main_layout.addWidget(self.overlay_header_btn) + self._main_layout.addLayout(self._layout) + self.setLayout(self._main_layout) self.style_widgets() + self.add_tooltips() - self.overlay_tw.cellChanged.connect(self.label_editingFinished) - self.overlay_tw.setItemDelegate(NoRectDelegate()) self.show_cbs = [] self.color_btns = [] + self.scale_sbs = [] + self.offset_sbs = [] def style_widgets(self): + icon_size = QtCore.QSize(17, 17) + self.clear_btn.setIcon(QtGui.QIcon(os.path.join(icons_path, 'reset_dark.ico'))) + self.clear_btn.setIconSize(icon_size) + + self.add_btn.setIcon(QtGui.QIcon(os.path.join(icons_path, 'open.ico'))) + self.add_btn.setIconSize(icon_size) + + self.delete_btn.setIcon(QtGui.QIcon(os.path.join(icons_path, 'delete.png'))) + self.delete_btn.setIconSize(QtCore.QSize(12, 14)) + + self.move_up_btn.setIcon(QtGui.QIcon(os.path.join(icons_path, 'arrow_up.ico'))) + self.move_up_btn.setIconSize(icon_size) + + self.move_down_btn.setIcon(QtGui.QIcon(os.path.join(icons_path, 'arrow_down.ico'))) + self.move_down_btn.setIconSize(icon_size) + + def modify_btn_to_icon_size(btn): + button_height = 25 + button_width = 25 + btn.setMinimumHeight(button_height) + btn.setMaximumHeight(button_height) + btn.setMinimumWidth(button_width) + btn.setMaximumWidth(button_width) + + modify_btn_to_icon_size(self.add_btn) + modify_btn_to_icon_size(self.delete_btn) + modify_btn_to_icon_size(self.clear_btn) + modify_btn_to_icon_size(self.move_up_btn) + modify_btn_to_icon_size(self.move_down_btn) + step_txt_width = 70 self.scale_step_msb.setMaximumWidth(step_txt_width) self.scale_step_msb.setMinimumWidth(step_txt_width) self.offset_step_msb.setMaximumWidth(step_txt_width) self.waterfall_separation_msb.setMaximumWidth(step_txt_width) - self.scale_sb.setMinimum(-9999999) - self.scale_sb.setMaximum(9999999) - self.scale_sb.setValue(1) - self.scale_sb.setSingleStep(0.01) - - self.offset_sb.setMaximum(999999998) - self.offset_sb.setMinimum(-99999999) - self.offset_sb.setSingleStep(100) - self.scale_step_msb.setMaximum(10.0) self.scale_step_msb.setMinimum(0.01) self.scale_step_msb.setValue(0.01) @@ -135,15 +197,19 @@ def style_widgets(self): self.waterfall_separation_msb.setMinimum(0.01) self.waterfall_separation_msb.setValue(100.0) - self.setStyleSheet(""" - #overlay_control_widget QPushButton { - min-width: 95; - } - QSpinBox { - min-width: 110; - max-width: 110; - } - """) + self.set_as_bkg_btn.setStyleSheet('font-size: 11px') + self.waterfall_btn.setMaximumWidth(step_txt_width) + self.waterfall_reset_btn.setMaximumWidth(step_txt_width) + + self.set_as_bkg_btn.setMinimumHeight(40) + self.set_as_bkg_btn.setMaximumHeight(40) + + self.overlay_header_btn.setStyleSheet("border-radius: 0px") + + def add_tooltips(self): + self.add_btn.setToolTip('Loads Overlay(s) from file(s)') + self.delete_btn.setToolTip('Removes currently selected overlay') + self.clear_btn.setToolTip('Removes all overlays') def add_overlay(self, name, color): current_rows = self.overlay_tw.rowCount() @@ -167,9 +233,31 @@ def add_overlay(self, name, color): name_item.setFlags(name_item.flags() & ~QtCore.Qt.ItemIsEditable) self.overlay_tw.setItem(current_rows, 2, QtWidgets.QTableWidgetItem(name)) + scale_sb = DoubleSpinBoxAlignRight() + scale_sb.setMinimum(-9999999) + scale_sb.setMaximum(9999999) + scale_sb.setValue(1) + scale_sb.setSingleStep(self.scale_step_msb.value()) + scale_sb.valueChanged.connect(partial(self.scale_sb_callback, scale_sb)) + self.overlay_tw.setCellWidget(current_rows, 3, scale_sb) + self.scale_sbs.append(scale_sb) + + offset_sb = DoubleSpinBoxAlignRight() + offset_sb.setMinimum(-9999999) + offset_sb.setMaximum(9999999) + offset_sb.setValue(0) + offset_sb.setSingleStep(self.offset_step_msb.value()) + offset_sb.valueChanged.connect(partial(self.offset_sb_callback, offset_sb)) + self.overlay_tw.setCellWidget(current_rows, 4, offset_sb) + self.offset_sbs.append(offset_sb) + self.overlay_tw.setColumnWidth(0, 20) self.overlay_tw.setColumnWidth(1, 25) self.overlay_tw.setRowHeight(current_rows, 25) + + self.overlay_tw.horizontalHeader().setResizeMode(3, QtWidgets.QHeaderView.ResizeToContents) + self.overlay_tw.horizontalHeader().setResizeMode(4, QtWidgets.QHeaderView.ResizeToContents) + self.select_overlay(current_rows) self.overlay_tw.blockSignals(False) @@ -191,6 +279,8 @@ def remove_overlay(self, ind): self.overlay_tw.blockSignals(False) del self.show_cbs[ind] del self.color_btns[ind] + del self.offset_sbs[ind] + del self.scale_sbs[ind] if self.overlay_tw.rowCount() > ind: self.select_overlay(ind) @@ -203,6 +293,8 @@ def move_overlay_up(self, ind): self.overlay_tw.insertRow(new_ind) self.overlay_tw.setCellWidget(new_ind, 0, self.overlay_tw.cellWidget(ind + 1, 0)) self.overlay_tw.setCellWidget(new_ind, 1, self.overlay_tw.cellWidget(ind + 1, 1)) + self.overlay_tw.setCellWidget(new_ind, 3, self.overlay_tw.cellWidget(ind + 1, 3)) + self.overlay_tw.setCellWidget(new_ind, 4, self.overlay_tw.cellWidget(ind + 1, 4)) self.overlay_tw.setItem(new_ind, 2, self.overlay_tw.takeItem(ind + 1, 2)) self.overlay_tw.setCurrentCell(new_ind, 2) self.overlay_tw.removeRow(ind + 1) @@ -211,6 +303,8 @@ def move_overlay_up(self, ind): self.color_btns.insert(new_ind, self.color_btns.pop(ind)) self.show_cbs.insert(new_ind, self.show_cbs.pop(ind)) + self.scale_sbs.insert(new_ind, self.scale_sbs.pop(ind)) + self.offset_sbs.insert(new_ind, self.offset_sbs.pop(ind)) def move_overlay_down(self, ind): new_ind = ind + 2 @@ -218,6 +312,8 @@ def move_overlay_down(self, ind): self.overlay_tw.insertRow(new_ind) self.overlay_tw.setCellWidget(new_ind, 0, self.overlay_tw.cellWidget(ind, 0)) self.overlay_tw.setCellWidget(new_ind, 1, self.overlay_tw.cellWidget(ind, 1)) + self.overlay_tw.setCellWidget(new_ind, 3, self.overlay_tw.cellWidget(ind, 3)) + self.overlay_tw.setCellWidget(new_ind, 4, self.overlay_tw.cellWidget(ind, 4)) self.overlay_tw.setItem(new_ind, 2, self.overlay_tw.takeItem(ind, 2)) self.overlay_tw.setCurrentCell(new_ind, 2) self.overlay_tw.setRowHeight(new_ind, 25) @@ -226,6 +322,8 @@ def move_overlay_down(self, ind): self.color_btns.insert(ind + 1, self.color_btns.pop(ind)) self.show_cbs.insert(ind + 1, self.show_cbs.pop(ind)) + self.scale_sbs.insert(ind + 1, self.scale_sbs.pop(ind)) + self.offset_sbs.insert(ind + 1, self.offset_sbs.pop(ind)) def color_btn_click(self, button): self.color_btn_clicked.emit(self.color_btns.index(button), button) @@ -244,3 +342,9 @@ def show_cb_is_checked(self, ind): def label_editingFinished(self, row, col): label_item = self.overlay_tw.item(row, col) self.name_changed.emit(row, str(label_item.text())) + + def scale_sb_callback(self, scale_sb): + self.scale_sb_value_changed.emit(self.scale_sbs.index(scale_sb), scale_sb.value()) + + def offset_sb_callback(self, offset_sb): + self.offset_sb_value_changed.emit(self.offset_sbs.index(offset_sb), offset_sb.value()) diff --git a/dioptas/widgets/integration/control/PatternWidget.py b/dioptas/widgets/integration/control/PatternWidget.py index cb98086ed..049456e6a 100644 --- a/dioptas/widgets/integration/control/PatternWidget.py +++ b/dioptas/widgets/integration/control/PatternWidget.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -27,6 +29,8 @@ def __init__(self): super(PatternWidget, self).__init__() self._layout = QtWidgets.QVBoxLayout() + self._layout.setContentsMargins(5, 0, 5, 5) + self._layout.setSpacing(5) self.file_widget = BrowseFileWidget(files='Pattern', checkbox_text='autocreate') diff --git a/dioptas/widgets/integration/control/PhaseWidget.py b/dioptas/widgets/integration/control/PhaseWidget.py index a416cd0f3..6ed01a3ba 100644 --- a/dioptas/widgets/integration/control/PhaseWidget.py +++ b/dioptas/widgets/integration/control/PhaseWidget.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -17,129 +19,169 @@ # along with this program. If not, see . from functools import partial +import os -from qtpy import QtWidgets, QtCore +from qtpy import QtWidgets, QtCore, QtGui from ...CustomWidgets import FlatButton, DoubleSpinBoxAlignRight, VerticalSpacerItem, NoRectDelegate, \ - HorizontalSpacerItem, ListTableWidget, VerticalLine, DoubleMultiplySpinBoxAlignRight + ListTableWidget, HorizontalLine, DoubleMultiplySpinBoxAlignRight +from .... import icons_path -class PhaseWidget(QtWidgets.QWidget): +class PhaseWidget(QtWidgets.QWidget): color_btn_clicked = QtCore.Signal(int, QtWidgets.QWidget) show_cb_state_changed = QtCore.Signal(int, bool) + pressure_sb_value_changed = QtCore.Signal(int, float) + temperature_sb_value_changed = QtCore.Signal(int, float) + def __init__(self): super(PhaseWidget, self).__init__() - self._layout = QtWidgets.QVBoxLayout() + self._layout = QtWidgets.QHBoxLayout() + self._layout.setContentsMargins(5, 5, 5, 5) + + self.add_btn = FlatButton() + self.edit_btn = FlatButton() + self.delete_btn = FlatButton() + self.clear_btn = FlatButton() + self.save_list_btn = FlatButton('Save List') + self.load_list_btn = FlatButton('Load List') self.button_widget = QtWidgets.QWidget(self) self.button_widget.setObjectName('phase_control_button_widget') - self._button_layout = QtWidgets.QHBoxLayout() + self._button_layout = QtWidgets.QVBoxLayout() self._button_layout.setContentsMargins(0, 0, 0, 0) self._button_layout.setSpacing(6) - self.add_btn = FlatButton('Add') - self.edit_btn = FlatButton('Edit') - self.delete_btn = FlatButton('Delete') - self.clear_btn = FlatButton('Clear') - self.save_list_btn = FlatButton('Save List') - self.load_list_btn = FlatButton('Load List') - self._button_layout.addWidget(self.add_btn) self._button_layout.addWidget(self.edit_btn) + self._button_layout.addWidget(HorizontalLine()) + self._button_layout.addWidget(HorizontalLine()) self._button_layout.addWidget(self.delete_btn) + self._button_layout.addWidget(HorizontalLine()) + self._button_layout.addWidget(HorizontalLine()) self._button_layout.addWidget(self.clear_btn) - self._button_layout.addWidget(VerticalLine()) - self._button_layout.addSpacerItem(HorizontalSpacerItem()) - self._button_layout.addWidget(VerticalLine()) - self._button_layout.addWidget(self.save_list_btn) - self._button_layout.addWidget(self.load_list_btn) + self._button_layout.addSpacerItem(VerticalSpacerItem()) self.button_widget.setLayout(self._button_layout) self._layout.addWidget(self.button_widget) self.parameter_widget = QtWidgets.QWidget() - self._parameter_layout = QtWidgets.QGridLayout() - self.pressure_sb = DoubleSpinBoxAlignRight() - self.temperature_sb = DoubleSpinBoxAlignRight() + self._parameter_layout = QtWidgets.QVBoxLayout() + self._parameter_layout.setContentsMargins(0, 0, 0, 0) + self._parameter_layout.setSpacing(4) + self.pressure_step_msb = DoubleMultiplySpinBoxAlignRight() self.temperature_step_msb = DoubleMultiplySpinBoxAlignRight() - self.apply_to_all_cb = QtWidgets.QCheckBox('Apply to all phases') - self.show_in_pattern_cb = QtWidgets.QCheckBox('Show in Pattern') - - self._parameter_layout.addWidget(QtWidgets.QLabel('Parameter'), 0, 1) - self._parameter_layout.addWidget(QtWidgets.QLabel('Step'), 0, 3) - self._parameter_layout.addWidget(QtWidgets.QLabel('P:'), 1, 0) - self._parameter_layout.addWidget(QtWidgets.QLabel('T:'), 2, 0) - self._parameter_layout.addWidget(QtWidgets.QLabel('GPa'), 1, 2) - self._parameter_layout.addWidget(QtWidgets.QLabel('K'), 2, 2) - - self._parameter_layout.addWidget(self.pressure_sb, 1, 1) - self._parameter_layout.addWidget(self.pressure_step_msb, 1, 3) - self._parameter_layout.addWidget(self.temperature_sb, 2, 1) - self._parameter_layout.addWidget(self.temperature_step_msb, 2, 3) - - self._parameter_layout.addWidget(self.apply_to_all_cb, 3, 0, 1, 5) - self._parameter_layout.addWidget(self.show_in_pattern_cb, 4, 0, 1, 5) - self._parameter_layout.addItem(VerticalSpacerItem(), 5, 0) + self.apply_to_all_cb = QtWidgets.QCheckBox('apply to all') + + self._parameter_layout.addWidget(QtWidgets.QLabel('P step')) + self._parameter_layout.addWidget(self.pressure_step_msb) + self._parameter_layout.addWidget(QtWidgets.QLabel('T Step')) + self._parameter_layout.addWidget(self.temperature_step_msb) + self._parameter_layout.addWidget(self.apply_to_all_cb) + self._parameter_layout.addWidget(HorizontalLine()) + self._parameter_layout.addItem(VerticalSpacerItem()) + self._parameter_layout.addWidget(self.save_list_btn) + self._parameter_layout.addWidget(self.load_list_btn) + self.parameter_widget.setLayout(self._parameter_layout) self._body_layout = QtWidgets.QHBoxLayout() + self.phase_tw = ListTableWidget(columns=5) + self.phase_tw.setObjectName('phase_table_widget') + self.phase_tw.setHorizontalHeaderLabels(['', '', 'Name', 'P (GPa)', 'T (K)']) + self.phase_tw.horizontalHeader().setVisible(True) + self.phase_tw.horizontalHeader().setStretchLastSection(False) + self.phase_tw.setColumnWidth(0, 20) + self.phase_tw.setColumnWidth(1, 25) + self.phase_tw.horizontalHeader().setResizeMode(2, QtWidgets.QHeaderView.Stretch) + self.phase_tw.horizontalHeader().setResizeMode(3, QtWidgets.QHeaderView.ResizeToContents) + self.phase_tw.horizontalHeader().setResizeMode(4, QtWidgets.QHeaderView.ResizeToContents) + self.phase_tw.setItemDelegate(NoRectDelegate()) self._body_layout.addWidget(self.phase_tw, 10) self._body_layout.addWidget(self.parameter_widget, 0) self._layout.addLayout(self._body_layout) - self.setLayout(self._layout) + # label for alternative view: + self.phase_header_btn = FlatButton('Phase') + self.phase_header_btn.setEnabled(False) + self.phase_header_btn.setVisible(False) + self._main_layout = QtWidgets.QVBoxLayout() + self._main_layout.setContentsMargins(0, 0, 0, 0) + self._main_layout.addWidget(self.phase_header_btn) + self._main_layout.addLayout(self._layout) + self.setLayout(self._main_layout) self.style_widgets() + self.add_tooltips() self.phase_show_cbs = [] self.phase_color_btns = [] + self.pressure_sbs = [] + self.temperature_sbs = [] + self.show_parameter_in_pattern = True - header_view = QtWidgets.QHeaderView(QtCore.Qt.Horizontal, self.phase_tw) - self.phase_tw.setHorizontalHeader(header_view) - header_view.setResizeMode(2, QtWidgets.QHeaderView.Stretch) - header_view.setResizeMode(3, QtWidgets.QHeaderView.ResizeToContents) - header_view.setResizeMode(4, QtWidgets.QHeaderView.ResizeToContents) - header_view.hide() - self.phase_tw.setItemDelegate(NoRectDelegate()) def style_widgets(self): - self.phase_tw.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.MinimumExpanding) - self.parameter_widget.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) - self.phase_tw.setMinimumHeight(130) + icon_size = QtCore.QSize(17, 17) + + self.add_btn.setIcon(QtGui.QIcon(os.path.join(icons_path, 'open.ico'))) + self.add_btn.setIconSize(icon_size) + + self.edit_btn.setIcon(QtGui.QIcon(os.path.join(icons_path, 'edit.png'))) + self.edit_btn.setIconSize(QtCore.QSize(14, 14)) - self.temperature_step_msb.setMaximumWidth(60) - self.pressure_step_msb.setMaximumWidth(60) - self.pressure_sb.setMinimumWidth(100) + self.delete_btn.setIcon(QtGui.QIcon(os.path.join(icons_path, 'delete.png'))) + self.delete_btn.setIconSize(QtCore.QSize(12, 14)) - self.pressure_sb.setMaximum(9999999) - self.pressure_sb.setMinimum(-9999999) - self.pressure_sb.setValue(0) + self.clear_btn.setIcon(QtGui.QIcon(os.path.join(icons_path, 'reset_dark.ico'))) + self.clear_btn.setIconSize(icon_size) - self.pressure_step_msb.setMaximum(1000.0) - self.pressure_step_msb.setMinimum(0.01) - self.pressure_step_msb.setValue(0.5) + def modify_btn_to_icon_size(btn): + button_height = 25 + button_width = 25 + btn.setMinimumHeight(button_height) + btn.setMaximumHeight(button_height) + btn.setMinimumWidth(button_width) + btn.setMaximumWidth(button_width) - self.temperature_sb.setMaximum(99999999) - self.temperature_sb.setMinimum(0) - self.temperature_sb.setValue(298) + modify_btn_to_icon_size(self.add_btn) + modify_btn_to_icon_size(self.delete_btn) + modify_btn_to_icon_size(self.clear_btn) + modify_btn_to_icon_size(self.edit_btn) + + self.phase_tw.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.MinimumExpanding) + self.parameter_widget.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) + + step_txt_width = 70 + + self.pressure_step_msb.setMinimumWidth(step_txt_width) + self.pressure_step_msb.setMaximumWidth(step_txt_width) + self.temperature_step_msb.setMinimumWidth(step_txt_width) + self.temperature_step_msb.setMaximumWidth(step_txt_width) self.temperature_step_msb.setMaximum(1000.0) self.temperature_step_msb.setMinimum(1.0) self.temperature_step_msb.setValue(100.0) - self.setStyleSheet(""" - #phase_control_button_widget QPushButton { - min-width: 95; - } - """) + self.pressure_step_msb.setValue(1) + + self.phase_header_btn.setStyleSheet("border-radius: 0px") self.apply_to_all_cb.setChecked(True) - self.show_in_pattern_cb.setChecked(True) + + def add_tooltips(self): + self.add_btn.setToolTip('Loads Phase(s) from jcpds or cif file(s)') + self.edit_btn.setToolTip('Edit selected Phase') + self.delete_btn.setToolTip('Removes currently selected phase') + self.clear_btn.setToolTip('Removes all phases') + self.apply_to_all_cb.setToolTip('Whether individual changes in P or T\nare applied to all other phases') + self.pressure_step_msb.setToolTip('Sets the step for the pressure spinboxes') + self.temperature_step_msb.setToolTip('Sets the step for the temperature spinboxes') # ############################################################################################### # Now comes all the phase tw stuff @@ -168,15 +210,23 @@ def add_phase(self, name, color): name_item.setTextAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter) self.phase_tw.setItem(current_rows, 2, name_item) - pressure_item = QtWidgets.QTableWidgetItem('0 GPa') - pressure_item.setFlags(pressure_item.flags() & ~QtCore.Qt.ItemIsEditable) - pressure_item.setTextAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.phase_tw.setItem(current_rows, 3, pressure_item) - - temperature_item = QtWidgets.QTableWidgetItem('298 K') - temperature_item.setFlags(temperature_item.flags() & ~QtCore.Qt.ItemIsEditable) - temperature_item.setTextAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.phase_tw.setItem(current_rows, 4, temperature_item) + pressure_sb = DoubleSpinBoxAlignRight() + pressure_sb.setMinimum(-9999999) + pressure_sb.setMaximum(9999999) + pressure_sb.setValue(0) + pressure_sb.setSingleStep(self.pressure_step_msb.value()) + pressure_sb.valueChanged.connect(partial(self.pressure_sb_callback, pressure_sb)) + self.phase_tw.setCellWidget(current_rows, 3, pressure_sb) + self.pressure_sbs.append(pressure_sb) + + temperature_sb = DoubleSpinBoxAlignRight() + temperature_sb.setMinimum(-9999999) + temperature_sb.setMaximum(9999999) + temperature_sb.setValue(300) + temperature_sb.setSingleStep(self.temperature_step_msb.value()) + temperature_sb.valueChanged.connect(partial(self.temperature_sb_callback, temperature_sb)) + self.phase_tw.setCellWidget(current_rows, 4, temperature_sb) + self.temperature_sbs.append(temperature_sb) self.phase_tw.setColumnWidth(0, 20) self.phase_tw.setColumnWidth(1, 25) @@ -184,6 +234,9 @@ def add_phase(self, name, color): self.select_phase(current_rows) self.phase_tw.blockSignals(False) + self.phase_tw.horizontalHeader().setResizeMode(3, QtWidgets.QHeaderView.ResizeToContents) + self.phase_tw.horizontalHeader().setResizeMode(4, QtWidgets.QHeaderView.ResizeToContents) + def select_phase(self, ind): self.phase_tw.selectRow(ind) @@ -204,6 +257,8 @@ def del_phase(self, ind): self.phase_tw.blockSignals(False) del self.phase_show_cbs[ind] del self.phase_color_btns[ind] + del self.temperature_sbs[ind] + del self.pressure_sbs[ind] if self.phase_tw.rowCount() > ind: self.select_phase(ind) @@ -214,32 +269,22 @@ def rename_phase(self, ind, name): name_item = self.phase_tw.item(ind, 2) name_item.setText(name) - def set_phase_temperature(self, ind, T): - temperature_item = self.phase_tw.item(ind, 4) - try: - temperature_item.setText("{0:.2f} K".format(T)) - except ValueError: - temperature_item.setText("{0} K".format(T)) + def set_phase_temperature(self, ind, temperature): + pass + self.temperature_sbs[ind].blockSignals(True) + self.temperature_sbs[ind].setValue(temperature) + self.temperature_sbs[ind].blockSignals(False) def get_phase_temperature(self, ind): - temperature_item = self.phase_tw.item(ind, 4) - try: - temperature = float(str(temperature_item.text()).split()[0]) - except: - temperature = None - return temperature + return self.temperature_sbs[ind].value() - def set_phase_pressure(self, ind, P): - pressure_item = self.phase_tw.item(ind, 3) - try: - pressure_item.setText("{0:.2f} GPa".format(P)) - except ValueError: - pressure_item.setText("{0} GPa".format(P)) + def set_phase_pressure(self, ind, pressure): + self.pressure_sbs[ind].blockSignals(True) + self.pressure_sbs[ind].setValue(pressure) + self.pressure_sbs[ind].blockSignals(False) def get_phase_pressure(self, ind): - pressure_item = self.phase_tw.item(ind, 3) - pressure = float(str(pressure_item.text()).split()[0]) - return pressure + return self.pressure_sbs[ind].value() def phase_color_btn_click(self, button): self.color_btn_clicked.emit(self.phase_color_btns.index(button), button) @@ -254,3 +299,9 @@ def phase_show_cb_set_checked(self, ind, state): def phase_show_cb_is_checked(self, ind): checkbox = self.phase_show_cbs[ind] return checkbox.isChecked() + + def pressure_sb_callback(self, pressure_sb): + self.pressure_sb_value_changed.emit(self.pressure_sbs.index(pressure_sb), pressure_sb.value()) + + def temperature_sb_callback(self, temperature_sb): + self.temperature_sb_value_changed.emit(self.temperature_sbs.index(temperature_sb), temperature_sb.value()) diff --git a/dioptas/widgets/integration/control/__init__.py b/dioptas/widgets/integration/control/__init__.py index 6fe9aaaef..24dfd67fb 100644 --- a/dioptas/widgets/integration/control/__init__.py +++ b/dioptas/widgets/integration/control/__init__.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -16,7 +18,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from qtpy import QtWidgets +from qtpy import QtWidgets, QtCore, QtGui from .ImageWidget import ImageWidget from .PatternWidget import PatternWidget @@ -27,10 +29,17 @@ from .OptionsWidget import OptionsWidget -class IntegrationControlWidget(QtWidgets.QTabWidget): +class IntegrationControlWidget(QtWidgets.QWidget): def __init__(self): super(IntegrationControlWidget, self).__init__() + self._layout = QtWidgets.QHBoxLayout() + self._layout.setContentsMargins(0, 0, 0, 0) + self._layout.setSpacing(5) + + self.tab_widget_1 = QtWidgets.QTabWidget() + self.tab_widget_2 = QtWidgets.QTabWidget() + self.img_control_widget = ImageWidget() self.pattern_control_widget = PatternWidget() self.overlay_control_widget = OverlayWidget() @@ -39,30 +48,104 @@ def __init__(self): self.background_control_widget = BackgroundWidget() self.integration_options_widget = OptionsWidget() - self.addTab(self.img_control_widget, 'Image') - self.addTab(self.pattern_control_widget, 'Pattern') - self.addTab(self.overlay_control_widget, 'Overlay') - self.addTab(self.phase_control_widget, 'Phase') - self.addTab(self.corrections_control_widget, 'Cor') - self.addTab(self.background_control_widget, 'Bkg') - self.addTab(self.integration_options_widget, 'X') - - self.style_widgets() - - def style_widgets(self): - self.setStyleSheet(""" - QTableWidget QPushButton { - margin: 5px; - } - - QTableWidget QPushButton::pressed{ - margin-top: 7px; - margin-left: 7px; - } - - QTableWidget { - selection-background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 rgba(177,80,0,255), stop:1 rgba(255,120,0,255)); - selection-color: #F1F1F1; - } - """) + self.horizontal_splitter = QtWidgets.QSplitter() + self.horizontal_splitter.setOrientation(QtCore.Qt.Horizontal) + + self.horizontal_splitter.addWidget(self.tab_widget_1) + self.horizontal_splitter.addWidget(self.tab_widget_2) + + self.vertical_splitter = QtWidgets.QSplitter() + self.vertical_splitter.setOrientation(QtCore.Qt.Vertical) + + self.vertical_splitter.addWidget(self.horizontal_splitter) + + self._layout.addWidget(self.vertical_splitter) + self.setLayout(self._layout) + + self.current_layout = None + + self.orientation = QtCore.Qt.Horizontal # other value is QtCore.Qt.Horizontal + + self.tab_widget_1.addTab(self.img_control_widget, 'Image') + self.tab_widget_1.addTab(self.pattern_control_widget, 'Pattern') + self.tab_widget_1.addTab(self.overlay_control_widget, 'Overlay') + self.tab_widget_1.addTab(self.phase_control_widget, 'Phase') + self.tab_widget_1.addTab(self.corrections_control_widget, 'Cor') + self.tab_widget_1.addTab(self.background_control_widget, 'Bkg') + self.tab_widget_1.addTab(self.integration_options_widget, 'X') + + self.horizontal_layout_2() + + def horizontal_layout_1(self): + self.current_layout = 1 + + self.tab_widget_2.hide() + + self.tab_widget_1.insertTab(2, self.overlay_control_widget, 'Overlay') + self.tab_widget_1.insertTab(3, self.phase_control_widget, 'Phase') + + self.overlay_control_widget.overlay_header_btn.hide() + self.phase_control_widget.phase_header_btn.hide() + + def horizontal_layout_2(self): + self.current_layout = 2 + + self.tab_widget_2.show() + + self.tab_widget_2.addTab(self.overlay_control_widget, 'Overlay') + self.tab_widget_2.addTab(self.phase_control_widget, 'Phase') + + self.overlay_control_widget.overlay_header_btn.hide() + self.phase_control_widget.phase_header_btn.hide() + + def horizontal_layout_3(self): + self.current_layout = 3 + self.tab_widget_2.hide() + + self.horizontal_splitter.addWidget(self.overlay_control_widget) + self.horizontal_splitter.addWidget(self.phase_control_widget) + + self.overlay_control_widget.show() + self.phase_control_widget.show() + + self.overlay_control_widget.overlay_header_btn.show() + self.phase_control_widget.phase_header_btn.show() + + def vertical_layout(self): + self.tab_widget_2.hide() + self.vertical_splitter.addWidget(self.overlay_control_widget) + self.vertical_splitter.addWidget(self.phase_control_widget) + + self.overlay_control_widget.show() + self.phase_control_widget.show() + + self.overlay_control_widget.overlay_header_btn.show() + self.phase_control_widget.phase_header_btn.show() + + def update_layout(self, force_layout=False): + if self.orientation == QtCore.Qt.Horizontal: + if self.width() < 800: + if self.current_layout != 1 or force_layout: + self.horizontal_layout_1() + elif self.width() > 1400: + if self.current_layout != 3 or force_layout: + self.horizontal_layout_3() + else: + if self.current_layout != 2 or force_layout: + self.horizontal_layout_2() + elif self.orientation == QtCore.Qt.Vertical: + self.vertical_layout() + + def resizeEvent(self, a0: QtGui.QResizeEvent): + self.update_layout() + super(IntegrationControlWidget, self).resizeEvent(a0) + + def setOrientation(self, a0): + """ + Sets the orientation of the control widgets + :param a0: either QtCore.Qt.Horizontal or QtCore.Qt.Vertical + """ + self.orientation = a0 + self.update_layout(True) + diff --git a/dioptas/widgets/integration/display/ImgWidget.py b/dioptas/widgets/integration/display/ImgWidget.py index a7118d704..b8999c6a6 100644 --- a/dioptas/widgets/integration/display/ImgWidget.py +++ b/dioptas/widgets/integration/display/ImgWidget.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -18,12 +20,13 @@ import os -from qtpy import QtWidgets, QtCore +from qtpy import QtWidgets, QtCore, QtGui from pyqtgraph import GraphicsLayoutWidget from ...plot_widgets.ImgWidget import IntegrationImgWidget from ...CustomWidgets import FlatButton, CheckableFlatButton, HorizontalSpacerItem from ..CustomWidgets import MouseCurrentAndClickedWidget, MouseUnitCurrentAndClickedWidget +from .... import icons_path from .. import CLICKED_COLOR @@ -64,6 +67,9 @@ def __init__(self): self._control_layout.setContentsMargins(6, 6, 6, 6) self._control_layout.setSpacing(6) + self.save_image_btn = FlatButton() + self.save_image_btn.setToolTip("Save Image") + self.roi_btn = CheckableFlatButton('ROI') self.mode_btn = FlatButton('Cake') self.cake_shift_azimuth_sl = QtWidgets.QSlider(QtCore.Qt.Horizontal) @@ -73,6 +79,7 @@ def __init__(self): self.autoscale_btn = CheckableFlatButton('AutoScale') self.undock_btn = FlatButton('Undock') + self._control_layout.addWidget(self.save_image_btn) self._control_layout.addWidget(self.roi_btn) self._control_layout.addWidget(self.mode_btn) self._control_layout.addWidget(self.cake_shift_azimuth_sl) @@ -105,4 +112,8 @@ def style_widgets(self): } """) self.autoscale_btn.setChecked(True) - self.position_and_unit_widget.hide() \ No newline at end of file + self.position_and_unit_widget.hide() + + self.save_image_btn.setIcon(QtGui.QIcon(os.path.join(icons_path, 'save.ico'))) + self.save_image_btn.setIconSize(QtCore.QSize(13, 13)) + self.save_image_btn.setMaximumWidth(25) \ No newline at end of file diff --git a/dioptas/widgets/integration/display/PatternWidget.py b/dioptas/widgets/integration/display/PatternWidget.py index 942e427b8..2cc30e808 100644 --- a/dioptas/widgets/integration/display/PatternWidget.py +++ b/dioptas/widgets/integration/display/PatternWidget.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -16,12 +18,13 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from qtpy import QtWidgets +import os +from qtpy import QtWidgets, QtGui, QtCore from pyqtgraph import GraphicsLayoutWidget from ...plot_widgets import PatternWidget from ...CustomWidgets import LabelAlignRight, FlatButton, CheckableFlatButton, HorizontalSpacerItem, VerticalSpacerItem - +from .... import icons_path class IntegrationPatternWidget(QtWidgets.QWidget): def __init__(self): @@ -36,14 +39,13 @@ def __init__(self): self._top_control_layout = QtWidgets.QHBoxLayout() self._top_control_layout.setContentsMargins(8, 8, 0, 0) - self.save_image_btn = FlatButton('Save Image') - self.save_pattern_btn = FlatButton('Save Pattern') + self.save_pattern_btn = FlatButton() + self.save_pattern_btn.setToolTip("Save Pattern") self.as_overlay_btn = FlatButton('As Overlay') self.as_bkg_btn = FlatButton('As Bkg') self.load_calibration_btn = FlatButton('Load Calibration') self.calibration_lbl = LabelAlignRight('None') - self._top_control_layout.addWidget(self.save_image_btn) self._top_control_layout.addWidget(self.save_pattern_btn) self._top_control_layout.addWidget(self.as_overlay_btn) self._top_control_layout.addWidget(self.as_bkg_btn) @@ -129,3 +131,7 @@ def style_widgets(self): self.background_inspect_btn.setMaximumWidth(right_controls_button_width) self.antialias_btn.setMaximumWidth(right_controls_button_width) self.auto_range_btn.setMaximumWidth(right_controls_button_width) + + self.save_pattern_btn.setIcon(QtGui.QIcon(os.path.join(icons_path, 'save.ico'))) + self.save_pattern_btn.setIconSize(QtCore.QSize(13, 13)) + self.save_pattern_btn.setMaximumWidth(right_controls_button_width) diff --git a/dioptas/widgets/integration/display/StatusWidget.py b/dioptas/widgets/integration/display/StatusWidget.py index 0c6ae0017..c122b2313 100644 --- a/dioptas/widgets/integration/display/StatusWidget.py +++ b/dioptas/widgets/integration/display/StatusWidget.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -33,7 +35,9 @@ def __init__(self): self.mouse_pos_widget = MouseCurrentAndClickedWidget(CLICKED_COLOR) self.mouse_unit_widget = MouseUnitCurrentAndClickedWidget(CLICKED_COLOR) self.bkg_name_lbl = LabelAlignRight('') + self.change_view_btn = QtWidgets.QPushButton('Change View') + self._layout.addWidget(self.change_view_btn) self._layout.addWidget(self.mouse_pos_widget) self._layout.addSpacerItem(HorizontalSpacerItem()) self._layout.addWidget(self.mouse_unit_widget) diff --git a/dioptas/widgets/plot_widgets/ExLegendItem.py b/dioptas/widgets/plot_widgets/ExLegendItem.py index 84eaa0e46..ba93eb1c6 100644 --- a/dioptas/widgets/plot_widgets/ExLegendItem.py +++ b/dioptas/widgets/plot_widgets/ExLegendItem.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/widgets/plot_widgets/HistogramLUTItem.py b/dioptas/widgets/plot_widgets/HistogramLUTItem.py index 8db3ba20d..3df7b6f81 100644 --- a/dioptas/widgets/plot_widgets/HistogramLUTItem.py +++ b/dioptas/widgets/plot_widgets/HistogramLUTItem.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/widgets/plot_widgets/ImgWidget.py b/dioptas/widgets/plot_widgets/ImgWidget.py index 88d588f82..e74f55efa 100644 --- a/dioptas/widgets/plot_widgets/ImgWidget.py +++ b/dioptas/widgets/plot_widgets/ImgWidget.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -35,9 +37,9 @@ class ImgWidget(QtCore.QObject): def __init__(self, pg_layout, orientation='vertical'): super(ImgWidget, self).__init__() self.pg_layout = pg_layout - self.orientation = orientation self.create_graphics() + self.set_orientation(orientation) self.create_scatter_plot() self.modify_mouse_behavior() @@ -46,63 +48,49 @@ def __init__(self, pg_layout, orientation='vertical'): self._max_range = True + def create_graphics(self): # self.img_histogram_LUT = pg.HistogramLUTItem(self.data_img_item) - if self.orientation == 'horizontal': - - self.img_view_box = self.pg_layout.addViewBox(1, 1) - - # create the item handling the Data img - self.data_img_item = pg.ImageItem() - self.img_view_box.addItem(self.data_img_item) - self.img_histogram_LUT = HistogramLUTItem(self.data_img_item) - self.pg_layout.addItem(self.img_histogram_LUT, 0, 1) - # self.left_axis_image = pg.AxisItem('left', linkView=self.img_view_box) - # self.pg_layout.addItem(self.left_axis_image, 1, 0) - self.left_axis_cake = pg.AxisItem('left') - # self.bottom_axis_image = pg.AxisItem('bottom', linkView=self.img_view_box) - # self.pg_layout.addItem(self.bottom_axis_image, 2, 1) - self.bottom_axis_cake = pg.AxisItem('bottom') - self.left_axis_cake.hide() - self.bottom_axis_cake.hide() - self.bottom_axis_cake.setLabel(u'2θ', u'°') - self.left_axis_cake.setLabel(u'Azimuth', u'°') - - elif self.orientation == 'vertical': - self.img_view_box = self.pg_layout.addViewBox(0, 1) - # create the item handling the Data img - self.data_img_item = pg.ImageItem() - self.img_view_box.addItem(self.data_img_item) - self.img_histogram_LUT = HistogramLUTItem(self.data_img_item, orientation='vertical') - # self.img_histogram_LUT.axis.hide() - self.pg_layout.addItem(self.img_histogram_LUT, 0, 2) - # self.left_axis_image = pg.AxisItem('left', linkView=self.img_view_box) - # self.pg_layout.addItem(self.left_axis_image, 0, 0) - # self.bottom_axis_image = pg.AxisItem('bottom', linkView=self.img_view_box) - # self.pg_layout.addItem(self.bottom_axis_image, 1, 1) - - self.img_view_box.setAspectLocked(True) + + self.img_view_box = self.pg_layout.addViewBox(1, 1) + + self.data_img_item = pg.ImageItem() + self.img_view_box.addItem(self.data_img_item) + + self.img_histogram_LUT_horizontal = HistogramLUTItem(self.data_img_item) + self.pg_layout.addItem(self.img_histogram_LUT_horizontal, 0, 1) + self.img_histogram_LUT_vertical = HistogramLUTItem(self.data_img_item, orientation='vertical') + self.pg_layout.addItem(self.img_histogram_LUT_vertical, 1, 2) + + self.left_axis_cake = pg.AxisItem('left') + self.bottom_axis_cake = pg.AxisItem('bottom') + self.bottom_axis_cake.setLabel(u'2θ', u'°') + self.left_axis_cake.setLabel(u'Azimuth', u'°') + + self.left_axis_cake.hide() + self.bottom_axis_cake.hide() + + def set_orientation(self, orientation): + if orientation == 'horizontal': + self.img_histogram_LUT_vertical.hide() + self.img_histogram_LUT_horizontal.show() + elif orientation == 'vertical': + self.img_histogram_LUT_horizontal.hide() + self.img_histogram_LUT_vertical.show() + self.orientation = orientation def replace_image_and_cake_axes(self, mode='image'): if mode == 'image': self.pg_layout.removeItem(self.bottom_axis_cake) self.pg_layout.removeItem(self.left_axis_cake) - # self.pg_layout.addItem(self.left_axis_image, 1, 0) - # self.pg_layout.addItem(self.bottom_axis_image, 2, 1) self.bottom_axis_cake.hide() self.left_axis_cake.hide() - # self.bottom_axis_image.show() - # self.left_axis_image.show() elif mode == 'cake': - # self.pg_layout.removeItem(self.left_axis_image) - # self.pg_layout.removeItem(self.bottom_axis_image) self.pg_layout.addItem(self.bottom_axis_cake, 2, 1) self.pg_layout.addItem(self.left_axis_cake, 1, 0) self.bottom_axis_cake.show() self.left_axis_cake.show() - # self.bottom_axis_image.hide() - # self.left_axis_image.hide() def create_scatter_plot(self): self.img_scatter_plot_item = pg.ScatterPlotItem(pen=pg.mkPen('w'), brush=pg.mkBrush('r')) @@ -143,7 +131,7 @@ def auto_range_rescale(self): self.auto_range() def auto_level(self): - hist_x, hist_y = self.img_histogram_LUT.hist_x, self.img_histogram_LUT.hist_y + hist_x, hist_y = self.img_histogram_LUT_horizontal.hist_x, self.img_histogram_LUT_horizontal.hist_y hist_y_cumsum = np.cumsum(hist_y) hist_y_sum = np.sum(hist_y) @@ -158,7 +146,8 @@ def auto_level(self): else: max_level = 0.5 * np.max(hist_x) - self.img_histogram_LUT.setLevels(min_level, max_level) + self.img_histogram_LUT_vertical.setLevels(min_level, max_level) + self.img_histogram_LUT_horizontal.setLevels(min_level, max_level) def add_scatter_data(self, x, y): self.img_scatter_plot_item.addPoints(x=y, y=x) @@ -166,6 +155,13 @@ def add_scatter_data(self, x, y): def clear_scatter_plot(self): self.img_scatter_plot_item.setData(x=None, y=None) + def remove_last_scatter_points(self, num_points): + data_x, data_y = self.img_scatter_plot_item.getData() + if not data_x.size == 0: + data_x = data_x[:-num_points] + data_y = data_y[:-num_points] + self.img_scatter_plot_item.setData(data_x, data_y) + def hide_scatter_plot(self): self.img_scatter_plot_item.hide() @@ -299,6 +295,7 @@ def __init__(self, pg_layout, orientation='vertical'): super(MaskImgWidget, self).__init__(pg_layout, orientation) self.mask_img_item = pg.ImageItem() self.img_view_box.addItem(self.mask_img_item) + self.img_view_box.setAspectLocked(True) self.set_color() self.mask_preview_fill_color = QtGui.QColor(255, 0, 0, 150) diff --git a/dioptas/widgets/plot_widgets/PatternWidget.py b/dioptas/widgets/plot_widgets/PatternWidget.py index 567d4b3f9..2b6d5869b 100644 --- a/dioptas/widgets/plot_widgets/PatternWidget.py +++ b/dioptas/widgets/plot_widgets/PatternWidget.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/dioptas/widgets/plot_widgets/__init__.py b/dioptas/widgets/plot_widgets/__init__.py index 61eb89e39..78e9acbd0 100644 --- a/dioptas/widgets/plot_widgets/__init__.py +++ b/dioptas/widgets/plot_widgets/__init__.py @@ -1,7 +1,9 @@ -# -*- coding: utf8 -*- -# Dioptas - GUI program for fast processing of 2D X-ray data -# Copyright (C) 2017 Clemens Prescher (clemens.prescher@gmail.com) -# Institute for Geology and Mineralogy, University of Cologne +# -*- coding: utf-8 -*- +# Dioptas - GUI program for fast processing of 2D X-ray diffraction data +# Principal author: Clemens Prescher (clemens.prescher@gmail.com) +# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +# Copyright (C) 2019 DESY, Hamburg, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/docs/source/conf.py b/docs/source/conf.py index 268675ada..a076bbde2 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -47,7 +47,11 @@ # General information about the project. project = 'Dioptas' -copyright = '2017, Clemens Prescher' +copyright = """Dioptas - GUI program for fast processing of 2D X-ray diffraction data +Principal author: Clemens Prescher (clemens.prescher@gmail.com) +Copyright (C) 2014-2019 GSECARS, University of Chicago, USA +Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany +Copyright (C) 2019 DESY, Hamburg, Germany""" author = 'Clemens Prescher' # The version info for the project you're documenting, acts as replacement for diff --git a/scripts/dioptas b/scripts/dioptas index c1bccbabd..63456182d 100644 --- a/scripts/dioptas +++ b/scripts/dioptas @@ -1,3 +1,9 @@ #!/usr/bin/env python -from dioptas import main -main() \ No newline at end of file +import sys +from dioptas import main, icons_path, make_shortcut + +if len(sys.argv) > 1 and sys.argv[1].startswith('makeshortcut'): + make_shortcut('Dioptas', 'dioptas', description='Dioptas 2D XRD', + icon_path=icons_path, icon='icon') +else: + main() diff --git a/setup.py b/setup.py index 7bfbc03c0..9d9983485 100644 --- a/setup.py +++ b/setup.py @@ -1,13 +1,23 @@ -# -*- coding: utf8 -*- +# -*- coding: utf-8 -*- from setuptools import find_packages from numpy.distutils.core import Extension, setup import versioneer -smooth_bruckner = Extension( - name='dioptas.model.util.smooth_bruckner', - sources= ['dioptas/model/util/smooth_bruckner.f95'] -) +with_bruckner_cython = True +with_bruckner_f95 = False + +ext_modules = [] +if with_bruckner_cython: + from Cython.Build import cythonize + + ext_modules = cythonize('dioptas/model/util/smooth_bruckner_cython.pyx') + +if with_bruckner_f95: + ext_modules.append(Extension( + name='dioptas.model.util.smooth_bruckner', + sources=['dioptas/model/util/smooth_bruckner.f95']) + ) setup( name='dioptas', @@ -17,7 +27,7 @@ author='Clemens Prescher', author_email="clemens.prescher@gmail.com", url='https://github.com/Dioptas/Dioptas/', - install_requires=['numpy'], + install_requires=['numpy', 'cython'], description='GUI program for reduction and exploration of 2D X-ray diffraction data', classifiers=['Intended Audience :: Science/Research', 'Operating System :: OS Independent', @@ -32,5 +42,5 @@ ] }, scripts=['scripts/dioptas'], - ext_modules=[smooth_bruckner] + ext_modules=ext_modules, )