diff --git a/.github/workflows/automated-testing.yml b/.github/workflows/automated-testing.yml new file mode 100644 index 0000000..5b553a3 --- /dev/null +++ b/.github/workflows/automated-testing.yml @@ -0,0 +1,35 @@ +name: Automated testing + +on: [push] + +jobs: + build-linux: + runs-on: ubuntu-latest + strategy: + max-parallel: 5 + + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: 3.10.4 + - name: Add conda to system path + run: | + # $CONDA is an environment variable pointing to the root of the miniconda directory + echo $CONDA/bin >> $GITHUB_PATH + - name: Install dependencies + run: | + conda env update --file environment.yml --name base + pip install . + - name: Lint with flake8 + run: | + conda install flake8 + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with pytest + run: | + conda install pytest + pytest diff --git a/beamtime/README.md b/README.md similarity index 100% rename from beamtime/README.md rename to README.md diff --git a/beamtime/reqirements2.txt b/beamtime/reqirements2.txt deleted file mode 100644 index 5cef354..0000000 --- a/beamtime/reqirements2.txt +++ /dev/null @@ -1,141 +0,0 @@ -# This file may be used to create an environment using: -# $ conda create --name --file -# platform: win-64 -argon2-cffi=21.3.0=pyhd3eb1b0_0 -argon2-cffi-bindings=21.2.0=py39h2bbff1b_0 -attrs=21.4.0=pyhd3eb1b0_0 -backcall=0.2.0=pyhd3eb1b0_0 -beamtime=0.1=pypi_0 -blas=1.0=mkl -bleach=4.1.0=pyhd3eb1b0_0 -bottleneck=1.3.2=py39h7cc1a96_1 -ca-certificates=2022.2.1=haa95532_0 -cached-property=1.5.2=hd8ed1ab_1 -cached_property=1.5.2=pyha770c72_1 -certifi=2021.10.8=py39haa95532_2 -cffi=1.15.0=py39h2bbff1b_1 -colorama=0.4.4=pyhd3eb1b0_0 -cycler=0.10.0=py_2 -debugpy=1.4.1=py39hd77b12b_0 -decorator=5.1.0=pyhd3eb1b0_0 -defusedxml=0.7.1=pyhd3eb1b0_0 -entrypoints=0.3=py39haa95532_0 -fabio=0.12.0=py39h5d4886f_0 -freetype=2.10.4=h546665d_1 -glymur=0.9.4=pyhd8ed1ab_0 -h5py=3.2.1=nompi_py39hf27771d_100 -hdf5=1.10.6=nompi_h5268f04_1114 -hdf5plugin=3.1.1=py39h71586dd_0 -icc_rt=2019.0.0=h0cc432a_1 -icu=68.1=h6c2663c_0 -importlib-metadata=4.8.2=py39haa95532_0 -importlib_metadata=4.8.2=hd3eb1b0_0 -intel-openmp=2021.3.0=haa95532_3372 -ipykernel=6.4.1=py39haa95532_1 -ipython=7.27.0=py39hd4e2768_0 -ipython_genutils=0.2.0=pyhd3eb1b0_1 -ipywidgets=7.6.5=pyhd3eb1b0_1 -jbig=2.1=h8d14728_2003 -jedi=0.18.0=py39haa95532_1 -jinja2=3.0.2=pyhd3eb1b0_0 -jpeg=9d=h2bbff1b_0 -jsonschema=3.2.0=pyhd3eb1b0_2 -jupyter_client=7.0.1=pyhd3eb1b0_0 -jupyter_core=4.8.1=py39haa95532_0 -jupyterlab_pygments=0.1.2=py_0 -jupyterlab_widgets=1.0.0=pyhd3eb1b0_1 -kiwisolver=1.3.2=py39h2e07f2f_0 -krb5=1.19.2=hbae68bd_2 -lerc=3.0=h0e60522_0 -libclang=11.1.0=default_h5c34c98_1 -libcurl=7.79.1=h789b8ee_1 -libdeflate=1.8=h2bbff1b_5 -libiconv=1.16=he774522_0 -libpng=1.6.37=h1d00b33_2 -libssh2=1.10.0=h680486a_2 -libtiff=4.3.0=hd413186_2 -libwebp=1.2.0=h2bbff1b_0 -libxml2=2.9.12=h0ad7f3c_0 -libxslt=1.1.34=he774522_0 -libzlib=1.2.11=h8ffe710_1013 -lxml=4.6.3=py39h4fd7cdf_0 -lz4-c=1.9.3=h8ffe710_1 -mako=1.1.5=pyhd8ed1ab_0 -markupsafe=2.0.1=py39h2bbff1b_0 -matplotlib=3.4.3=py39hcbf5309_1 -matplotlib-base=3.4.3=py39h581301d_1 -matplotlib-inline=0.1.2=pyhd3eb1b0_2 -mistune=0.8.4=py39h2bbff1b_1000 -mkl=2021.3.0=haa95532_524 -mkl-service=2.4.0=py39h2bbff1b_0 -mkl_fft=1.3.1=py39h277e83a_0 -mkl_random=1.2.2=py39hf11a4ad_0 -nbclient=0.5.11=pyhd3eb1b0_0 -nbconvert=6.1.0=py39haa95532_0 -nbformat=5.1.3=pyhd3eb1b0_0 -nest-asyncio=1.5.1=pyhd3eb1b0_0 -notebook=6.4.8=py39haa95532_0 -numexpr=2.7.3=py39hb80d3ca_1 -numpy=1.21.2=py39hfca59bb_0 -numpy-base=1.21.2=py39h0829f74_0 -olefile=0.46=pyh9f0ad1d_1 -openjpeg=2.4.0=hb211442_1 -openssl=1.1.1m=h2bbff1b_0 -packaging=21.3=pyhd3eb1b0_0 -pandas=1.3.3=py39h6214cd6_0 -pandocfilters=1.5.0=pyhd3eb1b0_0 -parso=0.8.2=pyhd3eb1b0_0 -pickleshare=0.7.5=pyhd3eb1b0_1003 -pillow=8.4.0=py39hd45dc43_0 -pip=21.2.4=py39haa95532_0 -prometheus_client=0.13.1=pyhd3eb1b0_0 -prompt-toolkit=3.0.20=pyhd3eb1b0_0 -pycparser=2.21=pyhd3eb1b0_0 -pyfai=0.20.0=hd8ed1ab_0 -pyfai-base=0.20.0=py39h2e25243_0 -pygments=2.10.0=pyhd3eb1b0_0 -pyparsing=2.4.7=pyhd3eb1b0_0 -pyqt=5.12.3=py39hcbf5309_7 -pyqt-impl=5.12.3=py39h415ef7b_7 -pyqt5-sip=4.19.18=py39h415ef7b_7 -pyqtchart=5.12=py39h415ef7b_7 -pyqtwebengine=5.12.1=py39h415ef7b_7 -pyreadline=2.1=py39hcbf5309_1004 -pyrsistent=0.18.0=py39h196d8e1_0 -python=3.9.7=h6244533_1 -python-dateutil=2.8.2=pyhd3eb1b0_0 -python_abi=3.9=2_cp39 -pytz=2021.3=pyhd3eb1b0_0 -pywin32=228=py39hbaba5e8_1 -pywinpty=2.0.2=py39h5da7b33_0 -pyzmq=22.2.1=py39hd77b12b_1 -qt=5.12.9=h5909a2a_4 -qtconsole=5.1.1=pyhd3eb1b0_0 -qtpy=1.11.2=pyhd8ed1ab_0 -scipy=1.7.1=py39hbe87c03_2 -send2trash=1.8.0=pyhd3eb1b0_1 -setuptools=58.0.4=py39haa95532_0 -silx=0.15.2=hd8ed1ab_0 -silx-base=0.15.2=py39h2e25243_0 -six=1.16.0=pyhd3eb1b0_0 -sqlite=3.36.0=h2bbff1b_0 -terminado=0.13.1=py39haa95532_0 -testpath=0.5.0=pyhd3eb1b0_0 -tk=8.6.11=h8ffe710_1 -tornado=6.1=py39h2bbff1b_0 -traitlets=5.1.0=pyhd3eb1b0_0 -typing-extensions=3.10.0.2=hd3eb1b0_0 -typing_extensions=3.10.0.2=pyh06a4308_0 -tzdata=2021a=h5d7bf9c_0 -vc=14.2=h21ff451_1 -vs2015_runtime=14.27.29016=h5e58377_2 -wcwidth=0.2.5=pyhd3eb1b0_0 -webencodings=0.5.1=py39haa95532_1 -wheel=0.37.0=pyhd3eb1b0_1 -widgetsnbextension=3.5.2=py39haa95532_0 -wincertstore=0.2=py39haa95532_2 -winpty=0.4.3=4 -xz=5.2.5=h62dcd97_1 -zipp=3.7.0=pyhd3eb1b0_0 -zlib=1.2.11=h8ffe710_1013 -zstd=1.5.0=h6255e5f_0 diff --git a/beamtime/requirements.txt b/beamtime/requirements.txt deleted file mode 100644 index d8eeb75..0000000 --- a/beamtime/requirements.txt +++ /dev/null @@ -1,108 +0,0 @@ -# This file may be used to create an environment using: -# $ conda create --name --file -# platform: win-64 -@EXPLICIT -https://repo.anaconda.com/pkgs/main/win-64/blas-1.0-mkl.conda -https://conda.anaconda.org/conda-forge/win-64/ca-certificates-2021.10.8-h5b45459_0.tar.bz2 -https://repo.anaconda.com/pkgs/main/win-64/icc_rt-2019.0.0-h0cc432a_1.conda -https://repo.anaconda.com/pkgs/main/win-64/intel-openmp-2021.3.0-haa95532_3372.conda -https://repo.anaconda.com/pkgs/main/noarch/tzdata-2021a-h5d7bf9c_0.conda -https://repo.anaconda.com/pkgs/main/win-64/vs2015_runtime-14.27.29016-h5e58377_2.conda -https://repo.anaconda.com/pkgs/main/win-64/mkl-2021.3.0-haa95532_524.conda -https://repo.anaconda.com/pkgs/main/win-64/vc-14.2-h21ff451_1.conda -https://repo.anaconda.com/pkgs/main/win-64/icu-68.1-h6c2663c_0.conda -https://conda.anaconda.org/conda-forge/win-64/jbig-2.1-h8d14728_2003.tar.bz2 -https://repo.anaconda.com/pkgs/main/win-64/jpeg-9d-h2bbff1b_0.conda -https://conda.anaconda.org/conda-forge/win-64/lerc-3.0-h0e60522_0.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/libclang-11.1.0-default_h5c34c98_1.tar.bz2 -https://repo.anaconda.com/pkgs/main/win-64/libdeflate-1.8-h2bbff1b_5.conda -https://conda.anaconda.org/conda-forge/win-64/libiconv-1.16-he774522_0.tar.bz2 -https://repo.anaconda.com/pkgs/main/win-64/libwebp-1.2.0-h2bbff1b_0.conda -https://conda.anaconda.org/conda-forge/win-64/libzlib-1.2.11-h8ffe710_1013.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/lz4-c-1.9.3-h8ffe710_1.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/openssl-1.1.1l-h8ffe710_0.tar.bz2 -https://repo.anaconda.com/pkgs/main/win-64/sqlite-3.36.0-h2bbff1b_0.conda -https://conda.anaconda.org/conda-forge/win-64/tk-8.6.11-h8ffe710_1.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/xz-5.2.5-h62dcd97_1.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/krb5-1.19.2-hbae68bd_2.tar.bz2 -https://repo.anaconda.com/pkgs/main/win-64/python-3.9.7-h6244533_1.conda -https://conda.anaconda.org/conda-forge/win-64/zlib-1.2.11-h8ffe710_1013.tar.bz2 -https://repo.anaconda.com/pkgs/main/noarch/backcall-0.2.0-pyhd3eb1b0_0.tar.bz2 -https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 -https://repo.anaconda.com/pkgs/main/win-64/certifi-2021.10.8-py39haa95532_0.conda -https://repo.anaconda.com/pkgs/main/noarch/colorama-0.4.4-pyhd3eb1b0_0.conda -https://repo.anaconda.com/pkgs/main/win-64/debugpy-1.4.1-py39hd77b12b_0.conda -https://repo.anaconda.com/pkgs/main/noarch/decorator-5.1.0-pyhd3eb1b0_0.conda -https://repo.anaconda.com/pkgs/main/win-64/entrypoints-0.3-py39haa95532_0.conda -https://repo.anaconda.com/pkgs/main/noarch/ipython_genutils-0.2.0-pyhd3eb1b0_1.conda -https://conda.anaconda.org/conda-forge/win-64/libpng-1.6.37-h1d00b33_2.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/libssh2-1.10.0-h680486a_2.tar.bz2 -https://repo.anaconda.com/pkgs/main/win-64/libxml2-2.9.12-h0ad7f3c_0.conda -https://repo.anaconda.com/pkgs/main/win-64/markupsafe-2.0.1-py39h2bbff1b_0.conda -https://repo.anaconda.com/pkgs/main/noarch/nest-asyncio-1.5.1-pyhd3eb1b0_0.conda -https://conda.anaconda.org/conda-forge/noarch/olefile-0.46-pyh9f0ad1d_1.tar.bz2 -https://repo.anaconda.com/pkgs/main/noarch/parso-0.8.2-pyhd3eb1b0_0.conda -https://repo.anaconda.com/pkgs/main/noarch/pickleshare-0.7.5-pyhd3eb1b0_1003.conda -https://repo.anaconda.com/pkgs/main/noarch/pyparsing-2.4.7-pyhd3eb1b0_0.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/python_abi-3.9-2_cp39.tar.bz2 -https://repo.anaconda.com/pkgs/main/noarch/pytz-2021.3-pyhd3eb1b0_0.conda -https://repo.anaconda.com/pkgs/main/win-64/pywin32-228-py39hbaba5e8_1.conda -https://repo.anaconda.com/pkgs/main/win-64/pyzmq-22.2.1-py39hd77b12b_1.conda -https://conda.anaconda.org/conda-forge/noarch/qtpy-1.11.2-pyhd8ed1ab_0.tar.bz2 -https://repo.anaconda.com/pkgs/main/noarch/six-1.16.0-pyhd3eb1b0_0.conda -https://repo.anaconda.com/pkgs/main/win-64/tornado-6.1-py39h2bbff1b_0.conda -https://repo.anaconda.com/pkgs/main/noarch/traitlets-5.1.0-pyhd3eb1b0_0.conda -https://repo.anaconda.com/pkgs/main/noarch/wcwidth-0.2.5-pyhd3eb1b0_0.conda -https://repo.anaconda.com/pkgs/main/noarch/wheel-0.37.0-pyhd3eb1b0_1.conda -https://repo.anaconda.com/pkgs/main/win-64/wincertstore-0.2-py39haa95532_2.conda -https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.0-h6255e5f_0.tar.bz2 -https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 -https://conda.anaconda.org/conda-forge/noarch/cycler-0.10.0-py_2.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/freetype-2.10.4-h546665d_1.tar.bz2 -https://repo.anaconda.com/pkgs/main/win-64/jedi-0.18.0-py39haa95532_1.conda -https://repo.anaconda.com/pkgs/main/win-64/jupyter_core-4.8.1-py39haa95532_0.conda -https://conda.anaconda.org/conda-forge/win-64/kiwisolver-1.3.2-py39h2e07f2f_0.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/libcurl-7.79.1-h789b8ee_1.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/libtiff-4.3.0-hd413186_2.tar.bz2 -https://repo.anaconda.com/pkgs/main/win-64/libxslt-1.1.34-he774522_0.conda -https://conda.anaconda.org/conda-forge/noarch/mako-1.1.5-pyhd8ed1ab_0.tar.bz2 -https://repo.anaconda.com/pkgs/main/noarch/matplotlib-inline-0.1.2-pyhd3eb1b0_2.conda -https://repo.anaconda.com/pkgs/main/win-64/mkl-service-2.4.0-py39h2bbff1b_0.conda -https://repo.anaconda.com/pkgs/main/noarch/prompt-toolkit-3.0.20-pyhd3eb1b0_0.conda -https://conda.anaconda.org/conda-forge/win-64/pyqt5-sip-4.19.18-py39h415ef7b_7.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/pyreadline-2.1-py39hcbf5309_1004.tar.bz2 -https://repo.anaconda.com/pkgs/main/noarch/python-dateutil-2.8.2-pyhd3eb1b0_0.conda -https://conda.anaconda.org/conda-forge/win-64/qt-5.12.9-h5909a2a_4.tar.bz2 -https://repo.anaconda.com/pkgs/main/win-64/setuptools-58.0.4-py39haa95532_0.conda -https://conda.anaconda.org/conda-forge/win-64/hdf5-1.10.6-nompi_h5268f04_1114.tar.bz2 -https://repo.anaconda.com/pkgs/main/noarch/jupyter_client-7.0.1-pyhd3eb1b0_0.conda -https://conda.anaconda.org/conda-forge/win-64/lxml-4.6.3-py39h4fd7cdf_0.tar.bz2 -https://repo.anaconda.com/pkgs/main/win-64/numpy-base-1.21.2-py39h0829f74_0.conda -https://conda.anaconda.org/conda-forge/win-64/openjpeg-2.4.0-hb211442_1.tar.bz2 -https://repo.anaconda.com/pkgs/main/win-64/pillow-8.4.0-py39hd45dc43_0.conda -https://repo.anaconda.com/pkgs/main/win-64/pip-21.2.4-py39haa95532_0.conda -https://repo.anaconda.com/pkgs/main/noarch/pygments-2.10.0-pyhd3eb1b0_0.conda -https://conda.anaconda.org/conda-forge/win-64/pyqt-impl-5.12.3-py39h415ef7b_7.tar.bz2 -https://repo.anaconda.com/pkgs/main/win-64/ipython-7.27.0-py39hd4e2768_0.conda -https://conda.anaconda.org/conda-forge/win-64/pyqtchart-5.12-py39h415ef7b_7.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/pyqtwebengine-5.12.1-py39h415ef7b_7.tar.bz2 -https://repo.anaconda.com/pkgs/main/win-64/ipykernel-6.4.1-py39haa95532_1.conda -https://conda.anaconda.org/conda-forge/win-64/pyqt-5.12.3-py39hcbf5309_7.tar.bz2 -https://repo.anaconda.com/pkgs/main/noarch/qtconsole-5.1.1-pyhd3eb1b0_0.conda -https://conda.anaconda.org/conda-forge/noarch/glymur-0.9.4-pyhd8ed1ab_0.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/fabio-0.12.0-py39h5d4886f_0.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/h5py-3.2.1-nompi_py39hf27771d_100.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/hdf5plugin-3.1.1-py39h71586dd_0.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/matplotlib-3.4.3-py39hcbf5309_1.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/matplotlib-base-3.4.3-py39h581301d_1.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/silx-base-0.15.2-py39h2e25243_0.tar.bz2 -https://conda.anaconda.org/conda-forge/noarch/silx-0.15.2-hd8ed1ab_0.tar.bz2 -https://conda.anaconda.org/conda-forge/noarch/pyfai-0.20.0-hd8ed1ab_0.tar.bz2 -https://repo.anaconda.com/pkgs/main/win-64/bottleneck-1.3.2-py39h7cc1a96_1.conda -https://repo.anaconda.com/pkgs/main/win-64/mkl_fft-1.3.1-py39h277e83a_0.conda -https://repo.anaconda.com/pkgs/main/win-64/mkl_random-1.2.2-py39hf11a4ad_0.conda -https://repo.anaconda.com/pkgs/main/win-64/numpy-1.21.2-py39hfca59bb_0.conda -https://repo.anaconda.com/pkgs/main/win-64/numexpr-2.7.3-py39hb80d3ca_1.conda -https://repo.anaconda.com/pkgs/main/win-64/scipy-1.7.1-py39hbe87c03_2.conda -https://repo.anaconda.com/pkgs/main/win-64/pandas-1.3.3-py39h6214cd6_0.conda -https://conda.anaconda.org/conda-forge/win-64/pyfai-base-0.20.0-py39h2e25243_0.tar.bz2 diff --git a/beamtime/xrd/plot.py b/beamtime/xrd/plot.py deleted file mode 100644 index daa7eaf..0000000 --- a/beamtime/xrd/plot.py +++ /dev/null @@ -1,378 +0,0 @@ -import matplotlib.pyplot as plt -from matplotlib.ticker import (MultipleLocator, FormatStrFormatter,AutoMinorLocator) - -import pandas as pd -import numpy as np -import math - -import ipywidgets as widgets - -import beamtime.xrd as xrd -import beamtime.auxillary as aux -import beamtime.plotting as btp - - -def plot_diffractogram(data, options={}): - ''' Plots a diffractogram. - - Input: - data (dict): Must include path = string to diffractogram data, and plot_kind = (recx, beamline, image)''' - - # Update options - required_options = ['x_vals', 'y_vals', 'ylabel', 'xlabel', 'xunit', 'yunit', 'line', 'scatter', 'xlim', 'ylim', - 'reflections_plot', 'reflections_indices', 'reflections_data', 'plot_kind', 'palettes', 'interactive', 'rc_params', 'format_params'] - - default_options = { - 'x_vals': '2th', - 'y_vals': 'I', - 'ylabel': 'Intensity', 'xlabel': '2theta', - 'xunit': 'deg', 'yunit': 'a.u.', - 'xlim': None, 'ylim': None, - 'line': True, # whether or not to plot diffractogram as a line plot - 'scatter': False, # whether or not to plot individual data points - 'reflections_plot': False, # whether to plot reflections as a plot - 'reflections_indices': False, # whether to plot the reflection indices - 'reflections_data': None, # Should be passed as a list of dictionaries on the form {path: rel_path, reflection_indices: number of indices, colour: [r,g,b], min_alpha: 0-1] - 'plot_kind': None, - 'palettes': [('qualitative', 'Dark2_8')], - 'interactive': False, - 'interactive_session_active': False, - 'rc_params': {}, - 'format_params': {}, - } - - options = aux.update_options(options=options, required_options=required_options, default_options=default_options) - - if not 'diffractogram' in data.keys(): - diffractogram = xrd.io.read_data(data=data, options=options) - data['diffractogram'] = diffractogram - - else: - diffractogram = data['diffractogram'] - - # Sets the xlim if this has not bee specified - if not options['xlim']: - options['xlim'] = [diffractogram[options['x_vals']].min(), diffractogram[options['x_vals']].max()] - - - # Start inteactive session with ipywidgets - if options['interactive']: - options['interactive'] = False - options['interactive_session_active'] = True - plot_diffractogram_interactive(data=data, options=options) - return - - - # Makes a list out of reflections_data if it only passed as a dict, as it will be looped through later - if options['reflections_data']: - if not isinstance(options['reflections_data'], list): - options['reflections_data'] = [options['reflections_data']] - - # Determine number of subplots and height ratios between them - if len(options['reflections_data']) >= 1: - options = determine_grid_layout(options=options) - - - # Prepare plot, and read and process data - - fig, ax = btp.prepare_plot(options=options) - - - # Assign the correct axes - if options['reflections_plot'] or options['reflections_indices']: - - if options['reflections_indices']: - indices_ax = ax[0] - - if options['reflections_plot']: - ref_axes = [axx for axx in ax[range(1,len(options['reflections_data'])+1)]] - - else: - ref_axes = [axx for axx in ax[range(0,len(options['reflections_data']))]] - - ax = ax[-1] - - colours = btp.generate_colours(options['palettes']) - - - if options['line']: - diffractogram.plot(x=options['x_vals'], y=options['y_vals'], ax=ax, c=next(colours), zorder=1) - - if options['scatter']: - ax.scatter(x=diffractogram[options['x_vals']], y = diffractogram[options['y_vals']], c=[(1,1,1,0)], edgecolors=[next(colours)], linewidths=plt.rcParams['lines.markeredgewidth'], zorder=2) #, edgecolors=np.array([next(colours)])) - - - - fig, ax = btp.adjust_plot(fig=fig, ax=ax, options=options) - - - - # Make the reflection plots - if options['reflections_plot'] and options['reflections_data']: - options['xlim'] = ax.get_xlim() - options['to_wavelength'] = data['wavelength'] - - for reference, axis in zip(options['reflections_data'], ref_axes): - plot_reflection_table(data=reference, ax=axis, options=options) - - # Print the reflection indices - if options['reflections_indices'] and options['reflections_data']: - options['xlim'] = ax.get_xlim() - options['to_wavelength'] = data['wavelength'] - - for reference in options['reflections_data']: - plot_reflection_indices(data=reference, ax=indices_ax, options=options) - - - if options['interactive_session_active']: - btp.update_widgets(options=options) - - - return diffractogram, fig, ax - - -def determine_grid_layout(options): - - - nrows = 1 if not options['reflections_indices'] else 2 - - if options['reflections_plot']: - for reference in options['reflections_data']: - nrows += 1 - - options['format_params']['nrows'] = nrows - options['format_params']['grid_ratio_height'] = [1 for i in range(nrows-1)]+[10] - - return options - - - -def plot_diffractogram_interactive(data, options): - - options['widgets'] = { - 'xlim': { - 'w': widgets.FloatRangeSlider(value=[data['diffractogram']['2th'].min(), data['diffractogram']['2th'].max()], min=data['diffractogram']['2th'].min(), max=data['diffractogram']['2th'].max(), step=0.5, layout=widgets.Layout(width='95%')), - '2th_default': {'min': data['diffractogram']['2th'].min(), 'max': data['diffractogram']['2th'].max(), 'value': [data['diffractogram']['2th'].min(), data['diffractogram']['2th'].max()], 'step': 0.5}, - '2th_cuka_default': {'min': data['diffractogram']['2th_cuka'].min(), 'max': data['diffractogram']['2th_cuka'].max(), 'value': [data['diffractogram']['2th_cuka'].min(), data['diffractogram']['2th_cuka'].max()], 'step': 0.5}, - '2th_moka_default': {'min': data['diffractogram']['2th_moka'].min(), 'max': data['diffractogram']['2th_moka'].max(), 'value': [data['diffractogram']['2th_moka'].min(), data['diffractogram']['2th_moka'].max()], 'step': 0.5}, - 'd_default': {'min': data['diffractogram']['d'].min(), 'max': data['diffractogram']['d'].max(), 'value': [data['diffractogram']['d'].min(), data['diffractogram']['d'].max()], 'step': 0.5}, - '1/d_default': {'min': data['diffractogram']['1/d'].min(), 'max': data['diffractogram']['1/d'].max(), 'value': [data['diffractogram']['1/d'].min(), data['diffractogram']['1/d'].max()], 'step': 0.5}, - 'q_default': {'min': data['diffractogram']['q'].min(), 'max': data['diffractogram']['q'].max(), 'value': [data['diffractogram']['q'].min(), data['diffractogram']['q'].max()], 'step': 0.5}, - 'q2_default': {'min': data['diffractogram']['q2'].min(), 'max': data['diffractogram']['q2'].max(), 'value': [data['diffractogram']['q2'].min(), data['diffractogram']['q2'].max()], 'step': 0.5}, - 'q4_default': {'min': data['diffractogram']['q4'].min(), 'max': data['diffractogram']['q4'].max(), 'value': [data['diffractogram']['q4'].min(), data['diffractogram']['q4'].max()], 'step': 0.5}, - 'state': '2th' - } - } - - - if options['reflections_data']: - w = widgets.interactive(btp.ipywidgets_update, func=widgets.fixed(plot_diffractogram), data=widgets.fixed(data), options=widgets.fixed(options), - scatter=widgets.ToggleButton(value=False), - line=widgets.ToggleButton(value=True), - reflections_plot=widgets.ToggleButton(value=True), - reflections_indices=widgets.ToggleButton(value=False), - x_vals=widgets.Dropdown(options=['2th', 'd', '1/d', 'q', 'q2', 'q4', '2th_cuka', '2th_moka'], value='2th', description='X-values'), - xlim=options['widgets']['xlim']['w']) - - else: - w = widgets.interactive(btp.ipywidgets_update, func=widgets.fixed(plot_diffractogram), data=widgets.fixed(data), options=widgets.fixed(options), - scatter=widgets.ToggleButton(value=False), - line=widgets.ToggleButton(value=True), - xlim=options['widgets']['xlim']['w']) - - - display(w) - - -def update_widgets(options): - - for widget in options['widgets'].values(): - - if widget['state'] != options['x_vals']: - for arg in widget[f'{options["x_vals"]}_default']: - setattr(widget['w'], arg, widget[f'{options["x_vals"]}_default'][arg]) - - widget['state'] = options['x_vals'] - - - -def plot_reflection_indices(data, ax, options={}): - ''' Print reflection indices from output generated by VESTA. - - Required contents of data: - path (str): relative path to reflection table file''' - - required_options = ['reflection_indices', 'text_colour', 'hide_indices'] - - default_options = { - 'reflection_indices': 3, # Number of reflection indices to plot, from highest intensity and working its way down - 'text_colour': 'black', - 'hide_indices': False - } - - data = aux.update_options(options=data, required_options=required_options, default_options=default_options) - - if not data['hide_indices']: - reflection_table = xrd.io.load_reflection_table(data=data, options=options) - - if data['reflection_indices'] > 0: - - # Get the data['reflection_indices'] number of highest reflections within the subrange options['xlim'] - reflection_indices = reflection_table.loc[(reflection_table[options['x_vals']] > options['xlim'][0]) & (reflection_table[options['x_vals']] < options['xlim'][1])].nlargest(options['reflection_indices'], 'I') - - # Plot the indices - for i in range(data['reflection_indices']): - if reflection_indices.shape[0] > i: - ax.text(s=f'({reflection_indices["h"].iloc[i]} {reflection_indices["k"].iloc[i]} {reflection_indices["l"].iloc[i]})', x=reflection_indices[options['x_vals']].iloc[i], y=0, fontsize=2.5, rotation=90, va='bottom', ha='center', c=data['text_colour']) - - - if options['xlim']: - ax.set_xlim(options['xlim']) - - ax.axis('off') - - - return - -def plot_reflection_table(data, ax=None, options={}): - ''' Plots a reflection table from output generated by VESTA. - - Required contents of data: - path (str): relative path to reflection table file''' - - required_options = ['reflection_indices', 'reflections_colour', 'min_alpha', 'wavelength', 'format_params', 'rc_params', 'label'] - - default_options = { - 'reflection_indices': 0, # Number of indices to print - 'reflections_colour': [0,0,0], - 'min_alpha': 0, - 'wavelength': 1.54059, # CuKalpha, [Å] - 'format_params': {}, - 'rc_params': {}, - 'label': None - } - - if 'colour' in data.keys(): - options['reflections_colour'] = data['colour'] - if 'min_alpha' in data.keys(): - options['min_alpha'] = data['min_alpha'] - if 'reflection_indices' in data.keys(): - options['reflection_indices'] = data['reflection_indices'] - if 'label' in data.keys(): - options['label'] = data['label'] - if 'wavelength' in data.keys(): - options['wavelength'] = data['wavelength'] - - options = aux.update_options(options=options, required_options=required_options, default_options=default_options) - - - if not ax: - _, ax = btp.prepare_plot(options) - - reflection_table = xrd.io.load_reflection_table(data=data, options=options) - - reflections, intensities = reflection_table[options['x_vals']], reflection_table['I'] - - - - colours = [] - - for ref, intensity in zip(reflections, intensities): - - colour = list(options['reflections_colour']) - rel_intensity = (intensity / intensities.max())*(1-options['min_alpha']) + options['min_alpha'] - colour.append(rel_intensity) - colours.append(colour) - - - - - ax.vlines(x=reflections, ymin=-1, ymax=1, colors=colours, lw=0.5) - ax.set_ylim([-0.5,0.5]) - - - ax.tick_params(which='both', bottom=False, labelbottom=False, right=False, labelright=False, left=False, labelleft=False, top=False, labeltop=False) - - if options['xlim']: - ax.set_xlim(options['xlim']) - - - if options['label']: - xlim_range = ax.get_xlim()[1] - ax.get_xlim()[0] - ylim_avg = (ax.get_ylim()[0]+ax.get_ylim()[1])/2 - - ax.text(s=data['label'], x=(ax.get_xlim()[0]-0.01*xlim_range), y=ylim_avg, ha = 'right', va = 'center') - - - - - -def prettify_labels(label): - - labels_dict = { - '2th': '2$\\theta$', - 'I': 'Intensity' - } - - return labels_dict[label] - -def plot_diffractograms(paths, kind, options=None): - - - fig, ax = prepare_diffractogram_plot(options=options) - - diffractograms = [] - - for path in paths: - diffractogram = xrd.io.read_data(path=path, kind=kind, options=options) - diffractograms.append(diffractogram) - - - required_options = ['type', 'xvals', 'yvals', 'x_offset', 'y_offset', 'normalise', 'normalise_around', 'reverse_order'] - default_options = { - 'type': 'stacked', - 'xvals': '2th', - 'yvals': 'I', - 'x_offset': 0, - 'y_offset': 0.2, - 'normalise': True, - 'normalise_around': None, - 'reverse_order': False - } - - - # If reverse_order is enabled, reverse the order - if options['reverse_order']: - diffractograms = reverse_diffractograms(diffractograms) - - - # If normalise is enbaled, normalise all the diffractograms - if options['normalise']: - if not options['normalise_around']: - for diffractogram in diffractograms: - diffractogram["I"] = diffractogram["I"]/diffractogram["I"].max() - else: - diffractogram["I"] = diffractogram["I"]/diffractogram["I"].loc[(diffractogram['2th'] > options['normalise_around'][0]) & (diffractogram['2th'] < options['normalise_around'][1])].max() - - - if options['type'] == 'stacked': - for diffractogram in diffractograms: - diffractogram.plot(x=options['xvals'], y=options['yvals'], ax=ax) - - - fig, ax = prettify_diffractogram_plot(fig=fig, ax=ax, options=options) - - - return diffractogram, fig, ax - - -def reverse_diffractograms(diffractograms): - - rev_diffractograms = [] - - for i in len(diffractograms): - rev_diffractograms.append(diffractograms.pop()) - - return rev_diffractograms - -#def plot_heatmap(): diff --git a/environment.yml b/environment.yml new file mode 100644 index 0000000..04c88d5 --- /dev/null +++ b/environment.yml @@ -0,0 +1,16 @@ +name: nafuma +channels: + - diffpy + - defaults + - conda-forge +dependencies: + - ipywidgets + - seaborn + - sympy + - matplotlib + - pytest + - numpy + - pandas + - palettable + - pyfai +prefix: C:\Users\rasmusvt\Anaconda3\envs\nafuma diff --git a/beamtime/__init__.py b/nafuma/__init__.py similarity index 100% rename from beamtime/__init__.py rename to nafuma/__init__.py diff --git a/beamtime/auxillary.py b/nafuma/auxillary.py similarity index 81% rename from beamtime/auxillary.py rename to nafuma/auxillary.py index 76ec551..68785f7 100644 --- a/beamtime/auxillary.py +++ b/nafuma/auxillary.py @@ -1,4 +1,5 @@ import json +import numpy as np def update_options(options, required_options, default_options): ''' Takes a dictionary of options along with a list of required options and dictionary of default options, and sets all keyval-pairs of options that is not already defined to the default values''' @@ -37,6 +38,18 @@ def swap_values(dict, key1, key2): -def hello_world2(a=1, b=2): +def ceil(a, roundto=1): - print(f'Halla, MAFAKKAS! a = {a} og b = {b}') \ No newline at end of file + fac = 1/roundto + + a = np.ceil(a*fac) / fac + + return a + +def floor(a, roundto=1): + + fac = 1/roundto + + a = np.floor(a*fac) / fac + + return a \ No newline at end of file diff --git a/beamtime/electrochemistry/__init__.py b/nafuma/electrochemistry/__init__.py similarity index 100% rename from beamtime/electrochemistry/__init__.py rename to nafuma/electrochemistry/__init__.py diff --git a/beamtime/electrochemistry/io.py b/nafuma/electrochemistry/io.py similarity index 100% rename from beamtime/electrochemistry/io.py rename to nafuma/electrochemistry/io.py diff --git a/beamtime/electrochemistry/plot.py b/nafuma/electrochemistry/plot.py similarity index 99% rename from beamtime/electrochemistry/plot.py rename to nafuma/electrochemistry/plot.py index 64b7115..df977a5 100644 --- a/beamtime/electrochemistry/plot.py +++ b/nafuma/electrochemistry/plot.py @@ -5,7 +5,7 @@ import pandas as pd import numpy as np import math -import beamtime.electrochemistry as ec +import nafuma.electrochemistry as ec def plot_gc(path, kind, options=None): diff --git a/beamtime/electrochemistry/unit_tables.py b/nafuma/electrochemistry/unit_tables.py similarity index 100% rename from beamtime/electrochemistry/unit_tables.py rename to nafuma/electrochemistry/unit_tables.py diff --git a/beamtime/pdf/__init__.py b/nafuma/pdf/__init__.py similarity index 100% rename from beamtime/pdf/__init__.py rename to nafuma/pdf/__init__.py diff --git a/beamtime/plotting.py b/nafuma/plotting.py similarity index 80% rename from beamtime/plotting.py rename to nafuma/plotting.py index 3430a2a..2233ab7 100644 --- a/beamtime/plotting.py +++ b/nafuma/plotting.py @@ -1,13 +1,11 @@ -import beamtime.auxillary as aux +import nafuma.auxillary as aux import matplotlib.pyplot as plt -from matplotlib.ticker import (MultipleLocator, FormatStrFormatter,AutoMinorLocator) -from mpl_toolkits.axes_grid.inset_locator import (inset_axes, InsetPosition, mark_inset) +from matplotlib.ticker import (MultipleLocator) import importlib import matplotlib.patches as mpatches from matplotlib.lines import Line2D import matplotlib.lines as mlines -from cycler import cycler import itertools @@ -22,8 +20,15 @@ def prepare_plot(options={}): format_params will determine the size, aspect ratio, resolution etc. of the figure. Should be modified to conform with any requirements from a journal.''' - rc_params = options['rc_params'] - format_params = options['format_params'] + if 'rc_params' in options.keys(): + rc_params = options['rc_params'] + else: + rc_params = {} + + if 'format_params' in options.keys(): + format_params = options['format_params'] + else: + format_params = {} required_format_params = ['single_column_width', 'double_column_width', 'column_type', 'width_ratio', 'aspect_ratio', 'width', 'height', 'compress_width', 'compress_height', 'upscaling_factor', 'dpi', @@ -82,53 +87,14 @@ def prepare_plot(options={}): return fig, axes -def prepare_plots(options={}): - - rc_params = options['rc_params'] - format_params = options['format_params'] - - required_options = ['single_column_width', 'double_column_width', 'column_type', 'width_ratio', 'aspect_ratio', 'compress_width', 'compress_height', 'upscaling_factor', 'dpi'] - - default_options = { - 'single_column_width': 8.3, - 'double_column_width': 17.1, - 'column_type': 'single', - 'width_ratio': '1:1', - 'aspect_ratio': '1:1', - 'compress_width': 1, - 'compress_height': 1, - 'upscaling_factor': 1.0, - 'dpi': 600, - } - - format_params = aux.update_options(format_params, required_options, default_options) - - - # Reset run commands - plt.rcdefaults() - - # Update run commands if any is passed (will pass an empty dictionary if not passed) - update_rc_params(rc_params) - - width = determine_width(format_params) - height = determine_height(format_params, width) - width, height = scale_figure(options=format_params, width=width, height=height) - - - if options['plot_kind'] == 'relative': - fig, axes = plt.subplots(nrows=1, ncols=options['number_of_frames'], figsize=(width,height), facecolor='w', dpi=format_params['dpi']) - - elif options['plot_kind'] == 'absolute': - fig, axes = plt.subplots(nrows=2, ncols=options['number_of_frames'], figsize=(width,height), gridspec_kw={'height_ratios': [1,5]}, facecolor='w', dpi=format_params['dpi']) - - return fig, axes - def adjust_plot(fig, ax, options): ''' A general function to adjust plot according to contents of the options-dictionary ''' required_options = [ 'plot_kind', + 'xlabel', 'ylabel', + 'xunit', 'yunit', 'hide_x_labels', 'hide_y_labels', 'hide_x_ticklabels', 'hide_y_ticklabels', 'hide_x_ticks', 'hide_y_ticks', @@ -143,6 +109,8 @@ def adjust_plot(fig, ax, options): default_options = { 'plot_kind': None, # defaults to None, but should be utilised when requiring special formatting for a particular plot + 'xlabel': None, 'ylabel': None, + 'xunit': None, 'yunit': None, 'hide_x_labels': False, 'hide_y_labels': False, # Whether the main labels on the x- and/or y-axes should be hidden 'hide_x_ticklabels': False, 'hide_y_ticklabels': False, # Whether ticklabels on the x- and/or y-axes should be hidden 'hide_x_ticks': False, 'hide_y_ticks': False, # Whether the ticks on the x- and/or y-axes should be hidden @@ -182,10 +150,10 @@ def adjust_plot(fig, ax, options): ax.xaxis.set_minor_locator(MultipleLocator(options['x_tick_locators'][1])) - # THIS NEEDS REWORK FOR IT TO FUNCTION PROPERLY! - if options['xticks']: - ax.set_xticks(np.arange(plot_data['start'], plot_data['end']+1)) - ax.set_xticklabels(options['xticks']) + # FIXME THIS NEEDS REWORK FOR IT TO FUNCTION PROPERLY! + #if options['xticks']: + # ax.set_xticks(np.arange(plot_data['start'], plot_data['end']+1)) + # ax.set_xticklabels(options['xticks']) # else: # ax.set_xticks(np.arange(plot_data['start'], plot_data['end']+1)) # ax.set_xticklabels([x/2 for x in np.arange(plot_data['start'], plot_data['end']+1)]) @@ -307,17 +275,6 @@ def ipywidgets_update(func, data, options={}, **kwargs): func(data=data, options=options) -def update_widgets(options): - - for widget in options['widgets'].values(): - - if widget['state'] != options['x_vals']: - for arg in widget[f'{options["x_vals"]}_default']: - setattr(widget['w'], arg, widget[f'{options["x_vals"]}_default'][arg]) - - widget['state'] = options['x_vals'] - - def determine_width(format_params): ''' ''' @@ -366,16 +323,20 @@ def update_rc_params(rc_params): plt.rcParams.update({key: rc_params[key]}) -def generate_colours(palettes): +def generate_colours(palettes, kind=None): - # Creates a list of all the colours that is passed in the colour_cycles argument. Then makes cyclic iterables of these. - colour_collection = [] - for palette in palettes: - mod = importlib.import_module("palettable.colorbrewer.%s" % palette[0]) - colour = getattr(mod, palette[1]).mpl_colors - colour_collection = colour_collection + colour + if kind == 'single': + colour_cycle = itertools.cycle(palettes) - colour_cycle = itertools.cycle(colour_collection) + else: + # Creates a list of all the colours that is passed in the colour_cycles argument. Then makes cyclic iterables of these. + colour_collection = [] + for palette in palettes: + mod = importlib.import_module("palettable.colorbrewer.%s" % palette[0]) + colour = getattr(mod, palette[1]).mpl_colors + colour_collection = colour_collection + colour + + colour_cycle = itertools.cycle(colour_collection) return colour_cycle \ No newline at end of file diff --git a/beamtime/xrd/test.txt b/nafuma/test/__init__.py similarity index 100% rename from beamtime/xrd/test.txt rename to nafuma/test/__init__.py diff --git a/nafuma/test/pytest.ini b/nafuma/test/pytest.ini new file mode 100644 index 0000000..c317621 --- /dev/null +++ b/nafuma/test/pytest.ini @@ -0,0 +1,9 @@ +# pytest.ini + +[pytest] +minversion = 6.0 +testpaths = + . + +filterwarnings = + ignore::DeprecationWarning diff --git a/nafuma/test/test_auxillary.py b/nafuma/test/test_auxillary.py new file mode 100644 index 0000000..4cb9cd1 --- /dev/null +++ b/nafuma/test/test_auxillary.py @@ -0,0 +1,78 @@ +import nafuma.auxillary as aux +import os + +def test_swap_values(): + + + dict = {'test1': 1, 'test2': 2} + key1 = 'test1' + key2 = 'test2' + + oldval1 = dict[key1] + oldval2 = dict[key2] + + new_dict = aux.swap_values(dict=dict, key1=key1, key2=key2) + + assert (dict[key1] == oldval2) and (dict[key2] == oldval1) + + +def test_ceil() -> None: + + assert aux.ceil(1.05, 0.5) == 1.5 + assert aux.ceil(1.05, 1) == 2.0 + assert aux.ceil(1.1, 0.2) == 1.2 + + +def test_floor() -> None: + + assert aux.floor(2.02, 1) == 2.0 + assert aux.floor(2.02, 0.01) == 2.02 + assert aux.floor(2.013, 0.01) == 2.01 + + + +def test_options() -> None: + + + options = {} + required_options = ['test1', 'test2', 'test3', 'test4'] + default_options = { + 'test1': 1, + 'test2': 2, + 'test3': 3, + 'test4': 4, + 'test5': 5, + } + + + options = aux.update_options(options=options, required_options=required_options, default_options=default_options) + + assert options['test1'] == default_options['test1'] + assert len(options.items()) == len(required_options) + assert 'test5' not in options.keys() + + +def test_save_options() -> None: + + options = {'test1': 1, 'test2': 2} + path = 'tmp.dat' + + aux.save_options(options, path) + + assert os.path.isfile(path) + + os.remove(path) + + +def test_load_options() -> None: + + options = {'test1': 1, 'test2': 2} + path = 'tmp.dat' + + aux.save_options(options, path) + + loaded_options = aux.load_options(path) + + assert (loaded_options['test1'] == 1) and (loaded_options['test2'] == 2) + + os.remove(path) \ No newline at end of file diff --git a/nafuma/test/test_plotting.py b/nafuma/test/test_plotting.py new file mode 100644 index 0000000..e1af0b8 --- /dev/null +++ b/nafuma/test/test_plotting.py @@ -0,0 +1,181 @@ +import nafuma.plotting as btp +from cycler import cycler +import itertools +import numpy as np + +import matplotlib.pyplot as plt +import matplotlib as mpl + + +def test_generate_colours() -> None: + + assert type(btp.generate_colours('black', kind='single')) == itertools.cycle + + palettes = [('qualitative', 'Dark2_8')] + colour_cycle = btp.generate_colours(palettes) + + assert type(colour_cycle) == itertools.cycle + + + # Test that it actually loaded 8 colours when given a set of 8 colours to + + same_colour = None + for i in range(10): + colour = next(colour_cycle) + if i == 0: + first_colour = colour + + if colour == first_colour: + repeat_colour_index = i + + + assert repeat_colour_index == 8 + + + + +def test_update_rc_params() -> None: + + rc_params = { + 'lines.linewidth': 100 + } + + prev_params = plt.rcParams['lines.linewidth'] + + # Update run commands if any is passed (will pass an empty dictionary if not passed) + btp.update_rc_params(rc_params) + + new_params = plt.rcParams['lines.linewidth'] + + assert new_params == 100 + assert prev_params != new_params + + + # Reset run commands + plt.rcdefaults() + + + +def test_scale_figure() -> None: + + width, height = 1, 1 + + format_params = { + 'upscaling_factor': 2, + 'compress_width': 1, + 'compress_height': 1 + } + + width1, height1 = btp.scale_figure(format_params=format_params, width=width, height=height) + + assert width1 == 2 and height1 == 2 + + format_params = { + 'upscaling_factor': 1, + 'compress_width': 0.5, + 'compress_height': 1 + } + + width2, height2 = btp.scale_figure(format_params=format_params, width=width, height=height) + + assert width2 == 0.5 and height2 == 1 + + format_params = { + 'upscaling_factor': 2, + 'compress_width': 0.5, + 'compress_height': 0.2 + } + + width2, height2 = btp.scale_figure(format_params=format_params, width=width, height=height) + + assert width2 == 1 and height2 == 0.4 + + +def test_determine_width() -> None: + + conversion_cm_inch = 0.3937008 # cm to inch + + format_params = { + 'column_type': 'single', + 'single_column_width': 5, + 'double_column_width': 10, + 'width_ratio': '1:1' + } + + assert np.round(btp.determine_width(format_params),6) == np.round(5*conversion_cm_inch,6) + + format_params['column_type'] = 'double' + + assert np.round(btp.determine_width(format_params), 6) == np.round(10*conversion_cm_inch, 6) + + + format_params['column_type'] = 'single' + format_params['width_ratio'] = '1:2' + + assert np.round(btp.determine_width(format_params), 6) == np.round(2.5*conversion_cm_inch, 6) + +def test_determine_height() -> None: + + + width = 1 + + format_params = { + 'aspect_ratio': '1:1' + } + + assert btp.determine_height(format_params=format_params, width=width) == 1 + + format_params['aspect_ratio'] = '3:1' + + assert (btp.determine_height(format_params=format_params, width=width) - 0.333333333333333) < 10e-7 + + assert True + + + +def test_prepare_plot() -> None: + + fig, ax = btp.prepare_plot() + + assert type(fig) == plt.Figure + assert fig.get_dpi() == 600 + assert ax.get_xlim() == (0.0, 1.0) + + + +def test_adjust_plot() -> None: + + fig, ax = btp.prepare_plot() + + options = { + 'xlim': (0.0, 2.0), + 'title': 'Test' + } + + fig, ax = btp.adjust_plot(fig, ax, options) + + + assert ax.get_xlim() == (0.0, 2.0) + assert ax.get_title() == 'Test' + + + +def test_ipywidgets_update() -> None: + + + + def test_func(data, options): + test1 = options['test1'] + test2 = options['test2'] + + assert type(data) == dict + assert test1 == 1 + assert test2 == 2 + + + + data = {} + options = {} + + btp.ipywidgets_update(func=test_func, data=data, options=options, test1=1, test2=2) + diff --git a/nafuma/test/xrd/test_io.py b/nafuma/test/xrd/test_io.py new file mode 100644 index 0000000..e69de29 diff --git a/nafuma/test/xrd/test_plot.py b/nafuma/test/xrd/test_plot.py new file mode 100644 index 0000000..e69de29 diff --git a/beamtime/xanes/__init__.py b/nafuma/xanes/__init__.py similarity index 100% rename from beamtime/xanes/__init__.py rename to nafuma/xanes/__init__.py diff --git a/beamtime/xanes/calib.py b/nafuma/xanes/calib.py similarity index 96% rename from beamtime/xanes/calib.py rename to nafuma/xanes/calib.py index 7a0ecb5..ad3443f 100644 --- a/beamtime/xanes/calib.py +++ b/nafuma/xanes/calib.py @@ -24,6 +24,7 @@ def finding_edge(df): edge='Ni' return(edge) +<<<<<<< HEAD:beamtime/xanes/calib.py #def pre_edge_subtraction(df,filenames, options={}): def test(innmat): df_test= xas.io.put_in_dataframe(innmat) @@ -40,6 +41,11 @@ def pre_edge_subtraction(path, options={}): filenames = xas.io.get_filenames(path) df= xas.io.put_in_dataframe(path) edge=finding_edge(df) +======= +def split_xanes_scan(filename, destination=None, replace=False): + #root is the path to the beamtime-folder + #destination should be the path to the processed data +>>>>>>> master:nafuma/xanes/calib.py #Defining the end of the region used to define the background, thus start of the edge #implement widget diff --git a/beamtime/xanes/io.py b/nafuma/xanes/io.py similarity index 100% rename from beamtime/xanes/io.py rename to nafuma/xanes/io.py diff --git a/beamtime/xrd/__init__.py b/nafuma/xrd/__init__.py similarity index 100% rename from beamtime/xrd/__init__.py rename to nafuma/xrd/__init__.py diff --git a/beamtime/xrd/io.py b/nafuma/xrd/io.py similarity index 53% rename from beamtime/xrd/io.py rename to nafuma/xrd/io.py index 6b0bb7a..b3f3951 100644 --- a/beamtime/xrd/io.py +++ b/nafuma/xrd/io.py @@ -9,7 +9,7 @@ import zipfile import xml.etree.ElementTree as ET -import beamtime.auxillary as aux +import nafuma.auxillary as aux def get_image_array(path): @@ -27,7 +27,7 @@ def get_image_headers(path): return image.header -def integrate_1d(data, options={}): +def integrate_1d(data, options={}, index=0): ''' Integrates an image file to a 1D diffractogram. Required content of data: @@ -40,10 +40,12 @@ def integrate_1d(data, options={}): df: DataFrame contianing 1D diffractogram if option 'return' is True ''' - required_options = ['unit', 'save', 'save_filename', 'save_extension', 'save_folder', 'overwrite'] + required_options = ['unit', 'nbins', 'save', 'save_filename', 'save_extension', 'save_folder', 'overwrite', 'extract_folder'] default_options = { 'unit': '2th_deg', + 'nbins': 3000, + 'extract_folder': 'tmp', 'save': False, 'save_filename': None, 'save_extension': '_integrated.xy', @@ -51,51 +53,57 @@ def integrate_1d(data, options={}): 'overwrite': False} options = aux.update_options(options=options, required_options=required_options, default_options=default_options) + + if not isinstance(data['path'], list): + data['path'] = [data['path']] # Get image array from filename if not passed if 'image' not in data.keys(): - data['image'] = get_image_array(data['path']) + data['image'] = get_image_array(data['path'][index]) # Instanciate the azimuthal integrator from pyFAI from the calibrant (.poni-file) ai = pyFAI.load(data['calibrant']) # Determine filename - filename = make_filename(data=data, options=options) + filename = make_filename(options=options, path=data['path'][index]) # Make save_folder if this does not exist already - if not os.path.isdir(options['save_folder']): - os.makedirs(options['save_folder']) + if not os.path.isdir(options['extract_folder']): + os.makedirs(options['extract_folder']) - res = ai.integrate1d(data['image'], data['nbins'], unit=options['unit'], filename=filename) + res = ai.integrate1d(data['image'], options['nbins'], unit=options['unit'], filename=filename) - data['path'] = filename - diffractogram = read_xy(data=data, options=options) + data['path'][index] = filename + diffractogram, wavelength = read_xy(data=data, options=options, index=index) if not options['save']: os.remove(filename) - shutil.rmtree('tmp') + shutil.rmtree(f'tmp') + + + # Reset this option + options['save_folder'] = None - return diffractogram + return diffractogram, wavelength -def make_filename(data, options): +def make_filename(options, path=None): # Define save location for integrated diffractogram data if not options['save']: - options['save_folder'] = 'tmp' - filename = os.path.join(options['save_folder'], 'tmp_diff.dat') + filename = os.path.join(options['extract_folder'], 'tmp_diff.dat') elif options['save']: # Case 1: No filename is given. if not options['save_filename']: # If a path is given instead of an image array, the path is taken as the trunk of the savename - if data['path']: + if path: # Make filename by joining the save_folder, the filename (with extension deleted) and adding the save_extension - filename = os.path.join(options['save_folder'], os.path.split(data['path'])[-1].split('.')[0] + options['save_extension']) + filename = os.path.join(options['save_folder'], os.path.split(path)[-1].split('.')[0] + options['save_extension']) else: # Make filename just "integrated.dat" in the save_folder filename = os.path.join(options['save_folder'], 'integrated.xy') @@ -141,7 +149,7 @@ def average_images(images): image_arrays = [] for image in images: - image_array = xrd.io.get_image_array(image) + image_array = get_image_array(image) image_arrays.append(image_array) @@ -176,7 +184,10 @@ def view_integrator(calibrant): -def read_brml(data, options={}): +def read_brml(data, options={}, index=0): + + + # FIXME: Can't read RECX1-data, apparently must be formatted differently from RECX2. Check the RawData-files and compare between the two files. required_options = ['extract_folder', 'save_folder'] @@ -194,7 +205,7 @@ def read_brml(data, options={}): # Extract the RawData0.xml file from the brml-file - with zipfile.ZipFile(data['path'], 'r') as brml: + with zipfile.ZipFile(data['path'][index], 'r') as brml: for info in brml.infolist(): if "RawData" in info.filename: brml.extract(info.filename, options['extract_folder']) @@ -213,31 +224,66 @@ def read_brml(data, options={}): for chain in root.findall('./DataRoutes/DataRoute'): - for scantype in chain.findall('ScanInformation/ScanMode'): - if scantype.text == 'StillScan': - if chain.get('Description') == 'Originally measured data.': - for scandata in chain.findall('Datum'): + # Get the scan type to be able to handle different data formats + scantype = chain.findall('ScanInformation')[0].get('VisibleName') + + # Check if the chain is the right one to extract the data from + if chain.get('Description') == 'Originally measured data.': + + + if scantype == 'TwoTheta': + for scandata in chain.findall('Datum'): + scandata = scandata.text.split(',') + twotheta, intensity = float(scandata[2]), float(scandata[3]) + + if twotheta > 0: + diffractogram.append({'2th': twotheta, 'I': intensity}) + + elif scantype == 'Coupled TwoTheta/Theta': + for scandata in chain.findall('Datum'): + scandata = scandata.text.split(',') + twotheta, intensity = float(scandata[2]), float(scandata[4]) + + if twotheta > 0: + diffractogram.append({'2th': twotheta, 'I': intensity}) + + elif scantype == 'Still (Eiger2R_500K (1D mode))': + + start = float(chain.findall('ScanInformation/ScaleAxes/ScaleAxisInfo/Start')[0].text) + stop = float(chain.findall('ScanInformation/ScaleAxes/ScaleAxisInfo/Stop')[0].text) + + + + + for scandata in chain.findall('Datum'): scandata = scandata.text.split(',') - scandata = [float(i) for i in scandata] - twotheta, intensity = float(scandata[2]), float(scandata[3]) + raw = [float(i) for i in scandata] - - else: - if chain.get('Description') == 'Originally measured data.': - for scandata in chain.findall('Datum'): - scandata = scandata.text.split(',') - twotheta, intensity = float(scandata[2]), float(scandata[3]) - - if twotheta > 0: - diffractogram.append({'2th': twotheta, 'I': intensity}) + intensity = [] + for r in raw: + if r > 601: + intensity.append(r) + + intensity = np.array(intensity) + + + + + twotheta = np.linspace(start, stop, len(intensity)) + + diffractogram = {'2th': twotheta, 'I': intensity} - if 'wavelength' not in data.keys(): - for chain in root.findall('./FixedInformation/Instrument/PrimaryTracks/TrackInfoData/MountedOptics/InfoData/Tube/WaveLengthAlpha1'): - data['wavelength'] = float(chain.attrib['Value']) + + #if 'wavelength' not in data.keys(): + # Find wavelength + for chain in root.findall('./FixedInformation/Instrument/PrimaryTracks/TrackInfoData/MountedOptics/InfoData/Tube/WaveLengthAlpha1'): + wavelength = float(chain.attrib['Value']) + diffractogram = pd.DataFrame(diffractogram) + @@ -249,15 +295,16 @@ def read_brml(data, options={}): - return diffractogram + return diffractogram, wavelength -def read_xy(data, options={}): +def read_xy(data, options={}, index=0): - if 'wavelength' not in data.keys(): - find_wavelength_from_xy(data=data) + #if 'wavelength' not in data.keys(): + # Get wavelength from scan + wavelength = find_wavelength_from_xy(path=data['path'][index]) - with open(data['path'], 'r') as f: + with open(data['path'][index], 'r') as f: position = 0 current_line = f.readline() @@ -276,37 +323,89 @@ def read_xy(data, options={}): diffractogram.columns = ['2th', 'I', 'sigma'] - return diffractogram + return diffractogram, wavelength -def read_data(data, options={}): +def read_data(data, options={}, index=0): beamline_extensions = ['mar3450', 'edf', 'cbf'] - file_extension = data['path'].split('.')[-1] + file_extension = data['path'][index].split('.')[-1] if file_extension in beamline_extensions: - diffractogram = integrate_1d(data=data, options=options) + diffractogram, wavelength = integrate_1d(data=data, options=options, index=index) elif file_extension == 'brml': - diffractogram = read_brml(data=data, options=options) + diffractogram, wavelength = read_brml(data=data, options=options, index=index) elif file_extension in['xy', 'xye']: - diffractogram = read_xy(data=data, options=options) + diffractogram, wavelength = read_xy(data=data, options=options, index=index) - diffractogram = translate_wavelengths(data=diffractogram, wavelength=data['wavelength']) + + + if options['offset'] or options['normalise']: + # Make copy of the original intensities before any changes are made through normalisation or offset, to easily revert back if need to update. + diffractogram['I_org'] = diffractogram['I'] + diffractogram['2th_org'] = diffractogram['2th'] + + diffractogram = apply_offset(diffractogram, wavelength, index, options) + + + diffractogram = translate_wavelengths(data=diffractogram, wavelength=wavelength) + + return diffractogram, wavelength + + +def apply_offset(diffractogram, wavelength, index, options): + + if 'current_offset_y' not in options.keys(): + options['current_offset_y'] = options['offset_y'] + else: + if options['current_offset_y'] != options['offset_y']: + options['offset_change'] = True + + options['current_offset_y'] = options['offset_y'] + + options['current_offset_x'] = options['offset_x'] + + + + #Apply offset along y-axis + diffractogram['I'] = diffractogram['I_org'] # Reset intensities + + if options['normalise']: + diffractogram['I'] = diffractogram['I'] / diffractogram['I'].max() + + diffractogram['I'] = diffractogram['I'] + index*options['offset_y'] + + # Apply offset along x-axis + relative_shift = (wavelength / 1.54059)*options['offset_x'] # Adjusts the offset-factor to account for wavelength, so that offset_x given is given in 2th_cuka-units + diffractogram['2th'] = diffractogram['2th_org'] + diffractogram['2th'] = diffractogram['2th'] + index*relative_shift + return diffractogram - +def revert_offset(diffractogram,which=None): + if which == 'both': + diffractogram['2th'] = diffractogram['2th_org'] + diffractogram['I'] = diffractogram['I_org'] + + if which == 'y': + diffractogram['I'] = diffractogram['I_org'] + + if which == 'x': + diffractogram['2th'] = diffractogram['2th_org'] -def load_reflection_table(data, options={}): + return diffractogram - required_options = ['wavelength', 'to_wavelength'] +def load_reflection_table(data: dict, reflections_params: dict, options={}): + + required_options = ['ref_wavelength', 'to_wavelength'] default_options = { - 'wavelength': 1.54059, + 'ref_wavelength': 1.54059, 'to_wavelength': None } @@ -314,12 +413,12 @@ def load_reflection_table(data, options={}): # VESTA outputs the file with a header that has a space between the parameter and units - so there is some extra code to rectify the issue # that ensues from this formatting - reflections = pd.read_csv(data['path'], delim_whitespace=True) + reflections = pd.read_csv(reflections_params['path'], delim_whitespace=True) # Remove the extra column that appears from the headers issue reflections.drop(reflections.columns[-1], axis=1, inplace=True) - with open(data['path'], 'r') as f: + with open(reflections_params['path'], 'r') as f: line = f.readline() headers = line.split() @@ -333,15 +432,32 @@ def load_reflection_table(data, options={}): # Set the new modified headers as the headers of reflections.columns = headers - reflections = translate_wavelengths(data=reflections, wavelength=options['wavelength'], to_wavelength=options['to_wavelength']) + reflections = translate_wavelengths(data=reflections, wavelength=options['ref_wavelength'], to_wavelength=options['to_wavelength']) - #print(reflections) + if 'heatmap' in data.keys(): + + start_2th, stop_2th = data['diffractogram'][0]['2th'].min(), data['diffractogram'][0]['2th'].max() + len_2th = stop_2th - start_2th + #print(start_2th, stop_2th, len_2th) + + start_heatmap, stop_heatmap = 0, data['heatmap'].shape[1] + len_heatmap = stop_heatmap - start_heatmap + #print(start_heatmap, stop_heatmap, len_heatmap) + + scale = len_heatmap/len_2th + + #print(scale) + #print(stop_2th * scale) + + reflections['heatmap'] = (reflections['2th']-start_2th) * scale return reflections -def translate_wavelengths(data, wavelength, to_wavelength=None): +def translate_wavelengths(data: pd.DataFrame, wavelength: float, to_wavelength=None) -> pd.DataFrame: + # FIXME Somewhere here there is an invalid arcsin-argument. Not sure where. + pd.options.mode.chained_assignment = None # Translate to CuKalpha @@ -378,8 +494,7 @@ def translate_wavelengths(data, wavelength, to_wavelength=None): if to_wavelength: - - if to_wavelength > cuka: + if to_wavelength >= cuka: max_2th = 2*np.arcsin(cuka/to_wavelength) * 180/np.pi else: max_2th = data['2th_cuka'].max() @@ -395,19 +510,24 @@ def translate_wavelengths(data, wavelength, to_wavelength=None): -def find_wavelength_from_xy(data): +def find_wavelength_from_xy(path): + - wavelength_dict = {'Cu': 1.54059, 'Mo': 0.71073} - with open(data['path'], 'r') as f: + with open(path, 'r') as f: lines = f.readlines() for line in lines: + # For .xy-files output from EVA if 'Anode' in line: anode = line.split()[8].strip('"') - data['wavelength'] = wavelength_dict[anode] + wavelength = wavelength_dict[anode] + # For .xy-files output from pyFAI integration elif 'Wavelength' in line: - data['wavelength'] = float(line.split()[2])*10**10 + wavelength = float(line.split()[2])*10**10 + + + return wavelength diff --git a/nafuma/xrd/plot.py b/nafuma/xrd/plot.py new file mode 100644 index 0000000..8dd6a27 --- /dev/null +++ b/nafuma/xrd/plot.py @@ -0,0 +1,672 @@ +import seaborn as sns +import matplotlib.pyplot as plt +from matplotlib.ticker import (MultipleLocator, FormatStrFormatter,AutoMinorLocator) + +import pandas as pd +import numpy as np +import math + +import ipywidgets as widgets +from IPython.display import display + +import nafuma.xrd as xrd +import nafuma.auxillary as aux +import nafuma.plotting as btp + + +def plot_diffractogram(data, options={}): + ''' Plots a diffractogram. + + Input: + data (dict): Must include path = string to diffractogram data, and plot_kind = (recx, beamline, image)''' + + # Update options + required_options = ['x_vals', 'y_vals', 'ylabel', 'xlabel', 'xunit', 'yunit', 'line', 'scatter', 'xlim', 'ylim', 'normalise', 'offset', 'offset_x', 'offset_y', 'offset_change', + 'reflections_plot', 'reflections_indices', 'reflections_data', 'heatmap', 'cmap', 'plot_kind', 'palettes', 'interactive', 'rc_params', 'format_params', 'interactive_session_active'] + + default_options = { + 'x_vals': '2th', + 'y_vals': 'I', + 'ylabel': 'Intensity', 'xlabel': '2theta', + 'xunit': 'deg', 'yunit': 'a.u.', + 'xlim': None, 'ylim': None, + 'normalise': True, + 'offset': True, + 'offset_x': 0, + 'offset_y': 1, + 'offset_change': False, + 'line': True, # whether or not to plot diffractogram as a line plot + 'scatter': False, # whether or not to plot individual data points + 'reflections_plot': False, # whether to plot reflections as a plot + 'reflections_indices': False, # whether to plot the reflection indices + 'reflections_data': None, # Should be passed as a list of dictionaries on the form {path: rel_path, reflection_indices: number of indices, colour: [r,g,b], min_alpha: 0-1] + 'heatmap': False, + 'cmap': 'viridis', + 'plot_kind': None, + 'palettes': [('qualitative', 'Dark2_8')], + 'interactive': False, + 'interactive_session_active': False, + 'rc_params': {}, + 'format_params': {}, + } + + if 'offset_y' not in options.keys(): + if len(data['path']) > 10: + default_options['offset_y'] = 0.05 + + options = aux.update_options(options=options, required_options=required_options, default_options=default_options) + #options['current_offset_y'] = options['offset_y'] + + # Convert data['path'] to list to allow iteration over this to accommodate both single and multiple diffractograms + if not isinstance(data['path'], list): + data['path'] = [data['path']] + + + + # Check if there is some data stored already, load in data if not. This speeds up replotting in interactive mode. + if not 'diffractogram' in data.keys(): + # Initialise empty list for diffractograms and wavelengths + data['diffractogram'] = [None for _ in range(len(data['path']))] + data['wavelength'] = [None for _ in range(len(data['path']))] + + for index in range(len(data['path'])): + diffractogram, wavelength = xrd.io.read_data(data=data, options=options, index=index) + + data['diffractogram'][index] = diffractogram + data['wavelength'][index] = wavelength + + # Sets the xlim if this has not bee specified + if not options['xlim']: + options['xlim'] = [data['diffractogram'][0][options['x_vals']].min(), data['diffractogram'][0][options['x_vals']].max()] + + # Generate heatmap data + data['heatmap'], data['heatmap_xticks'], data['heatmap_xticklabels'] = generate_heatmap(data=data, options=options) + options['heatmap_loaded'] = True + + if options['heatmap']: + options['xlim'] = options['heatmap_xlim'] + + else: + if not isinstance(data['diffractogram'], list): + data['diffractogram'] = [data['diffractogram']] + data['wavelength'] = [data['wavelength']] + + + + if options['interactive_session_active']: + if options['offset']: + if (options['offset_x'] != options['current_offset_x']) or (options['offset_y'] != options['current_offset_y']): + for i, (diff, wl) in enumerate(zip(data['diffractogram'], data['wavelength'])): + xrd.io.apply_offset(diff, wl, i, options) + + + # Start inteactive session with ipywidgets. Disables options['interactive'] in order for the interactive loop to not start another interactive session + if options['interactive']: + options['interactive'] = False + options['interactive_session_active'] = True + plot_diffractogram_interactive(data=data, options=options) + return + + + # Makes a list out of reflections_data if it only passed as a dict, as it will be looped through later + if options['reflections_data']: + if not isinstance(options['reflections_data'], list): + options['reflections_data'] = [options['reflections_data']] + + # Determine number of subplots and height ratios between them + if len(options['reflections_data']) >= 1: + options = determine_grid_layout(options=options) + + + # Prepare plot, and read and process data + fig, ax = btp.prepare_plot(options=options) + + + # Assign the correct axes + if options['reflections_plot'] or options['reflections_indices']: + + if options['reflections_indices']: + indices_ax = ax[0] + + if options['reflections_plot']: + ref_axes = [axx for axx in ax[range(1,len(options['reflections_data'])+1)]] + + else: + ref_axes = [axx for axx in ax[range(0,len(options['reflections_data']))]] + + ax = ax[-1] + + if len(data['path']) < 10: + colours = btp.generate_colours(options['palettes']) + else: + colours = btp.generate_colours(['black'], kind='single') + + if options['heatmap']: + sns.heatmap(data['heatmap'], cmap=options['cmap'], cbar=False, ax=ax) + ax.set_xticks(data['heatmap_xticks'][options['x_vals']]) + ax.set_xticklabels(data['heatmap_xticklabels'][options['x_vals']]) + ax.tick_params(axis='x', which='minor', bottom=False, top=False) + + else: + for diffractogram in data['diffractogram']: + if options['line']: + diffractogram.plot(x=options['x_vals'], y=options['y_vals'], ax=ax, c=next(colours), zorder=1) + + if options['scatter']: + ax.scatter(x=diffractogram[options['x_vals']], y = diffractogram[options['y_vals']], c=[(1,1,1,0)], edgecolors=[next(colours)], linewidths=plt.rcParams['lines.markeredgewidth'], zorder=2) #, edgecolors=np.array([next(colours)])) + + + + fig, ax = btp.adjust_plot(fig=fig, ax=ax, options=options) + + + + # Make the reflection plots. By default, the wavelength of the first diffractogram will be used for these. + if options['reflections_plot'] and options['reflections_data']: + options['xlim'] = ax.get_xlim() + options['to_wavelength'] = data['wavelength'][0] + + for reflections_params, axis in zip(options['reflections_data'], ref_axes): + plot_reflection_table(data=data, reflections_params=reflections_params, ax=axis, options=options) + + # Print the reflection indices. By default, the wavelength of the first diffractogram will be used for this. + if options['reflections_indices'] and options['reflections_data']: + options['xlim'] = ax.get_xlim() + options['to_wavelength'] = data['wavelength'][0] + + for reflections_params in options['reflections_data']: + plot_reflection_indices(data=data, reflections_params=reflections_params, ax=indices_ax, options=options) + + + if options['interactive_session_active']: + options['current_y_offset'] = options['widget'].kwargs['offset_y'] + update_widgets(data=data, options=options) + + + + return data['diffractogram'], fig, ax + + + +def generate_heatmap(data, options={}): + + required_options = ['x_tick_locators'] + + default_options = { + 'x_tick_locators': [0.5, 0.1] + } + + options = aux.update_options(options=options, required_options=required_options, default_options=default_options) + + twotheta = [] + intensities = [] + scans = [] + + for i, d in enumerate(data['diffractogram']): + twotheta = np.append(twotheta, d['2th'].to_numpy()) + intensities = np.append(intensities, d['I'].to_numpy()) + scans = np.append(scans, np.full(len(d['2th'].to_numpy()), int(i))) + + + heatmap = pd.DataFrame({'2th': twotheta, 'scan': scans, 'I': intensities}) + xrd.io.translate_wavelengths(data=heatmap, wavelength=data['wavelength'][0]) + min_dict = {'2th': heatmap['2th'].min(), '2th_cuka': heatmap['2th_cuka'].min(), '2th_moka': heatmap['2th_moka'].min(), + 'q': heatmap['q'].min(), 'q2': heatmap['q2'].min(), 'q4': heatmap['q4'].min(), '1/d': heatmap['1/d'].min()} + + max_dict = {'2th': heatmap['2th'].max(), '2th_cuka': heatmap['2th_cuka'].max(), '2th_moka': heatmap['2th_moka'].max(), + 'q': heatmap['q'].max(), 'q2': heatmap['q2'].max(), 'q4': heatmap['q4'].max(), '1/d': heatmap['1/d'].max()} + + + ndatapoints = len(data['diffractogram'][0]['2th']) + + xlims = [0, ndatapoints, 0, ndatapoints] # 0: xmin, 1: xmax, 2: xmin_start, 3: xmax_start + xticks = {} + xticklabels = {} + + for xval in min_dict.keys(): + + # Add xticks labels + + label_max = aux.floor(max_dict[xval], roundto=options['x_tick_locators'][0]) + label_min = aux.ceil(min_dict[xval], roundto=options['x_tick_locators'][0]) + label_steps = (label_max - label_min)/options['x_tick_locators'][0] + + xticklabels[xval] = np.linspace(label_min, label_max, num=int(label_steps)+1) + + # Add xticks + xval_span = max_dict[xval] - min_dict[xval] + steps = xval_span / ndatapoints + + + xticks_xval = [] + + for tick in xticklabels[xval]: + xticks_xval.append((tick-min_dict[xval])/steps) + + xticks[xval] = xticks_xval + + + options['x_tick_locators'] = None + + heatmap = heatmap.reset_index().pivot_table(index='scan', columns='2th', values='I') + + options['heatmap_xlim'] = xlims + + + return heatmap, xticks, xticklabels + + + + + + + +# #results = np.transpose(np.vstack([twotheta, scans, intensities])) + + +def determine_grid_layout(options): + + + nrows = 1 if not options['reflections_indices'] else 2 + + if options['reflections_plot']: + for reference in options['reflections_data']: + nrows += 1 + + options['format_params']['nrows'] = nrows + options['format_params']['grid_ratio_height'] = [1 for i in range(nrows-1)]+[10] + + return options + + + + + +def plot_diffractogram_interactive(data, options): + + + # Format here is xminmax[0]: xmin, xminmax[1]: xmax, xminmax[2]: xmin_start, xminmax[3]: xmax_start, where "_start" denotes starting value of the slider + xminmax = { '2th': [None, None, None, None], '2th_cuka': [None, None, None, None], '2th_moka': [None, None, None, None], + 'd': [None, None, None, None], '1/d': [None, None, None, None], + 'q': [None, None, None, None], 'q2': [None, None, None, None], 'q4': [None, None, None, None], + 'heatmap': [None, None, None, None], 'start': [None, None, None, None]} + + yminmax = { 'diff': [None, None, None, None], 'heatmap': [None, None, None, None], 'start': [None, None, None, None]} + + update_xminmax(xminmax=xminmax, data=data, options=options) + update_yminmax(yminmax=yminmax, data=data, options=options) + + options['xminmax'], options['yminmax'] = xminmax, yminmax + + # Get start values for ylim slider based on choice (FIXME This can be impleneted into update_yminmax). Can also make a 'start' item that stores the start values, instead of having 4 items in 'diff' as it is now. + if options['heatmap']: + ymin = yminmax['heatmap'][0] + ymax = yminmax['heatmap'][1] + ymin_start = yminmax['heatmap'][0] + ymax_start = yminmax['heatmap'][1] + + elif not options['heatmap']: + ymin = yminmax['diff'][0] + ymax = yminmax['diff'][1] + ymin_start = yminmax['diff'][2] + ymax_start = yminmax['diff'][3] + + + # FIXME The start values for xlim should probably also be decided by initial value of x_vals, and can likewise be implemented in update_xminmax() + + + + options['widgets'] = { + 'xlim': { + 'w': widgets.FloatRangeSlider(value=[xminmax['start'][2], xminmax['start'][3]], min=xminmax['start'][0], max=xminmax['start'][1], step=0.5, layout=widgets.Layout(width='95%')), + 'state': options['x_vals'], + '2th_default': {'min': xminmax['2th'][0], 'max': xminmax['2th'][1], 'value': [xminmax['2th'][0], xminmax['2th'][1]], 'step': 0.5}, + '2th_cuka_default': {'min': xminmax['2th_cuka'][0], 'max': xminmax['2th_cuka'][1], 'value': [xminmax['2th_cuka'][0], xminmax['2th_cuka'][1]], 'step': 0.5}, + '2th_moka_default': {'min': xminmax['2th_moka'][0], 'max': xminmax['2th_moka'][1], 'value': [xminmax['2th_moka'][0], xminmax['2th_moka'][1]], 'step': 0.5}, + 'd_default': {'min': xminmax['d'][0], 'max': xminmax['d'][1], 'value': [xminmax['d'][0], xminmax['d'][1]], 'step': 0.5}, + '1/d_default': {'min': xminmax['1/d'][0], 'max': xminmax['1/d'][1], 'value': [xminmax['1/d'][0], xminmax['1/d'][1]], 'step': 0.5}, + 'q_default': {'min': xminmax['q'][0], 'max': xminmax['q'][1], 'value': [xminmax['q'][0], xminmax['q'][1]], 'step': 0.5}, + 'q2_default': {'min': xminmax['q2'][0], 'max': xminmax['q2'][1], 'value': [xminmax['q2'][0], xminmax['q2'][1]], 'step': 0.5}, + 'q4_default': {'min': xminmax['q4'][0], 'max': xminmax['q4'][1], 'value': [xminmax['q4'][0], xminmax['q4'][1]], 'step': 0.5}, + 'heatmap_default': {'min': xminmax['heatmap'][0], 'max': xminmax['heatmap'][1], 'value': [xminmax['heatmap'][0], xminmax['heatmap'][1]], 'step': 10} + }, + 'ylim': { + 'w': widgets.FloatRangeSlider(value=[yminmax['start'][2], yminmax['start'][3]], min=yminmax['start'][0], max=yminmax['start'][1], step=0.5, layout=widgets.Layout(width='95%')), + 'state': 'heatmap' if options['heatmap'] else 'diff', + 'diff_default': {'min': yminmax['diff'][0], 'max': yminmax['diff'][1], 'value': [yminmax['diff'][2], yminmax['diff'][3]], 'step': 0.1}, + 'heatmap_default': {'min': yminmax['heatmap'][0], 'max': yminmax['heatmap'][1], 'value': [yminmax['heatmap'][0], yminmax['heatmap'][1]], 'step': 0.1} + } + } + + if options['reflections_data']: + w = widgets.interactive(btp.ipywidgets_update, func=widgets.fixed(plot_diffractogram), data=widgets.fixed(data), options=widgets.fixed(options), + scatter=widgets.ToggleButton(value=False), + line=widgets.ToggleButton(value=True), + reflections_plot=widgets.ToggleButton(value=True), + reflections_indices=widgets.ToggleButton(value=False), + heatmap=widgets.ToggleButton(value=options['heatmap']), + x_vals=widgets.Dropdown(options=['2th', 'd', '1/d', 'q', 'q2', 'q4', '2th_cuka', '2th_moka'], value='2th', description='X-values'), + xlim=options['widgets']['xlim']['w'], + ylim=options['widgets']['ylim']['w'], + offset_y=widgets.BoundedFloatText(value=options['offset_y'], min=-5, max=5, step=0.01, description='offset_y'), + offset_x=widgets.BoundedFloatText(value=options['offset_x'], min=-1, max=1, step=0.01, description='offset_x') + ) + + else: + w = widgets.interactive(btp.ipywidgets_update, func=widgets.fixed(plot_diffractogram), data=widgets.fixed(data), options=widgets.fixed(options), + scatter=widgets.ToggleButton(value=False), + line=widgets.ToggleButton(value=True), + xlim=options['widgets']['xlim']['w']) + + + options['widget'] = w + + display(w) + + +def update_xminmax(xminmax, data, options={}): + ''' Finds minimum and maximum values of each column and updates the minmax dictionary to contain the correct values. + + Input: + minmax (dict): contains ''' + + xminmax['2th'] = [None, None, None, None] + for index, diffractogram in enumerate(data['diffractogram']): + + if not xminmax['2th'][0] or diffractogram['2th'].min() < xminmax['2th'][0]: + xminmax['2th'][0] = diffractogram['2th'].min() + min_index = index + + if not xminmax['2th'][1] or diffractogram['2th'].max() > xminmax['2th'][1]: + xminmax['2th'][1] = diffractogram['2th'].max() + max_index = index + + + xminmax['2th'][2], xminmax['2th'][3] = xminmax['2th'][0], xminmax['2th'][1] + + xminmax['2th_cuka'][0], xminmax['2th_cuka'][1] = data['diffractogram'][min_index]['2th_cuka'].min(), data['diffractogram'][max_index]['2th_cuka'].max() + xminmax['2th_cuka'][2], xminmax['2th_cuka'][3] = xminmax['2th_cuka'][0], xminmax['2th_cuka'][1] + + xminmax['2th_moka'][0], xminmax['2th_moka'][1] = data['diffractogram'][min_index]['2th_moka'].min(), data['diffractogram'][max_index]['2th_moka'].max() + xminmax['2th_moka'][2], xminmax['2th_moka'][3] = xminmax['2th_moka'][0], xminmax['2th_moka'][1] + + xminmax['d'][0], xminmax['d'][1] = data['diffractogram'][max_index]['d'].min(), data['diffractogram'][min_index]['d'].max() # swapped, intended + xminmax['d'][2], xminmax['d'][3] = xminmax['d'][0], xminmax['d'][1] + + xminmax['1/d'][0], xminmax['1/d'][1] = data['diffractogram'][min_index]['1/d'].min(), data['diffractogram'][max_index]['1/d'].max() + xminmax['1/d'][2], xminmax['1/d'][3] = xminmax['1/d'][0], xminmax['1/d'][1] + + xminmax['q'][0], xminmax['q'][1] = data['diffractogram'][min_index]['q'].min(), data['diffractogram'][max_index]['q'].max() + xminmax['q'][2], xminmax['q'][3] = xminmax['q'][0], xminmax['q'][1] + + xminmax['q2'][0], xminmax['q2'][1] = data['diffractogram'][min_index]['q2'].min(), data['diffractogram'][max_index]['q2'].max() + xminmax['q2'][2], xminmax['q2'][3] = xminmax['q2'][0], xminmax['q2'][1] + + xminmax['q4'][0], xminmax['q4'][1] = data['diffractogram'][min_index]['q4'].min(), data['diffractogram'][max_index]['q4'].max() + xminmax['q4'][2], xminmax['q4'][3] = xminmax['q4'][0], xminmax['q4'][1] + + + xminmax['heatmap'] = options['heatmap_xlim'] # This value is set in the generate_heatmap()-function + + + xminmax['start'][0], xminmax['start'][1] = xminmax[options['x_vals']][0], xminmax[options['x_vals']][1] + xminmax['start'][2], xminmax['start'][3] = xminmax[options['x_vals']][2], xminmax[options['x_vals']][3] + + +def update_yminmax(yminmax: dict, data: dict, options={}) -> None: + + yminmax['diff'] = [None, None, None, None] + # Go through diffractograms and find the minimum and maximum intensity values + for diffractogram in data['diffractogram']: + if not yminmax['diff'][0] or (yminmax['diff'][0] > (diffractogram['I'].min())): + yminmax['diff'][0] = diffractogram['I'].min() + + if not yminmax['diff'][1] or (yminmax['diff'][1] < (diffractogram['I'].max())): + yminmax['diff'][1] = diffractogram['I'].max() + + + # Set start values of ymin and ymax to be slightly below lowest data points and slightly above highest data points to give some whitespace around the plot + yminmax['diff'][2] = yminmax['diff'][0] - 0.1*yminmax['diff'][1] + yminmax['diff'][3] = yminmax['diff'][1] + 0.2*yminmax['diff'][1] + + # Allow for adjustment up to five times ymax above and below data + yminmax['diff'][0] = yminmax['diff'][0] - 5*yminmax['diff'][1] + yminmax['diff'][1] = yminmax['diff'][1]*5 + + + # Set start values to the edges of the dataset + yminmax['heatmap'][0], yminmax['heatmap'][1] = 0, data['heatmap'].shape[0] + yminmax['heatmap'][2], yminmax['heatmap'][3] = yminmax['heatmap'][0], yminmax['heatmap'][1] + + + if options['heatmap']: + yminmax['start'][0], yminmax['start'][1] = yminmax['heatmap'][0], yminmax['heatmap'][1] + yminmax['start'][2], yminmax['start'][3] = yminmax['heatmap'][0], yminmax['heatmap'][1] + + else: + # The third and fourth index are different here to not be zoomed completely out to begin with. + yminmax['start'][0], yminmax['start'][1] = yminmax['diff'][0], yminmax['diff'][1] + yminmax['start'][2], yminmax['start'][3] = yminmax['diff'][2], yminmax['diff'][3] + + +def update_defaults(widget: dict, minmax: dict) -> None: + ''' Updates the default x- or y-limits of a given widget. Refer to plot_diffractogram_interactive() to see the form of the widget that is passed in. An update of the min/max-values is done just prior to calling this function. + Changes dictionaries in place. + + Input: + widget (dict): A dictionary containing the widget itself (widget['w']) and all its default-values (e.g. widget['2th_default']) + minmax (dict): A dictionary containing min and max values, as well as min_start and max_start values. (e.g. minmax['2th'] is a list with four elements: [xmin, xmax, xmin_start, xmax_start]) + + Output: + None.''' + + for name, attr in widget.items(): + if name.endswith('default'): + attr['min'] = minmax[name.replace('_default', '')][0] + attr['max'] = minmax[name.replace('_default', '')][1] + attr['value'] = [minmax[name.replace('_default', '')][2], minmax[name.replace('_default', '')][3]] + + +def update_widgets(data, options): + + + for widget_name, widget in options['widgets'].items(): + + # Make changes to xlim-widget + if widget_name == 'xlim': + # First update the min and max values + update_xminmax(xminmax=options['xminmax'], data=data, options=options) + update_defaults(widget=widget, minmax=options['xminmax']) + + + if options['heatmap'] and (widget['state'] != 'heatmap'): + + + setattr(widget['w'], 'min', widget['heatmap_default']['min']) + setattr(widget['w'], 'max', widget['heatmap_default']['max']) + setattr(widget['w'], 'value', widget['heatmap_default']['value']) + setattr(widget['w'], 'step', widget['heatmap_default']['step']) + + widget['state'] = 'heatmap' + + elif not options['heatmap'] and (widget['state'] != options['x_vals']): + # Then loop through all attributes in the widget and change to current mode. + for arg in widget[f'{options["x_vals"]}_default']: + + # If new min value is larger than previous max, or new max value is smaller than previous min, set the opposite first + if arg == 'min': + if widget[f'{options["x_vals"]}_default']['min'] > getattr(widget['w'], 'max'): + setattr(widget['w'], 'max', widget[f'{options["x_vals"]}_default']['max']) + + elif arg == 'max': + if widget[f'{options["x_vals"]}_default']['max'] < getattr(widget['w'], 'min'): + setattr(widget['w'], 'min', widget[f'{options["x_vals"]}_default']['min']) + + + setattr(widget['w'], arg, widget[f'{options["x_vals"]}_default'][arg]) + + + widget['state'] = options['x_vals'] + + # Make changes to ylim-widget + elif widget_name == 'ylim': + update_yminmax(yminmax=options['yminmax'], data=data, options=options) + update_defaults(widget=widget, minmax=options['yminmax']) + + state = 'heatmap' if options['heatmap'] else 'diff' + + if widget['state'] != state or options['offset_change']: + + for arg in widget[f'{state}_default']: + # If new min value is larger than previous max, or new max value is smaller than previous min, set the opposite first + if arg == 'min': + if widget[f'{state}_default']['min'] > getattr(widget['w'], 'max'): + setattr(widget['w'], 'max', widget[f'{state}_default']['max']) + + elif arg == 'max': + if widget[f'{state}_default']['max'] < getattr(widget['w'], 'min'): + setattr(widget['w'], 'min', widget[f'{state}_default']['min']) + + + setattr(widget['w'], arg, widget[f'{state}_default'][arg]) + + options['offset_change'] = False + widget['state'] = state + + + + +def plot_reflection_indices(data, reflections_params, ax, options={}): + ''' Print reflection indices from output generated by VESTA. + + Required contents of data: + path (str): relative path to reflection table file''' + + required_options = ['reflection_indices', 'text_colour', 'hide_indices'] + + default_options = { + 'reflection_indices': 3, # Number of reflection indices to plot, from highest intensity and working its way down + 'text_colour': 'black', + 'hide_indices': False + } + + reflections_params = aux.update_options(options=reflections_params, required_options=required_options, default_options=default_options) + + if not reflections_params['hide_indices']: + reflection_table = xrd.io.load_reflection_table(data=data, reflections_params=reflections_params, options=options) + + if reflections_params['reflection_indices'] > 0: + + # Get the data['reflection_indices'] number of highest reflections within the subrange options['xlim'] + x_vals = 'heatmap' if options['heatmap'] else options['x_vals'] + reflection_indices = reflection_table.loc[(reflection_table[x_vals] > options['xlim'][0]) & (reflection_table[x_vals] < options['xlim'][1])].nlargest(options['reflection_indices'], 'I') + + # Plot the indices + for i in range(reflections_params['reflection_indices']): + if reflection_indices.shape[0] > i: + ax.text(s=f'({reflection_indices["h"].iloc[i]} {reflection_indices["k"].iloc[i]} {reflection_indices["l"].iloc[i]})', x=reflection_indices[x_vals].iloc[i], y=0, fontsize=2.5, rotation=90, va='bottom', ha='center', c=reflections_params['text_colour']) + + + if options['xlim']: + ax.set_xlim(options['xlim']) + + ax.axis('off') + + + return + +def plot_reflection_table(data, reflections_params, ax=None, options={}): + ''' Plots a reflection table from output generated by VESTA. + + Required contents of data: + path (str): relative path to reflection table file''' + + required_options = ['reflection_indices', 'reflections_colour', 'min_alpha', 'wavelength', 'format_params', 'rc_params', 'label'] + + default_options = { + 'reflection_indices': 0, # Number of indices to print + 'reflections_colour': [0,0,0], + 'min_alpha': 0, + 'wavelength': 1.54059, # CuKalpha, [Å] + 'format_params': {}, + 'rc_params': {}, + 'label': None + } + + if 'colour' in data.keys(): + options['reflections_colour'] = reflections_params['colour'] + if 'min_alpha' in reflections_params.keys(): + options['min_alpha'] = reflections_params['min_alpha'] + if 'reflection_indices' in reflections_params.keys(): + options['reflection_indices'] = reflections_params['reflection_indices'] + if 'label' in reflections_params.keys(): + options['label'] = reflections_params['label'] + if 'wavelength' in reflections_params.keys(): + options['wavelength'] = reflections_params['wavelength'] + + options = aux.update_options(options=options, required_options=required_options, default_options=default_options) + + + if not ax: + _, ax = btp.prepare_plot(options) + + x_vals = 'heatmap' if options['heatmap'] else options['x_vals'] + + reflection_table = xrd.io.load_reflection_table(data=data, reflections_params=reflections_params, options=options) + reflections, intensities = reflection_table[x_vals], reflection_table['I'] + + + + colours = [] + + for ref, intensity in zip(reflections, intensities): + + colour = list(options['reflections_colour']) + rel_intensity = (intensity / intensities.max())*(1-options['min_alpha']) + options['min_alpha'] + colour.append(rel_intensity) + colours.append(colour) + + + + + ax.vlines(x=reflections, ymin=-1, ymax=1, colors=colours, lw=0.5) + ax.set_ylim([-0.5,0.5]) + + + ax.tick_params(which='both', bottom=False, labelbottom=False, right=False, labelright=False, left=False, labelleft=False, top=False, labeltop=False) + + if options['xlim']: + ax.set_xlim(options['xlim']) + + + if options['label']: + xlim_range = ax.get_xlim()[1] - ax.get_xlim()[0] + ylim_avg = (ax.get_ylim()[0]+ax.get_ylim()[1])/2 + + ax.text(s=reflections_params['label'], x=(ax.get_xlim()[0]-0.01*xlim_range), y=ylim_avg, ha = 'right', va = 'center') + + + + + +def prettify_labels(label): + + labels_dict = { + '2th': '2$\\theta$', + 'I': 'Intensity' + } + + return labels_dict[label] + + + +def reverse_diffractograms(diffractograms): + + rev_diffractograms = [] + + for i in len(diffractograms): + rev_diffractograms.append(diffractograms.pop()) + + return rev_diffractograms + +#def plot_heatmap(): diff --git a/setup.py b/setup.py index 4c93b78..b3f9f34 100644 --- a/setup.py +++ b/setup.py @@ -1,11 +1,11 @@ from setuptools import setup, find_packages -setup(name='beamtime', - version='0.1', - description='Package for process and analysis of beamtime data from SNBL', - url='http://github.uio.no/rasmusvt/beamtime', +setup(name='nafuma', + version='0.2', + description='Analysis tools for inorganic materials chemistry at the NAFUMA-group at the University of Oslo', + url='https://github.com/rasmusthog/nafuma', author='Rasmus Vester Thøgersen, Halvor Høen Hval', - author_email='rasmusvt@smn.uio.no', + author_email='code@rasmusthog.me', license='MIT', packages=find_packages(), zip_safe=False)