├── radie
├── qt
│ ├── ui
│ │ ├── __init__.py
│ │ ├── compile_all.py
│ │ ├── xyscatter.ui
│ │ └── xyscatter.py
│ ├── uchars.py
│ ├── errors.py
│ ├── resources
│ │ └── icons
│ │ │ ├── radie.ico
│ │ │ ├── xyline.svg
│ │ │ ├── xypoints.svg
│ │ │ ├── saveicon.svg
│ │ │ ├── seriesvisualization.svg
│ │ │ ├── xyscatter.svg
│ │ │ ├── radie.svg
│ │ │ ├── exiticon.svg
│ │ │ └── histogramicon.svg
│ ├── __init__.py
│ ├── shortcuts.py
│ ├── dpi.py
│ ├── colors.py
│ ├── cfg.py
│ ├── visualizations
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── series.py
│ │ └── histogram.py
│ ├── dataframeview.py
│ ├── plotwidget.py
│ ├── indexabledict.py
│ ├── functions.py
│ └── classes.py
├── plugins
│ ├── visualizations
│ │ ├── __init__.py
│ │ ├── ui
│ │ │ ├── __init__.py
│ │ │ ├── compile_all.py
│ │ │ ├── psd.ui
│ │ │ ├── psd.py
│ │ │ ├── tga.ui
│ │ │ ├── tga.py
│ │ │ ├── powderdiffraction.ui
│ │ │ └── powderdiffraction.py
│ │ ├── icons
│ │ │ ├── tga_chart_curve.png
│ │ │ ├── powderdiffraction.svg
│ │ │ └── particlesizedistribution.svg
│ │ ├── tests
│ │ │ └── powderdiffraction.py
│ │ └── xyscatter_docdemo.py.txt
│ ├── loaders
│ │ ├── __init__.py
│ │ ├── powderdiffraction_siemensD500.py
│ │ ├── vsm_lakeshore.py
│ │ ├── powderdiffraction_peaks.py
│ │ ├── powderdiffraction_gsas.py
│ │ ├── powderdiffraction_rigaku.py
│ │ └── psd_LA960.py
│ ├── structures
│ │ ├── __init__.py
│ │ ├── vsm.py
│ │ ├── dsc.py
│ │ ├── tga.py
│ │ ├── psd.py
│ │ └── powderdiffraction.py
│ ├── examples
│ │ ├── data
│ │ │ └── tga samples
│ │ │ │ ├── PAN.001
│ │ │ │ ├── PEB.001
│ │ │ │ ├── KF3700.001
│ │ │ │ ├── PI2555.001
│ │ │ │ ├── KYNAR 461.001
│ │ │ │ ├── DAICEL 2200.001
│ │ │ │ ├── RESOLE R-23155.001
│ │ │ │ └── ARALDITE MT 35700.001
│ │ └── __init__.py
│ └── __init__.py
├── tests
│ ├── viewer_test.py
│ ├── debug_qtviewer.py
│ ├── dftree_test.py
│ ├── df_listviewtest.py
│ ├── df_treeviewtest.py
│ ├── histogramtest.py
│ ├── xyscattertest.py
│ ├── seriesviewtest.py
│ ├── loadtxt_test.py
│ ├── dataframequick.py
│ ├── savetxt_test.py
│ └── misc_test.py
├── __init__.py
├── exceptions.py
├── util.py
└── structures
│ └── __init__.py
├── doc
├── makehtml.bat
├── source
│ ├── _github_pages
│ │ ├── .nojekyll
│ │ └── README.md
│ ├── api
│ │ ├── plotlist.rst
│ │ ├── masterdftree.rst
│ │ ├── plugins-structures.rst
│ │ ├── qtvisualizations.rst
│ │ ├── index.rst
│ │ ├── dataframe.rst
│ │ ├── loaders.rst
│ │ ├── plugins-qtvisualizations.rst
│ │ └── plugins.rst
│ ├── _images
│ │ └── example_visualization.png
│ ├── index.rst
│ ├── articles
│ │ ├── intro_to_datastructures.rst
│ │ └── what_is_dataquick.rst
│ └── conf.py
├── Makefile
└── make.bat
├── setup.cfg
├── .flake8
├── executables
├── radie.rc
├── radie.ico
├── radie_qt_viewer
│ ├── __main__.py
│ └── qtconf_preprocess.py
├── standard_launcher.cpp
├── build.bat
└── embedded_runtime_launcher.cpp
├── devel
├── debug_qtviewer.py
└── file_io.py
├── .gitignore
├── BUILD.bat
├── install_windows_shortcut.py
├── setup.py
└── README.md
/radie/qt/ui/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/makehtml.bat:
--------------------------------------------------------------------------------
1 | make html
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [bdist_wheel]
2 |
--------------------------------------------------------------------------------
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | max-line-length = 90
--------------------------------------------------------------------------------
/doc/source/_github_pages/.nojekyll:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/radie/plugins/visualizations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/radie/qt/uchars.py:
--------------------------------------------------------------------------------
1 | angstrom = "\u00C5"
2 | mu = "\u00B5"
--------------------------------------------------------------------------------
/executables/radie.rc:
--------------------------------------------------------------------------------
1 | IDI_ICON1 ICON "radie_multiple.ico"
2 |
--------------------------------------------------------------------------------
/radie/qt/errors.py:
--------------------------------------------------------------------------------
1 | class DFTypeError(TypeError):
2 | pass
--------------------------------------------------------------------------------
/radie/tests/viewer_test.py:
--------------------------------------------------------------------------------
1 | from radie.qt.viewer import debug
2 | debug()
--------------------------------------------------------------------------------
/devel/debug_qtviewer.py:
--------------------------------------------------------------------------------
1 | import radie.qt.viewer as viewer
2 |
3 | viewer.run()
--------------------------------------------------------------------------------
/radie/plugins/loaders/__init__.py:
--------------------------------------------------------------------------------
1 | """the plugins package for file-loaders"""
--------------------------------------------------------------------------------
/radie/plugins/structures/__init__.py:
--------------------------------------------------------------------------------
1 | """the plugins package for data structures"""
--------------------------------------------------------------------------------
/radie/tests/debug_qtviewer.py:
--------------------------------------------------------------------------------
1 | from radie.qt.viewer import run
2 |
3 | run(False)
--------------------------------------------------------------------------------
/radie/tests/dftree_test.py:
--------------------------------------------------------------------------------
1 | from radie.qt.masterdftree import test
2 | test()
3 |
--------------------------------------------------------------------------------
/radie/tests/df_listviewtest.py:
--------------------------------------------------------------------------------
1 | from radie.qt.plotlist import test
2 | test(False)
3 |
--------------------------------------------------------------------------------
/radie/tests/df_treeviewtest.py:
--------------------------------------------------------------------------------
1 | from radie.qt.plotlist import test
2 | test(True)
3 |
--------------------------------------------------------------------------------
/doc/source/_github_pages/README.md:
--------------------------------------------------------------------------------
1 | # radie-doc
2 | Sphinx built documentation for radie
3 |
--------------------------------------------------------------------------------
/radie/tests/histogramtest.py:
--------------------------------------------------------------------------------
1 | from radie.qt.visualizations.histogram import test
2 | test()
--------------------------------------------------------------------------------
/radie/tests/xyscattertest.py:
--------------------------------------------------------------------------------
1 | from radie.qt.visualizations.xyscatter import test
2 | test()
--------------------------------------------------------------------------------
/doc/source/api/plotlist.rst:
--------------------------------------------------------------------------------
1 | Plotlist Module
2 | ===============
3 |
4 | Article in Progress
5 |
--------------------------------------------------------------------------------
/executables/radie.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dvincentwest/radie/HEAD/executables/radie.ico
--------------------------------------------------------------------------------
/radie/tests/seriesviewtest.py:
--------------------------------------------------------------------------------
1 | from radie.qt.visualizations import series
2 |
3 | series.test()
--------------------------------------------------------------------------------
/radie/qt/resources/icons/radie.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dvincentwest/radie/HEAD/radie/qt/resources/icons/radie.ico
--------------------------------------------------------------------------------
/radie/plugins/visualizations/ui/__init__.py:
--------------------------------------------------------------------------------
1 | """package to hold QtDesigner `.ui` files along with uic generated python files"""
--------------------------------------------------------------------------------
/executables/radie_qt_viewer/__main__.py:
--------------------------------------------------------------------------------
1 | import qtconf_preprocess
2 | import radie.qt.viewer
3 |
4 | radie.qt.viewer.run(debug=False)
5 |
--------------------------------------------------------------------------------
/doc/source/_images/example_visualization.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dvincentwest/radie/HEAD/doc/source/_images/example_visualization.png
--------------------------------------------------------------------------------
/doc/source/api/masterdftree.rst:
--------------------------------------------------------------------------------
1 | Master StructuredDataFrame Tree Module
2 | =================================
3 |
4 | Document in Progress
5 |
--------------------------------------------------------------------------------
/radie/qt/ui/compile_all.py:
--------------------------------------------------------------------------------
1 | from os import path
2 | from PyQt5 import uic
3 |
4 | uic_dir = path.dirname(__file__)
5 | uic.compileUiDir(uic_dir)
--------------------------------------------------------------------------------
/radie/plugins/examples/data/tga samples/PAN.001:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dvincentwest/radie/HEAD/radie/plugins/examples/data/tga samples/PAN.001
--------------------------------------------------------------------------------
/radie/plugins/examples/data/tga samples/PEB.001:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dvincentwest/radie/HEAD/radie/plugins/examples/data/tga samples/PEB.001
--------------------------------------------------------------------------------
/radie/plugins/examples/data/tga samples/KF3700.001:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dvincentwest/radie/HEAD/radie/plugins/examples/data/tga samples/KF3700.001
--------------------------------------------------------------------------------
/radie/plugins/examples/data/tga samples/PI2555.001:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dvincentwest/radie/HEAD/radie/plugins/examples/data/tga samples/PI2555.001
--------------------------------------------------------------------------------
/radie/plugins/examples/data/tga samples/KYNAR 461.001:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dvincentwest/radie/HEAD/radie/plugins/examples/data/tga samples/KYNAR 461.001
--------------------------------------------------------------------------------
/radie/plugins/visualizations/icons/tga_chart_curve.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dvincentwest/radie/HEAD/radie/plugins/visualizations/icons/tga_chart_curve.png
--------------------------------------------------------------------------------
/radie/tests/loadtxt_test.py:
--------------------------------------------------------------------------------
1 | import radie as rd
2 | import radie.loaders as loaders
3 |
4 | fname = "/home/vince/Documents/test.df"
5 |
6 | rd.load_file(fname)
--------------------------------------------------------------------------------
/radie/plugins/examples/data/tga samples/DAICEL 2200.001:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dvincentwest/radie/HEAD/radie/plugins/examples/data/tga samples/DAICEL 2200.001
--------------------------------------------------------------------------------
/radie/plugins/visualizations/ui/compile_all.py:
--------------------------------------------------------------------------------
1 | from os import path
2 | from PyQt5 import uic
3 |
4 | uic_dir = path.dirname(__file__)
5 | uic.compileUiDir(uic_dir)
6 |
--------------------------------------------------------------------------------
/radie/plugins/examples/data/tga samples/RESOLE R-23155.001:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dvincentwest/radie/HEAD/radie/plugins/examples/data/tga samples/RESOLE R-23155.001
--------------------------------------------------------------------------------
/radie/plugins/examples/data/tga samples/ARALDITE MT 35700.001:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dvincentwest/radie/HEAD/radie/plugins/examples/data/tga samples/ARALDITE MT 35700.001
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__/
2 | .idea
3 | /sandbox
4 | /doc/build
5 | /build
6 | /radie.egg-info
7 | /dist
8 | /executables/*.exe
9 | /executables/*.obj
10 | /executables/*.res
11 |
--------------------------------------------------------------------------------
/BUILD.bat:
--------------------------------------------------------------------------------
1 | python setup.py sdist bdist_wheel
2 |
3 | :: I don't need these directories for now, so I won't use them
4 | :: RMDIR /S /Q dist\
5 | RMDIR /S /Q build\
6 | RMDIR /S /Q radie.egg-info\
7 |
--------------------------------------------------------------------------------
/doc/source/api/plugins-structures.rst:
--------------------------------------------------------------------------------
1 | Extended DataStructures
2 | ==========================
3 |
4 | .. automodule:: radie.structures
5 | :exclude-members: StructuredDataFrame
6 | :members:
7 |
--------------------------------------------------------------------------------
/doc/source/api/qtvisualizations.rst:
--------------------------------------------------------------------------------
1 | Visualizations
2 | ==============
3 |
4 | .. toctree::
5 | :maxdepth: 2
6 |
7 | .. autoclass:: radie.qt.visualizations.base.Visualization
8 | :members:
9 |
--------------------------------------------------------------------------------
/radie/tests/dataframequick.py:
--------------------------------------------------------------------------------
1 | """let me work with a dataframe quickly please"""
2 |
3 | import numpy as np
4 | import pandas as pd
5 |
6 | df = pd.DataFrame({'a': ['foo', 'bar', 'baz'], 'b': np.random.randn(3)})
7 |
--------------------------------------------------------------------------------
/radie/tests/savetxt_test.py:
--------------------------------------------------------------------------------
1 | import os
2 | import radie.plugins.examples as ex
3 | df = ex.example_powderdiffraction()
4 | test_file = os.path.join(os.path.expanduser("~"), "Documents/test.df")
5 | df.savetxt(test_file, True)
6 |
--------------------------------------------------------------------------------
/radie/tests/misc_test.py:
--------------------------------------------------------------------------------
1 | import radie as rd
2 | from radie.plugins.loaders import psd_LA960
3 |
4 | fname = "C:/Users/A3R7LZZ/Desktop/g12_cgm_170127r4_5j87_avg1.dat"
5 | df = psd_LA960.LA960_csv_loader.load(fname)
6 |
7 | df1 = None
8 |
--------------------------------------------------------------------------------
/doc/source/api/index.rst:
--------------------------------------------------------------------------------
1 | Radie API Reference
2 | =======================
3 |
4 | .. toctree::
5 | :maxdepth: 3
6 |
7 | dataframe
8 | loaders
9 | qtvisualizations
10 | plugins
11 | plotlist
12 | masterdftree
13 |
--------------------------------------------------------------------------------
/radie/__init__.py:
--------------------------------------------------------------------------------
1 | from . import structures, loaders, plugins
2 | from .structures import StructuredDataFrame
3 | from .loaders import load_file, load_csv
4 |
5 | __version__ = '0.1.4'
6 |
7 | plugins.import_structures()
8 | plugins.import_loaders()
9 |
--------------------------------------------------------------------------------
/doc/source/api/dataframe.rst:
--------------------------------------------------------------------------------
1 | StructuredDataFrame
2 | ==============
3 |
4 | .. autoclass:: radie.structures.datastructure.StructuredDataFrame
5 | :members:
6 | :show-inheritance:
7 |
8 | .. automethod:: radie.structures.datastructure.StructuredDataFrame.__init__
9 |
10 |
--------------------------------------------------------------------------------
/doc/source/api/loaders.rst:
--------------------------------------------------------------------------------
1 | Loaders Module
2 | ==============
3 |
4 | .. automodule:: radie.loaders
5 | :members:
6 | :exclude-members: Loader
7 |
8 | .. autoclass:: radie.loaders.Loader
9 | :members:
10 |
11 | .. automethod:: radie.loaders.Loader.__init__
12 |
13 |
--------------------------------------------------------------------------------
/doc/source/api/plugins-qtvisualizations.rst:
--------------------------------------------------------------------------------
1 | Extended Visualizations
2 | ========================
3 |
4 | This page lists all of the visualization widgets made available through plugins
5 |
6 | .. automodule:: radie.qt.visualizations
7 | :exclude-members: Visualization
8 | :members:
9 |
10 |
--------------------------------------------------------------------------------
/radie/qt/__init__.py:
--------------------------------------------------------------------------------
1 | try:
2 | import PyQt5
3 | except ImportError:
4 | raise Exception("PyQt5 module note found, run `conda install pyqt` or `pip install PyQt5`")
5 |
6 | from . import cfg
7 | from .functions import *
8 |
9 | from .. import plugins
10 |
11 | plugins.import_visualizations()
12 |
--------------------------------------------------------------------------------
/devel/file_io.py:
--------------------------------------------------------------------------------
1 | import radie as rd
2 | import pathlib
3 | import os
4 |
5 | this_dir = pathlib.Path(os.path.abspath(os.path.dirname(__file__)))
6 | data_file = this_dir / "../radie/plugins/examples/data/barium_ferrite.ras"
7 | data_file = this_dir / "../radie/plugins/examples/data/idea_vsm.txt"
8 |
9 | df = rd.load_file(data_file)
10 | print(df.head())
11 |
12 | # df.savetxt()
--------------------------------------------------------------------------------
/doc/source/api/plugins.rst:
--------------------------------------------------------------------------------
1 | Plugins
2 | =======
3 |
4 | Plugins extend Radie by providing:
5 |
6 | 1. New datastructures via StructuredDataFrame subclasses
7 | 2. Loader functions to read new datafile formats
8 | 3. New QtWidget based Visualizations to plugin to the Radie Viewer
9 |
10 | .. toctree::
11 | :maxdepth: 3
12 |
13 | plugins-structures
14 | plugins-qtvisualizations
15 |
--------------------------------------------------------------------------------
/doc/source/index.rst:
--------------------------------------------------------------------------------
1 | .. image:: ../../radie/qt/resources/icons/radie.svg
2 | :width: 48pt
3 |
4 | Documentation for Radie
5 | =====================================
6 |
7 | *\*\*These Documentation pages are a Work in Progress*
8 |
9 | .. toctree::
10 | :maxdepth: 1
11 | :caption: Contents:
12 |
13 | articles/what_is_radie
14 | articles/intro_to_datastructures
15 | articles/creating_a_new_visualization
16 | API Reference
17 |
18 |
19 | Indices and tables
20 | ==================
21 |
22 | * :ref:`genindex`
23 | * :ref:`modindex`
24 | * :ref:`search`
25 |
--------------------------------------------------------------------------------
/radie/exceptions.py:
--------------------------------------------------------------------------------
1 | class RadieException(Exception):
2 | """Define a base class for Radie Exceptions, and subclass all specific exceptions from this"""
3 | pass
4 |
5 |
6 | class LoaderException(RadieException):
7 | """Generic exception for expected bad things happening inside loader functions"""
8 | pass
9 |
10 |
11 | class LoaderNotFound(LoaderException):
12 | """Raise when no loader was found"""
13 | pass
14 |
15 |
16 | class IncorrectFileType(LoaderException):
17 | """Raise this exception when a file is determined to be an incorrect type for the loader function"""
18 | pass
19 |
--------------------------------------------------------------------------------
/radie/plugins/visualizations/tests/powderdiffraction.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | from PyQt5 import QtWidgets
4 |
5 | from radie.plugins import examples
6 | from radie.qt import functions as fn
7 | from radie.qt.masterdftree import DFReference
8 | from radie.plugins.visualizations.powderdiffraction import VisPowderDiffraction
9 |
10 | app = QtWidgets.QApplication(sys.argv)
11 |
12 | fn.set_popup_exceptions()
13 |
14 | form = VisPowderDiffraction()
15 | df = examples.example_powderdiffraction()
16 | ref = DFReference(df, None)
17 | form.listView_datasets.addDataFrames(*[ref, ref, ref])
18 | form.show()
19 | sys.exit(app.exec_())
20 |
--------------------------------------------------------------------------------
/executables/radie_qt_viewer/qtconf_preprocess.py:
--------------------------------------------------------------------------------
1 | """account for Anaconda specific packaging of PyQt which is different than the pip wheel"""
2 |
3 | import sys
4 | from pathlib import Path
5 | import os
6 |
7 | dll_path = Path(sys.exec_prefix)
8 | qtconf = dll_path / 'qt.conf'
9 |
10 | if qtconf.exists():
11 | with open(qtconf) as fid:
12 | for line in fid:
13 | if line.startswith('Prefix'):
14 | prefix = Path(line.split('=')[1].strip())
15 | plugin_path = prefix / 'plugins'
16 | if plugin_path.exists():
17 | os.environ['QT_PLUGIN_PATH'] = str(plugin_path)
18 | break
19 |
--------------------------------------------------------------------------------
/doc/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = python -msphinx
7 | SPHINXPROJ = Radie
8 | SOURCEDIR = source
9 | BUILDDIR = build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
--------------------------------------------------------------------------------
/radie/qt/shortcuts.py:
--------------------------------------------------------------------------------
1 | from win32com.client import Dispatch
2 |
3 |
4 | def createShortcut(path, target='', wDir='', args= '', icon=''):
5 | shell = Dispatch('WScript.Shell')
6 | shortcut = shell.CreateShortCut(path)
7 | shortcut.TargetPath = target
8 | shortcut.WorkingDirectory = wDir
9 |
10 | if args:
11 | shortcut.Arguments = args
12 |
13 | if icon == '':
14 | pass
15 | else:
16 | shortcut.IconLocation = icon
17 | shortcut.save()
18 |
19 | path = r"C:\Users\A3R7LZZ\Desktop\Multiline TRL.lnk"
20 | target = r"C:\Miniconda3\pythonw.exe"
21 | args = r"C:\Coding\Python\scikit-rf\qtapps\multiline_trl.py"
22 | createShortcut(path, target, args=args)
23 |
--------------------------------------------------------------------------------
/radie/qt/dpi.py:
--------------------------------------------------------------------------------
1 | """A convenience module for getting dimensions in the context of the dpi of the monitor"""
2 |
3 | from . import cfg
4 |
5 |
6 | def scaling(dpi=None):
7 | """
8 | get the scaling factor based on the dpi of the current screen
9 |
10 | Parameters
11 | ----------
12 | dpi : int or float
13 |
14 | Returns
15 | -------
16 | float
17 |
18 | """
19 | if dpi:
20 | return dpi / cfg.dpi_100percent
21 | else:
22 | return cfg.dpi_scaling
23 |
24 |
25 | def length(pixels, dpi=None):
26 | return int(pixels * scaling(dpi))
27 |
28 |
29 | def width_by_height(w, h, dpi=None):
30 | scale = scaling(dpi)
31 | return int(w * scale), int(h * scale)
32 |
--------------------------------------------------------------------------------
/radie/qt/colors.py:
--------------------------------------------------------------------------------
1 | """a little module for handling colors and color generators"""
2 |
3 | _mpl_hex_colors = ['1f77b4', 'ff7f0e', '2ca02c', 'd62728', '9467bd', '8c564b', 'e377c2', '7f7f7f', 'bcbd22', '17becf']
4 |
5 |
6 | def colors(color_list=_mpl_hex_colors):
7 | """
8 | a simple generator to get the next color in the list
9 |
10 | Parameters
11 | ----------
12 | color_list : list
13 | a list of colors, usually hex strings, or other valid pyqgtraph Pen color specifiers
14 |
15 | Returns
16 | -------
17 | color : str
18 | returns whatever type of item in the list is
19 |
20 | """
21 | count = 0
22 | num = len(color_list)
23 | while True:
24 | yield color_list[count % num]
25 | count += 1
26 |
--------------------------------------------------------------------------------
/radie/util.py:
--------------------------------------------------------------------------------
1 | import re
2 | from datetime import datetime
3 | import time
4 |
5 |
6 | def excel_sheet_name(name, names_list=[], length=31):
7 | pattern = "[\\/\*\[\]:\?%&]+"
8 | clean_name = re.sub(pattern, "", name)[:length]
9 |
10 | if clean_name in names_list:
11 | base_name = clean_name[:length-3]
12 |
13 | for i in range(100):
14 | # not going to handle i > 99 errors, because I assume we'll never get that high
15 | clean_name = base_name + "-{:02d}".format(i)
16 | if clean_name not in names_list:
17 | break
18 |
19 | return clean_name
20 |
21 |
22 | def iso_date_string(dtime=None):
23 | if type(dtime) in (float, int):
24 | dtime = datetime(dtime)
25 | elif not isinstance(dtime, datetime):
26 | dtime = datetime.now()
27 | return dtime.isoformat()
28 |
--------------------------------------------------------------------------------
/radie/plugins/structures/vsm.py:
--------------------------------------------------------------------------------
1 | from collections import OrderedDict
2 | from radie.structures import StructuredDataFrame, register_data_structures
3 |
4 |
5 | class VSM(StructuredDataFrame):
6 | """
7 | A class for Vibrating Sample Magnetometer measurements. Although these measurements can get quite complicated,
8 | measurements versus angle, the typical measurement is a measurement of applied field (in Gauss) versus magnetic
9 | moment of the sample (in emu)
10 | """
11 |
12 | label = "VSM"
13 |
14 | _required_metadata = StructuredDataFrame._required_metadata.copy()
15 | _required_metadata.update({
16 | "mass": 1,
17 | "density": 1
18 | })
19 |
20 | _x = "Field"
21 | _y = "Moment"
22 |
23 | _required_columns = OrderedDict((
24 | ("Field", float),
25 | ("Moment", float),
26 | ))
27 | _column_properties = []
28 | _loaders = []
29 |
30 |
31 | register_data_structures(VSM)
32 |
--------------------------------------------------------------------------------
/doc/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=python -msphinx
9 | )
10 | set SOURCEDIR=source
11 | set BUILDDIR=build
12 | set SPHINXPROJ=Radie
13 |
14 | if "%1" == "" goto help
15 |
16 | %SPHINXBUILD% >NUL 2>NUL
17 | if errorlevel 9009 (
18 | echo.
19 | echo.The Sphinx module was not found. Make sure you have Sphinx installed,
20 | echo.then set the SPHINXBUILD environment variable to point to the full
21 | echo.path of the 'sphinx-build' executable. Alternatively you may add the
22 | echo.Sphinx directory to PATH.
23 | echo.
24 | echo.If you don't have Sphinx installed, grab it from
25 | echo.http://sphinx-doc.org/
26 | exit /b 1
27 | )
28 |
29 | REM RMDIR /S /Q %BUILDDIR%\%1
30 |
31 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
32 | goto end
33 |
34 | :help
35 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
36 |
37 | :end
38 | popd
39 |
--------------------------------------------------------------------------------
/radie/qt/cfg.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 |
4 | from PyQt5 import QtWidgets, QtGui
5 | import pyqtgraph as pg
6 |
7 | dpi = None
8 | dpi_scaling = 1.0
9 | dpi_100percent = 96
10 |
11 |
12 | def set_dpi_scaling():
13 | app = QtWidgets.QApplication.instance()
14 | screen = app.screens()[0] # type QtGui.QScreen
15 | dpi = screen.logicalDotsPerInch()
16 | global dpi_scaling
17 | dpi_scaling = dpi / dpi_100percent
18 |
19 |
20 | preferred_styles = ['plastique', 'Fusion', 'cleanlooks', 'motif', 'cde']
21 | preferred_style = 'Fusion'
22 |
23 | pg.setConfigOption('background', 'w')
24 | pg.setConfigOption('foreground', 'k')
25 | pg.setConfigOption('antialias', True)
26 |
27 | resource_manager = None
28 |
29 | user_path = os.path.expanduser("~")
30 | executable_dir = os.path.dirname(sys.executable)
31 |
32 | qt_dir = os.path.dirname(os.path.abspath(__file__))
33 | radie_dir = os.path.join(qt_dir, "..")
34 | resources_dir = os.path.join(qt_dir, "resources")
35 | icon_path = os.path.join(resources_dir, "icons")
36 |
37 | radie_icon = os.path.join(icon_path, "radie.svg")
38 |
--------------------------------------------------------------------------------
/radie/qt/visualizations/__init__.py:
--------------------------------------------------------------------------------
1 | """package for QtWidgets used to visualize StructuredDataFrame objects"""
2 |
3 | import warnings
4 | import typing
5 | from collections import OrderedDict
6 |
7 | from . import base, xyscatter, histogram, series
8 |
9 | __all__ = []
10 | visualizations = OrderedDict()
11 |
12 |
13 | def register_visualizations(*sub_classes):
14 | """
15 | Parameters
16 | ----------
17 | sub_classes : typing.Type(base.Visualization)
18 | base.Visualization sub-classes passed in as arguments
19 |
20 | """
21 | for cls in sub_classes:
22 | if cls.__name__ in visualizations.keys():
23 | warnings.warn("overwriting visualization class {:}".format(cls.__name__))
24 | visualizations[cls.__name__] = cls
25 |
26 | if not cls.__name__ in __all__:
27 | globals()[cls.__name__] = cls # put the class into this scope
28 | __all__.append(cls.__name__)
29 |
30 |
31 | register_visualizations(
32 | xyscatter.XYScatter,
33 | series.SeriesVisualization,
34 | histogram.Histogram,
35 | )
36 |
--------------------------------------------------------------------------------
/radie/structures/__init__.py:
--------------------------------------------------------------------------------
1 | import warnings
2 | from collections import OrderedDict
3 |
4 | from . import structureddataframe
5 | from .structureddataframe import StructuredDataFrame
6 |
7 | __all__ = [
8 | StructuredDataFrame.__name__
9 | ]
10 |
11 | structures = OrderedDict()
12 | structures[StructuredDataFrame.__name__] = StructuredDataFrame
13 |
14 |
15 | def register_data_structures(*sub_classes):
16 | """
17 | register StructuredDataFrame structures from the plugins folder
18 |
19 | Parameters
20 | ----------
21 | sub_classes
22 | StructuredDataFrame sub-classes as arguments
23 |
24 | """
25 | for cls in sub_classes: # type: StructuredDataFrame
26 | if cls.__name__ in structures.keys():
27 | warnings.warn("overwriting {:s} df_class".format(cls.__name__))
28 | structures[cls.__name__] = cls
29 | globals()[cls.__name__] = cls # put the class into this scope
30 |
31 | if cls.__name__ not in __all__:
32 | __all__.append(cls.__name__)
33 |
34 |
35 | def print_available_structures():
36 | for key in structures.keys():
37 | print(key)
38 |
--------------------------------------------------------------------------------
/radie/plugins/structures/dsc.py:
--------------------------------------------------------------------------------
1 | from collections import OrderedDict
2 | from radie.structures import StructuredDataFrame, register_data_structures
3 |
4 |
5 | class DSC(StructuredDataFrame):
6 | """
7 | StructuredDataFrame for holding Thermogravimetric analysis data
8 | Default units are minutes, mg, and celsius
9 | Please convert results to default units during instantiation, no unit checking is done within
10 | """
11 |
12 | label = "Differential Scanning Calorimetry"
13 |
14 | _required_metadata = StructuredDataFrame._required_metadata.copy()
15 | _required_metadata['mass'] = 1. # mg, Default to 1 in case none is provided, used for normalization
16 |
17 | _x = "temperature" # celsius
18 | _y = "heat_flow" # mW
19 | _z = "time" # min
20 |
21 | _required_columns = OrderedDict((
22 | ("temperature", float), # celsius
23 | ("heat_flow", float), # mW
24 | ("time", float), # min
25 | ))
26 | _column_properties = ["norm_heat_flow"]
27 |
28 | @property
29 | def norm_heat_flow(self):
30 | """Normalize the heat flow using the mass found in metadata"""
31 | return self.heat_flow/self.metadata['mass']
32 |
33 | register_data_structures(DSC)
34 |
--------------------------------------------------------------------------------
/radie/plugins/examples/__init__.py:
--------------------------------------------------------------------------------
1 | """convenience testing package to generate example DataFrames from example data"""
2 | import os
3 | from collections import OrderedDict
4 |
5 | import numpy as np
6 |
7 | from radie.structures import StructuredDataFrame
8 | from ..loaders import vsm_lakeshore, powderdiffraction_rigaku
9 |
10 | this_dir = os.path.dirname(__file__)
11 |
12 |
13 | def example_structureddataframe():
14 | data = OrderedDict()
15 | for key in ('x', 'y', 'z'):
16 | data[key] = np.random.rand(100)
17 | return StructuredDataFrame(data=data, name="random data")
18 |
19 |
20 | def example_powderdiffraction():
21 | """
22 | return an example `PowderDiffraction` generated by an example data file
23 |
24 | Returns
25 | -------
26 | df : PowderDiffraction
27 |
28 | """
29 | example_ras_file = os.path.join(this_dir, "data/xrd.ras")
30 | df = powderdiffraction_rigaku.load_ras(example_ras_file)
31 | return df
32 |
33 |
34 | def example_vsm():
35 | """
36 | return an example `VSM` generated by an example data file
37 |
38 | Returns
39 | -------
40 | df : VSM
41 |
42 | """
43 | example_file = os.path.join(this_dir, "data/vsm.txt")
44 | df = vsm_lakeshore.load_ideavsm_txt(example_file)
45 | return df
46 |
--------------------------------------------------------------------------------
/install_windows_shortcut.py:
--------------------------------------------------------------------------------
1 | from win32com.client import Dispatch
2 | import sys
3 | import os
4 | import pathlib
5 |
6 | executable = pathlib.Path(sys.executable)
7 | python = pathlib.Path(os.path.dirname(executable)) / "python.exe"
8 | pythonw = pathlib.Path(os.path.dirname(executable)) / "pythonw.exe"
9 |
10 | this_dir = pathlib.Path(os.path.abspath(os.path.dirname(__file__)))
11 | icon = this_dir / "radie/qt/resources/icons/radie.ico"
12 | usr = pathlib.Path(os.path.expanduser("~"))
13 | desktop = usr / "Desktop"
14 | start_menu = usr / "AppData/Roaming/Microsoft/Windows/Start Menu/Programs"
15 | radie_startmenu_dir = start_menu / "Radie"
16 | shortcut_windows = radie_startmenu_dir / "Radie.lnk"
17 | shortcut_console = radie_startmenu_dir / "Radie - Console.lnk"
18 |
19 | if not radie_startmenu_dir.exists():
20 | os.mkdir(radie_startmenu_dir)
21 |
22 | shell = Dispatch('WScript.Shell')
23 |
24 | if python.exists():
25 | if shortcut_console.exists():
26 | os.remove(shortcut_console)
27 | shortcut = shell.CreateShortCut(str(shortcut_console))
28 | shortcut.Targetpath = str(python)
29 | shortcut.Arguments = '-m radie.qt.viewer'
30 | shortcut.IconLocation = str(icon)
31 | shortcut.save()
32 |
33 | if pythonw.exists():
34 | if shortcut_windows.exists():
35 | os.remove(shortcut_windows)
36 | shortcut = shell.CreateShortCut(str(shortcut_windows))
37 | shortcut.Targetpath = str(pythonw)
38 | shortcut.Arguments = '-m radie.qt.viewer'
39 | shortcut.IconLocation = str(icon)
40 | shortcut.save()
41 |
--------------------------------------------------------------------------------
/radie/qt/dataframeview.py:
--------------------------------------------------------------------------------
1 | from PyQt5 import QtCore, QtWidgets, QtGui
2 |
3 | from ..structures.structureddataframe import StructuredDataFrame
4 |
5 |
6 | class PandasModel(QtCore.QAbstractTableModel):
7 | """
8 | Class to populate a table view with a pandas dataframe
9 | """
10 |
11 | def __init__(self, data, parent=None):
12 | QtCore.QAbstractTableModel.__init__(self, parent)
13 | self._data = data
14 |
15 | def rowCount(self, parent=None):
16 | return len(self._data.values)
17 |
18 | def columnCount(self, parent=None):
19 | return self._data.columns.size
20 |
21 | def data(self, index, role=QtCore.Qt.DisplayRole):
22 | if index.isValid():
23 | if role == QtCore.Qt.DisplayRole:
24 | return str(self._data.values[index.row()][index.column()])
25 | return None
26 |
27 | def headerData(self, col, orientation, role):
28 | if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
29 | return str(self._data.columns[col])
30 | return None
31 |
32 |
33 | def viewDataFrame(df: StructuredDataFrame):
34 | """
35 | Construct a TableView Dialog to view DataFrameContents)
36 |
37 | Parameters
38 | ----------
39 | df : StructuredDataFrame
40 | """
41 |
42 | # TODO: Implement Copy Function
43 |
44 | dialog = QtWidgets.QDialog(None)
45 | layout = QtWidgets.QVBoxLayout(dialog)
46 | layout.setContentsMargins(0, 0, 0, 0)
47 | view = QtWidgets.QTableView(None)
48 | view.setModel(PandasModel(df))
49 | layout.addWidget(view)
50 | dialog.exec_()
51 |
--------------------------------------------------------------------------------
/radie/plugins/__init__.py:
--------------------------------------------------------------------------------
1 | """plugins package, meant for custom data-structures and file-loaders and visualizations"""
2 |
3 | import os
4 | import pkgutil
5 | import importlib
6 |
7 | STRUCTURES_PKG_NAME = "structures"
8 | STRUCTURES_PKG = __package__ + "." + STRUCTURES_PKG_NAME
9 | LOADERS_PKG_NAME = "loaders"
10 | LOADERS_PKG = __package__ + "." + LOADERS_PKG_NAME
11 | VISUALIZATIONS_PKG_NAME = "visualizations"
12 | VISUALZIATIONS_PKG = __package__ + "." + VISUALIZATIONS_PKG_NAME
13 |
14 | this_dir = os.path.dirname(__file__)
15 | structures_dir = os.path.join(this_dir, STRUCTURES_PKG_NAME)
16 | loaders_dir = os.path.join(this_dir, LOADERS_PKG_NAME)
17 | visualizations_dir = os.path.join(this_dir, VISUALIZATIONS_PKG_NAME)
18 |
19 |
20 | disabled_plugins = (
21 | "powderdiffraction_siemensD500",
22 | )
23 |
24 |
25 | def import_structures():
26 | importlib.import_module(STRUCTURES_PKG)
27 | for finder, module_name, is_pkg in pkgutil.iter_modules([structures_dir]):
28 | if not is_pkg:
29 | importlib.import_module("." + module_name, STRUCTURES_PKG)
30 |
31 |
32 | def import_loaders():
33 | importlib.import_module(LOADERS_PKG)
34 | for finder, module_name, is_pkg in pkgutil.iter_modules([loaders_dir]):
35 | if not is_pkg and module_name not in disabled_plugins:
36 | importlib.import_module("." + module_name, LOADERS_PKG)
37 |
38 |
39 | def import_visualizations():
40 | importlib.import_module(VISUALZIATIONS_PKG)
41 | for finder, module_name, is_pkg in pkgutil.iter_modules([visualizations_dir]):
42 | if not is_pkg:
43 | importlib.import_module("." + module_name, VISUALZIATIONS_PKG)
44 |
--------------------------------------------------------------------------------
/radie/plugins/loaders/powderdiffraction_siemensD500.py:
--------------------------------------------------------------------------------
1 | """define loader objects that return PowderDiffraction Data Structures"""
2 | import re
3 | import numpy as np
4 | from radie.loaders import Loader, register_loaders
5 |
6 | from radie.plugins.structures.powderdiffraction import PowderDiffraction, CuKa
7 |
8 |
9 | def load_txt(fname):
10 | """
11 | .txt file output from Siemens D500 on custom 3M Software
12 |
13 | Parameters
14 | ----------
15 | fname : str
16 | filename
17 |
18 | Returns
19 | -------
20 | df_xrd : PowderDiffraction
21 | PowderDiffraction StructuredDataFrame based on XRD data
22 |
23 | """
24 |
25 | with open(fname, "r") as fid:
26 | lines = fid.readlines()
27 |
28 | lines = [line.strip() for line in lines]
29 | settings = lines.pop(0)
30 | mo = re.compile\
31 | (r'^"(?P.*)",(?P[0-9\.]+),(?P[0-9\.]+),(?P[0-9\.]+),(?P[0-9\.]+).*$')
32 | meta = mo.match(settings).groupdict()
33 |
34 | for k in ['min', 'max', 'step', 'count']:
35 | meta[k] = float(meta[k])
36 |
37 | data = np.array([[float(x) for x in line.split()] for line in lines])
38 | df_xrd = PowderDiffraction(data=data,
39 | columns=['twotheta', 'intensity'],
40 | wavelength=CuKa,
41 | source='CuKa',
42 | xunit="deg",
43 | yunit="counts",
44 | **meta)
45 |
46 | return df_xrd
47 |
48 |
49 | siemensD500_txt_loader = Loader(load_txt, PowderDiffraction, [".txt"], "Siemens D500")
50 |
51 | register_loaders(
52 | siemensD500_txt_loader,
53 | )
54 |
--------------------------------------------------------------------------------
/executables/standard_launcher.cpp:
--------------------------------------------------------------------------------
1 | /* Minimal main program -- everything is loaded from the library. */
2 |
3 | #define Py_LIMITED_API 1
4 | #include "Python.h"
5 |
6 | // replace #include "stdafx.h"
7 | #pragma once
8 | #include // #include "targetver.h"
9 |
10 | #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
11 | // Windows Header Files:
12 | #include
13 |
14 | // C RunTime Header Files
15 | #include
16 | #include
17 | #include
18 | #include
19 | // end include stdfax.h
20 |
21 | //#include "Windows.h"
22 |
23 | #ifdef WINGUI
24 | int APIENTRY wWinMain(
25 | _In_ HINSTANCE hInstance,
26 | _In_opt_ HINSTANCE hPrevInstance,
27 | _In_ LPWSTR lpCmdLine,
28 | _In_ int nCmdShow
29 | ) {
30 | int argc = __argc;
31 | wchar_t **argv = __wargv;
32 | #else
33 | int wmain(int argc, wchar_t **argv) {
34 | #endif
35 |
36 | // here we inject the python application launching commands into the arguments array
37 | int newargc;
38 | newargc = argc + 1;
39 | wchar_t **newargv = new wchar_t*[newargc];
40 |
41 | // determine the path of the executable so we know the absolute path
42 | // of the python runtime and application directories
43 | wchar_t executable_name[MAX_PATH];
44 | if (GetModuleFileName(NULL, executable_name, MAX_PATH) == 0)
45 | return 1;
46 |
47 | newargv[0] = argv[0];
48 | newargv[1] = executable_name;
49 | // newargv[1] = argv[0]; // call executable as the python zipapp
50 |
51 | for (int i = 1; i < argc; i++) {
52 | newargv[i + 2] = argv[i];
53 | }
54 |
55 | //now call Py_Main with our arguments
56 | return Py_Main(newargc, newargv);
57 | }
--------------------------------------------------------------------------------
/radie/plugins/visualizations/ui/psd.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | PSD
4 |
5 |
6 |
7 | 0
8 | 0
9 | 1000
10 | 561
11 |
12 |
13 |
14 | Particle Size Distribution
15 |
16 |
17 |
18 | 3
19 |
20 |
21 | 3
22 |
23 |
24 | 3
25 |
26 |
27 | 3
28 |
29 | -
30 |
31 |
32 | Qt::Horizontal
33 |
34 |
35 |
36 |
-
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | PlotWidget
49 | QGraphicsView
50 |
51 |
52 |
53 | DFListView
54 | QTreeView
55 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/radie/plugins/visualizations/ui/psd.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # Form implementation generated from reading ui file 'C:\Coding\Python\PythonPackageLinks\radie\plugins\visualizations\ui\psd.ui'
4 | #
5 | # Created by: PyQt5 UI code generator 5.6
6 | #
7 | # WARNING! All changes made in this file will be lost!
8 |
9 | from PyQt5 import QtCore, QtGui, QtWidgets
10 |
11 | class Ui_PSD(object):
12 | def setupUi(self, PSD):
13 | PSD.setObjectName("PSD")
14 | PSD.resize(1000, 561)
15 | self.verticalLayout = QtWidgets.QVBoxLayout(PSD)
16 | self.verticalLayout.setContentsMargins(3, 3, 3, 3)
17 | self.verticalLayout.setObjectName("verticalLayout")
18 | self.splitter = QtWidgets.QSplitter(PSD)
19 | self.splitter.setOrientation(QtCore.Qt.Horizontal)
20 | self.splitter.setObjectName("splitter")
21 | self.layoutWidget = QtWidgets.QWidget(self.splitter)
22 | self.layoutWidget.setObjectName("layoutWidget")
23 | self.verticalLayout_left = QtWidgets.QVBoxLayout(self.layoutWidget)
24 | self.verticalLayout_left.setContentsMargins(0, 0, 0, 0)
25 | self.verticalLayout_left.setObjectName("verticalLayout_left")
26 | self.listView_datasets = DFListView(self.layoutWidget)
27 | self.listView_datasets.setObjectName("listView_datasets")
28 | self.verticalLayout_left.addWidget(self.listView_datasets)
29 | self.plotWidget = PlotWidget(self.splitter)
30 | self.plotWidget.setObjectName("plotWidget")
31 | self.verticalLayout.addWidget(self.splitter)
32 |
33 | self.retranslateUi(PSD)
34 | QtCore.QMetaObject.connectSlotsByName(PSD)
35 |
36 | def retranslateUi(self, PSD):
37 | _translate = QtCore.QCoreApplication.translate
38 | PSD.setWindowTitle(_translate("PSD", "Particle Size Distribution"))
39 |
40 | from radie.qt.plotlist import DFListView
41 | from radie.qt.plotwidget import PlotWidget
42 |
--------------------------------------------------------------------------------
/radie/plugins/structures/tga.py:
--------------------------------------------------------------------------------
1 | from collections import OrderedDict
2 | import numpy as np
3 | import pandas as pd
4 | from radie.structures import StructuredDataFrame, register_data_structures
5 |
6 |
7 | class TGA(StructuredDataFrame):
8 | """
9 | StructuredDataFrame for holding Thermogravimetric analysis data
10 | Default units are minutes, mg, and celsius
11 | Please convert results to default units during instantiation, no unit checking is done within
12 | """
13 |
14 | label = "Thermogravimetric Analysis"
15 |
16 | _required_metadata = StructuredDataFrame._required_metadata.copy()
17 |
18 | _x = "time" # minutes
19 | _y = "weight" # mg
20 | _z = "temperature" # celsius
21 |
22 | _required_columns = OrderedDict((
23 | ("temperature", float),
24 | ("weight", float),
25 | ("time", float),
26 | ))
27 | _column_properties = ["norm_weight", "deriv_norm_weight", "deriv_weight"]
28 |
29 | @property
30 | def norm_weight(self):
31 | """Normalize the weight using the first datapoint (samples can gain or lose mass)"""
32 | return self.weight/self.weight[0]
33 |
34 | @property
35 | def deriv_norm_weight(self):
36 | """
37 | Normalized weight derivative wrt temperature
38 | Returns
39 | -------
40 | Array of derivative of normalized weight wrt time
41 | """
42 | y = self.norm_weight.values
43 | x = self.temperature.values
44 | return pd.Series(np.gradient(y, x), name='Deriv. Weight (%/°C)')
45 |
46 | @property
47 | def deriv_weight(self):
48 | """
49 | Normalized weight derivative wrt temperature
50 | Returns
51 | -------
52 | Array of derivative of normalized weight wrt time
53 | """
54 | y = self.weight
55 | x = self.temperature
56 | return pd.Series(np.gradient(y, x), name='Deriv. Weight (mg/°C)')
57 |
58 | register_data_structures(TGA)
59 |
--------------------------------------------------------------------------------
/doc/source/articles/intro_to_datastructures.rst:
--------------------------------------------------------------------------------
1 | Introduction to Data Structures
2 | ===============================
3 |
4 | Data structures get at the heart of what radie is for. Many kinds of data
5 | can easily be represented as labeled columns of data, combined with meta-data.
6 | The pandas python module provides a robust framework for this in the form of a
7 | StructuredDataFrame. For this module we define a lightly modified StructuredDataFrame subclass
8 | called a :doc:`StructuredDataFrame`
9 |
10 | As a StructuredDataFrame is a pandas dataframe, you can manipulate it in all the same
11 | ways that you can manipulate a StructuredDataFrame. The essential distinguishing attributes
12 | of a StructuredDataFrame are (Please see the API page for further details):
13 |
14 | * `_required_columns` , a class attribute defining data that a particular
15 | structure must have. For example, a powder diffraction measurement must have
16 | two columns: scattering angle (twotheta) and scattering intensity (intensity).
17 | * `_required_metadata` , a class attribute defining which values are required
18 | to completely determine the dataset. For example, a powder diffraction
19 | measurement must specify the wavelength of the incident radiation, which is
20 | 1.5414 for Cu-K\u0251, but may be different for Synchrotron X-Ray or Neutron
21 | Powder diffraction measurements.
22 |
23 | In the base class these attributes have empty values,
24 | meaning that in structure, the base class is no different than a pandas
25 | dataframe. Rather the structure is meant to be specified in subclasses
26 |
27 | By specifying the structures, it allows us to label and organize data sets, and
28 | more rapidly group them, and compare them, and visualize them in complex ways
29 | under the assumptions of their structures. In short, in the environment of many
30 | different types of datasets provided from measurements from many different
31 | vendors, it can greatly speed up analysis when dealing with many datasets.
32 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 |
3 | # To use a consistent encoding
4 | from codecs import open
5 | from os import path
6 |
7 | here = path.abspath(path.dirname(__file__))
8 |
9 | # Get the long description from the README file
10 | with open(path.join(here, 'README.md'), encoding='utf-8') as f:
11 | long_description = f.read()
12 |
13 | with open('radie/__init__.py') as fid:
14 | version_found = False
15 | for line in fid:
16 | if line.startswith('__version__'):
17 | version = line.split('=')[1].strip().replace("'", "")
18 | version_found = True
19 | break
20 | if not version_found:
21 | raise Exception('version string not found in __init__.py')
22 |
23 | setup(
24 | name='radie',
25 | version=version,
26 | description='a python application to rapidly import and view experimental data',
27 | long_description=long_description,
28 | long_description_content_type="text/markdown",
29 | url='https://www.github.com/dvincentwest/radie',
30 |
31 | # Author details
32 | author='D. Vincent West',
33 | author_email='dvincentwest@gmail.com',
34 |
35 | # Choose your license
36 | license='GPLv2',
37 |
38 | # See https://pypi.python.org/pypi?%3Aaction=list_classifiers
39 | classifiers=[
40 | 'Development Status :: 3 - Alpha',
41 | 'Intended Audience :: Science/Research',
42 | 'License :: OSI Approved :: GNU General Public License v2 (GPLv2)',
43 | 'Programming Language :: Python',
44 | 'Topic :: Scientific/Engineering :: Visualization',
45 | ],
46 | keywords='data visualization',
47 | packages=find_packages(exclude=['contrib', 'docs', 'tests*']),
48 | python_requires='>=3.5',
49 | install_requires=[
50 | 'numpy',
51 | 'pandas',
52 | # 'PyQt5' # left out because of naming conflicts with conda
53 | 'pyqtgraph',
54 | ],
55 | extras_require={},
56 | package_data={
57 | 'radie.plugins.examples': ['data/*'],
58 | 'radie.qt': ['resources/icons/*.svg'],
59 | 'radie.plugins.visualizations': ['icons/*.svg'],
60 | },
61 | data_files=[],
62 | entry_points={},
63 | )
64 |
--------------------------------------------------------------------------------
/radie/qt/plotwidget.py:
--------------------------------------------------------------------------------
1 | """Customize the Basic PyQtGraph PlotWidget with styles and helpful functions"""
2 |
3 | import types
4 |
5 | import pyqtgraph as pg
6 |
7 |
8 | class PlotWidget(pg.PlotWidget):
9 | def __init__(self, parent=None, background="default", **kwargs):
10 | super(PlotWidget, self).__init__(parent, background, **kwargs)
11 |
12 | # bolt on my PlotItem additions, for some reason all attempts to subclass don't really work
13 | self.plotItem.setupPlot = types.MethodType(PlotItem.setupPlot, self.plotItem)
14 | self.plotItem.resetLegend = types.MethodType(PlotItem.resetLegend, self.plotItem)
15 | self.plotItem.removeLegend = types.MethodType(PlotItem.removeLegend, self.plotItem)
16 | self.plotItem.setupPlot()
17 |
18 |
19 | class PlotItem(pg.PlotItem):
20 | def __init__(self, *args, **kwargs):
21 | super(PlotItem, self).__init__(*args, **kwargs)
22 | self.setupPlot()
23 |
24 | def setupPlot(self):
25 | """
26 | A universal style for datauick plots
27 |
28 | Parameters
29 | ----------
30 | self : pg.PlotItem
31 |
32 | """
33 | self.addLegend()
34 | self.showAxis('top')
35 | self.showAxis('right')
36 | self.showGrid(True, True, 0.1)
37 | self.getAxis('top').setStyle(showValues=False)
38 | self.getAxis('right').setStyle(showValues=False)
39 | self.vb.setMouseMode(pg.ViewBox.RectMode)
40 |
41 | def removeLegend(self):
42 | """remove the legend"""
43 | legend = self.legend
44 | try:
45 | legend.scene().removeItem(legend)
46 | self.legend = None
47 | except AttributeError:
48 | pass
49 |
50 | def resetLegend(self):
51 | """
52 | clear out the old legend, add a new one and repopulate
53 |
54 | Parameters
55 | ----------
56 | self : pg.PlotItem
57 |
58 | Returns
59 | -------
60 |
61 | """
62 | self.removeLegend()
63 | self.addLegend()
64 |
65 | for item in self.listDataItems():
66 | if item.name() is not None:
67 | self.legend.addItem(item, item.name())
68 |
--------------------------------------------------------------------------------
/radie/qt/resources/icons/xyline.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
70 |
--------------------------------------------------------------------------------
/executables/build.bat:
--------------------------------------------------------------------------------
1 | :: This build script uses the MSVC command line tools to build Windows Executables
2 | :: To launch the radie pyqt application
3 |
4 | :: creates Console and GUI executables for:
5 | :: - Embedded distribution (load python.dll after .exe loads)
6 | :: - Standard Launcher for Python environment on the path (load python.dll at .exe load time)
7 |
8 | @echo off
9 |
10 | SET APPNAME=radie
11 | set ZIPAPP=radie_qt_viewer
12 |
13 | :: Bare Python installs for development headers/libraries
14 | set PY3_x64=C:\Miniconda3\
15 |
16 | set VCVARSALL="C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\VC\Auxiliary\Build\vcvarsall.bat"
17 | if not exist %VCVARSALL% (
18 | set VCVARSALL="C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvarsall.bat"
19 | )
20 |
21 | :: Icon File
22 | set LINK=%APPNAME%.res
23 |
24 | :: use environment variables for INCLUDE LIB and LINK values
25 | set UseEnv=true
26 |
27 | :: Set environment variables for 64-bit and build
28 | if "%1"=="NOVS" (
29 | echo "skipping vcvarsall"
30 | ) else (
31 | set LIB=""
32 | set INCLUDE=""
33 | call %VCVARSALL% amd64
34 | )
35 | rc.exe %APPNAME%.rc
36 |
37 | :: set environment variables for python libraries
38 | set LIB=%PY3_x64%libs;%LIB%
39 | set INCLUDE=%PY3_x64%include;%INCLUDE%
40 |
41 | call python -m zipapp %ZIPAPP% -o %ZIPAPP%.pyz
42 |
43 | :: Compiler Flags
44 | :: /EHsc -> Exception handling : If used with s (/EHsc), catches C++ exceptions only and tells the compiler to assume that functions declared as extern "C" never throw a C++ exception.
45 | :: /DUNICODE /D_UNICODE -> Unicode preprocessor flags, in my case, fixes things like passing wchar_t pointers to functions expecting LPSTR, and other things as well.
46 | :: /Fe -> Name of the exe file, used as /Fe%nameofexe%
47 |
48 | echo Compiling embedded launchers
49 | cl /EHsc /DUNICODE /D_UNICODE /DWINGUI embedded_runtime_launcher.cpp /Feout
50 | COPY /B /Y out.exe+%ZIPAPP%.pyz exe\%APPNAME%-embedded.exe
51 |
52 | cl /EHsc /DUNICODE /D_UNICODE embedded_runtime_launcher.cpp /Feout
53 | COPY /B /Y out.exe+%ZIPAPP%.pyz exe\%APPNAME%-embedded-Console.exe
54 |
55 | echo Compiling standard launchers
56 | cl /EHsc /DUNICODE /D_UNICODE /DWINGUI standard_launcher.cpp /Feout
57 | COPY /B /Y out.exe+%ZIPAPP%.pyz exe\%APPNAME%.exe
58 |
59 | cl /EHsc /DUNICODE /D_UNICODE standard_launcher.cpp /Feout
60 | COPY /B /Y out.exe+%ZIPAPP%.pyz exe\%APPNAME%-Console.exe
61 |
62 | del out.exe
63 | del *.obj
64 |
65 | @echo on
66 |
--------------------------------------------------------------------------------
/radie/qt/visualizations/base.py:
--------------------------------------------------------------------------------
1 | """define the generic Visualization class"""
2 | import os
3 |
4 | from PyQt5 import QtWidgets, QtGui
5 | from ...structures.structureddataframe import StructuredDataFrame
6 |
7 |
8 | class Visualization(QtWidgets.QWidget):
9 | """base class for QtWidget visualizations
10 |
11 | Very minimal skeleton defining the expected behavior a QWidget used to visualize a StructuredDataFrame. Almost
12 | anything that will fit into a QtWidget is acceptable as a Visualization. Most of the internals are defined in
13 | subclass.
14 |
15 | Attributes
16 | ----------
17 | name : str
18 | *cls var*, The name of the visualization to be displayed in menus
19 | description : str
20 | *cls var*, a short one line description
21 | supported_classes : list of Type(StructuredDataFrame)
22 | *cls var*, provide an easy mechanism to validate if the StructuredDataFrame objects in the visualization
23 | will play nice with the visualization
24 |
25 | Notes
26 | -----
27 | Private class variables for subclassing:
28 |
29 | _icon_image : str
30 | the path to the icon image used for this visualization
31 | _icon : QtGui.QIcon
32 | the Qt Icon constructed using `_icon_image` after an icon is requested from the application
33 |
34 | """
35 |
36 | name = 'Base' # type: str
37 | description = None # type: str
38 | _icon_image = None # type: str
39 | _icon = None # type: QtGui.QIcon
40 |
41 | supportedClasses = ( # must be a tuple
42 | StructuredDataFrame,
43 | )
44 |
45 | @classmethod
46 | def icon(cls):
47 | """return the icon specified in cls._icon_image, or a blank icon if no icon is specified
48 |
49 | Returns
50 | -------
51 | QtGui.QIcon
52 |
53 | Notes
54 | -----
55 | Certain versions of PyQt fail if a QIcon is constructed before the QApplication. We specify the icon
56 | image file in cls._icon_image and then a QIcon is then constructed the first time this method is called.
57 | Subsequent calls return the previously constructed QIcon object
58 |
59 | """
60 | if isinstance(cls._icon, QtGui.QIcon):
61 | return cls._icon
62 | elif not cls._icon_image:
63 | return QtGui.QIcon()
64 |
65 | if os.path.isfile(cls._icon_image):
66 | try:
67 | cls._icon = QtGui.QIcon(cls._icon_image)
68 | return cls._icon
69 | except:
70 | cls._icon = None
71 |
72 | return QtGui.QIcon()
73 |
74 | def exportToExcel(self):
75 | """optional method to export the visualization to an excel file"""
76 | raise NotImplementedError
77 |
78 | def copyImage(self):
79 | """put an image of the visualization on the clipboard"""
80 | raise NotImplementedError
81 |
--------------------------------------------------------------------------------
/radie/qt/resources/icons/xypoints.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
83 |
--------------------------------------------------------------------------------
/radie/qt/indexabledict.py:
--------------------------------------------------------------------------------
1 | import typing
2 | from collections import OrderedDict
3 |
4 |
5 | class IndexableDict(OrderedDict):
6 | """
7 | A class that defines an ordered dictionary where the items are indexable with common slicing operations as well
8 | as by the dict key. We can also retrieve the 'row' of the item as an integer by providing the key, or the value,
9 | in which case row will return the first instance of value
10 | """
11 |
12 | def __getitem__(self, key):
13 | if type(key) is slice:
14 | return tuple(self.values())[key]
15 |
16 | # can only use this functionality with slice because ints are valid dict keys, leaving here until I'm sure
17 | # all such uses are gone
18 | # elif type(key) is int:
19 | # try:
20 | # key = tuple(self)[key]
21 | # except ValueError:
22 | # raise ValueError("index {:} out of range".format(key))
23 |
24 | return super(IndexableDict, self).__getitem__(key)
25 |
26 | def getKey(self, index):
27 | """
28 | return the dict key at the given index
29 |
30 | Parameters
31 | ----------
32 | index : int
33 |
34 | Returns
35 | -------
36 | typing.Any
37 | """
38 | return tuple(self)[index]
39 |
40 | def getValue(self, index):
41 | """
42 | return the value at the given index
43 |
44 | Parameters
45 | ----------
46 | index : int
47 |
48 | Returns
49 | -------
50 | typing.Any
51 | """
52 | return tuple(self.values())[index]
53 |
54 | def getKeyAndValue(self, row):
55 | """
56 | return the ith key, value pair at row i
57 |
58 | Parameters
59 | ----------
60 | row : int
61 |
62 | Returns
63 | -------
64 | key : typing.Hashable
65 | value : typing.Any
66 | """
67 | key = self.getKey(row)
68 | return key, self[key]
69 |
70 | def rowCount(self):
71 | """
72 | QAbstractIndex convenience function
73 |
74 | Returns
75 | -------
76 | int
77 | """
78 | return len(self)
79 |
80 | def getKeyRow(self, key):
81 | """
82 | return the position of key in the dict
83 |
84 | Parameters
85 | ----------
86 | key : typing.Hashable
87 |
88 | Returns
89 | -------
90 | int
91 | """
92 | return tuple(self).index(key)
93 |
94 | def getValueRow(self, value):
95 | """
96 | return the position of the first occurence of value in the dict
97 |
98 | Parameters
99 | ----------
100 | value : typing.Any
101 |
102 | Returns
103 | -------
104 | int
105 | """
106 | return tuple(self.values()).index(value)
107 |
--------------------------------------------------------------------------------
/radie/plugins/loaders/vsm_lakeshore.py:
--------------------------------------------------------------------------------
1 | import os
2 | import traceback
3 | import sys
4 | import re
5 |
6 | import numpy as np
7 | import pandas as pd
8 |
9 | from radie import exceptions
10 | from radie.loaders import Loader, register_loaders
11 | from radie.plugins.structures.vsm import VSM
12 |
13 |
14 | def load_ideavsm_dat(fname):
15 | """complete experimental output from Lakeshore, missing sample_id information, so we take it from the file"""
16 | with open(fname, 'r') as fid:
17 | file_contents = fid.read() # type: str
18 |
19 | data_line = re.search("\n\*+ Experiment Data \*+\n", file_contents)
20 | results_line = re.search("\n\*+ Results \*+\n", file_contents)
21 |
22 | if not data_line:
23 | raise exceptions.IncorrectFileType("Could not locate the DataLine")
24 | if not results_line:
25 | raise exceptions.IncorrectFileType("Could not locate the ResultsLine")
26 |
27 | data_string = file_contents[data_line.end():results_line.start()] # type: str
28 | data_lines = data_string.splitlines()[1:-1]
29 | data_length = int(data_lines[0].split(" ")[1].strip())
30 |
31 | data_segments = (len(data_lines) - 1) / (data_length + 1) # type: float
32 | if not data_segments.is_integer():
33 | # data_segments does not appear to be correctly evaluated as a whole number
34 | raise exceptions.IncorrectFileType
35 |
36 | df = pd.DataFrame()
37 | for i in range(int(data_segments)):
38 | start = 1 + i * (data_length + 1)
39 |
40 | data_label = data_lines[start] # type: str
41 | if data_label.endswith(" Data"):
42 | data_label = data_label[:-5]
43 |
44 | data = np.array(data_lines[start + 1: start + 1 + data_length], dtype="float")
45 | df[data_label] = data
46 | name = os.path.basename(fname)
47 | df.rename(columns={"MomentX": "Moment"}, inplace=True)
48 | return VSM(df, name=name, date=None)
49 |
50 |
51 | def load_ideavsm_txt(fname):
52 | """"simple moment v. Field Output from Lakeshore Idea"""
53 |
54 | try:
55 | with open(fname, "r") as fid:
56 | header = [fid.readline() for _ in range(12)]
57 | except EOFError:
58 | raise exceptions.IncorrectFileType
59 |
60 | # check to see if this is an understood filetype
61 | if not (
62 | header[0].startswith("Start Time: ") and
63 | header[1].startswith("Sample ID: ") and
64 | header[3].startswith("Experiment: ") and
65 | header[4].startswith("Data File: ") and
66 | header[9].startswith("***DATA***") and
67 | header[11].startswith("Field(G)\t Moment(emu)")
68 | ):
69 | raise exceptions.IncorrectFileType
70 | name = header[1].strip().split(":")[1].strip()
71 | date = header[0].strip().split(":")[1].strip().split()[0]
72 |
73 | df = pd.read_csv(fname, sep="\s+", header=8, index_col=False)
74 | df.rename(columns={"Field(G)": "Field", "Moment(emu)": "Moment"}, inplace=True)
75 | df_vsm = VSM(data=df, name=name, date=date)
76 | return df_vsm
77 |
78 |
79 | lakeshore_ideas_vsm_dat = Loader(load_ideavsm_dat, VSM, [".dat"], "Lakeshore IDEAs VSM (.dat)")
80 | lakeshore_ideas_vsm_txt = Loader(load_ideavsm_txt, VSM, [".txt"], "Lakeshore IDEAs VSM (.txt)")
81 |
82 | register_loaders(
83 | lakeshore_ideas_vsm_dat,
84 | lakeshore_ideas_vsm_txt,
85 | )
86 |
--------------------------------------------------------------------------------
/radie/plugins/visualizations/ui/tga.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | TGA
4 |
5 |
6 |
7 | 0
8 | 0
9 | 1000
10 | 561
11 |
12 |
13 |
14 | TGA
15 |
16 |
17 |
18 | 3
19 |
20 |
21 | 3
22 |
23 |
24 | 3
25 |
26 |
27 | 3
28 |
29 | -
30 |
31 |
32 | Qt::Horizontal
33 |
34 |
35 |
36 |
-
37 |
38 |
39 | -
40 |
41 |
-
42 |
43 |
44 | -
45 |
46 |
47 | Primary Vertical Axis
48 |
49 |
50 |
51 | -
52 |
53 |
54 | -
55 |
56 |
57 | Secondary Vertical Axis
58 |
59 |
60 |
61 | -
62 |
63 |
64 | Normalize weight
65 |
66 |
67 |
68 | -
69 |
70 |
71 | -
72 |
73 |
74 | Horizontal Axis
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | PlotWidget
90 | QGraphicsView
91 |
92 |
93 |
94 | DFListView
95 | QTreeView
96 |
97 |
98 |
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/radie/qt/resources/icons/saveicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
75 |
--------------------------------------------------------------------------------
/radie/plugins/structures/psd.py:
--------------------------------------------------------------------------------
1 | from collections import OrderedDict
2 |
3 | import numpy as np
4 | from radie.structures import StructuredDataFrame, register_data_structures
5 |
6 |
7 | class PSD(StructuredDataFrame):
8 | """
9 | StructuredDataFrame for holding Particle Size Distribution data
10 |
11 | """
12 |
13 | label = "PSD"
14 |
15 | _required_metadata = StructuredDataFrame._required_metadata.copy()
16 | _required_metadata.update({
17 | "refractive_index": "1.0",
18 | "distribution_mode": "volume",
19 | "ultrasound_time": 0, # ultrasound time in seconds
20 | })
21 |
22 | _required_columns = OrderedDict((
23 | ("diameter", float),
24 | ("frequency", float),
25 | ))
26 | _column_properties = ["oversize"]
27 |
28 | @property
29 | def d50(self):
30 | """
31 | Returns
32 | -------
33 | D50 : float
34 | Particles with diameters of D50 or less account for 50% of the volume
35 | """
36 | return self.d(50)
37 |
38 | @property
39 | def d90(self):
40 | """
41 | Returns
42 | -------
43 | D90 : float
44 | Particles with diameters of D90 or less account for 90% of the volume
45 | """
46 | return self.d(90)
47 |
48 | @property
49 | def d10(self):
50 | """
51 | Returns
52 | -------
53 | d10 : float
54 | Particles with diameters of D10 or less account for 10% of the volume
55 | """
56 | return self.d(10)
57 |
58 | @property
59 | def dmin(self):
60 | return self.diameter[self.oversize < 100].iloc[0]
61 |
62 | @property
63 | def dmax(self):
64 | return self.diameter[self.oversize > 0.0].iloc[-1]
65 |
66 | def d(self, number):
67 | """
68 | Parameters
69 | ----------
70 | number : int or float
71 | Distribution fraction used to determine the diameter.
72 | Assumes D10 means low diameter end and D90 means high diameter end
73 | Must be in [0,100] range
74 |
75 | Returns
76 | -------
77 | d : float
78 | Particles with diameters of "d" or less account for "number" of the volume
79 |
80 | """
81 | if number > 100 or number < 0:
82 | raise ValueError("Supplied number (%s) must be in [0,100] range" % number)
83 | if number == 0:
84 | return self.dmin
85 | elif number == 100:
86 | return self.dmax
87 |
88 | oversize_target = 100 - number
89 | oversize_less = self.oversize <= oversize_target
90 | oversize_more = self.oversize > oversize_target
91 | d_min, oversize_min = self.diameter[oversize_more].iloc[-1], self.oversize[oversize_more].iloc[-1]
92 | d_max, oversize_max = self.diameter[oversize_less].iloc[0], self.oversize[oversize_less].iloc[0]
93 | return np.exp(
94 | np.interp(oversize_target, np.array([oversize_max, oversize_min]), np.log(np.array([d_max, d_min]))))
95 |
96 | @property
97 | def oversize(self):
98 | """
99 | Returns
100 | -------
101 | oversize : ndarray, float
102 | """
103 | oversize = 100.0 - self.frequency.cumsum()
104 | oversize.name = "oversize"
105 | return oversize
106 |
107 |
108 | register_data_structures(PSD)
109 |
--------------------------------------------------------------------------------
/radie/qt/resources/icons/seriesvisualization.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
95 |
--------------------------------------------------------------------------------
/radie/qt/resources/icons/xyscatter.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
95 |
--------------------------------------------------------------------------------
/radie/plugins/loaders/powderdiffraction_peaks.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import os.path
3 |
4 | from radie.loaders import Loader, register_loaders
5 | from radie.plugins.structures.powderdiffraction import PowderDiffraction, CuKa
6 |
7 |
8 | def convert_to_line_peaks(xy, ybase=0, ynorm=100.):
9 | line_peaks = []
10 | for x, y in xy:
11 | line_peaks.append([x, ybase])
12 | line_peaks.append([x, y])
13 | line_peaks.append([x, ybase])
14 |
15 | # Normalize peaks
16 | line_peaks = np.array(line_peaks)
17 | line_peaks[:, 1] = line_peaks[:, 1] / line_peaks[:, 1].max() * ynorm
18 | return line_peaks
19 |
20 |
21 | def load_peaks(fname, name=None):
22 | """
23 | .peaks file
24 | Expected format is just "#" commented header and two columns of 2theta and intensities for the peaks
25 | Data gets reworked to make bars in a line graph with intensities of max 1000
26 |
27 | Parameters
28 | ----------
29 | fname : str
30 | filename
31 | name : str
32 | measurement identifier
33 |
34 | Returns
35 | -------
36 | df_xrd : single PowderDiffraction
37 |
38 | """
39 | ybase = 0.0
40 | ynorm = 100.
41 |
42 | with open(fname, 'rb') as f:
43 | peaks = np.loadtxt(f, delimiter=",")
44 |
45 | # The expectation is that this is a list of 2theta and peaks, in order to make it a line
46 | # it should be processed and shoulder point added
47 |
48 | line_peaks = convert_to_line_peaks(peaks, ybase=ybase, ynorm=ynorm)
49 |
50 | # Use filename for name
51 | name = os.path.splitext(os.path.basename(fname))[0]
52 | df_xrd = PowderDiffraction(data=line_peaks,
53 | columns=['twotheta', 'intensity'],
54 | wavelength=CuKa,
55 | name=name)
56 |
57 | return df_xrd
58 |
59 |
60 | def calc_twotheta_from_d(d, wavelength=CuKa):
61 | """
62 | Calcuate twotheta at a given wavelength from the length of the reciprocal lattice vector `Q`
63 |
64 | Parameters
65 | ----------
66 | d : np.ndarray, float
67 | d spacings
68 | wavelength : float
69 | the desired wavelength in angstroms, defaults to CuKa
70 |
71 | Returns
72 | -------
73 | twotheta : np.ndarray, float
74 | diffraction angle in degrees
75 |
76 | """
77 | return np.rad2deg(2 * np.arcsin(wavelength / (2 * d)))
78 |
79 |
80 | def load_dpeaks(fname, name=None):
81 | """
82 | .peaks file
83 | Expected format is just "#" commented header and two columns of 2theta and intensities for the peaks
84 | Data gets reworked to make bars in a line graph with intensities of max 1000
85 |
86 | Parameters
87 | ----------
88 | fname : str
89 | filename
90 | name : str
91 | measurement identifier
92 |
93 | Returns
94 | -------
95 | df_xrd : single PowderDiffraction
96 |
97 | """
98 | ybase = 0.0
99 | ynorm = 100.
100 |
101 | with open(fname, 'rb') as f:
102 | peaks = np.loadtxt(f, delimiter=",")
103 |
104 | # The expectation is that this is a list of d spacings
105 | peaks[:, 0] = calc_twotheta_from_d(peaks[:, 0])
106 | line_peaks = convert_to_line_peaks(peaks)
107 |
108 | # Use filename for name
109 | name = os.path.splitext(os.path.basename(fname))[0]
110 | df_xrd = PowderDiffraction(data=line_peaks,
111 | columns=['twotheta', 'intensity'],
112 | wavelength=CuKa,
113 | name=name)
114 |
115 | return df_xrd
116 |
117 |
118 | xrd_peaks_loader = Loader(load_peaks, PowderDiffraction, [".peaks"], "twotheta peak file")
119 | xrd_dpeaks_loader = Loader(load_dpeaks, PowderDiffraction, [".dpeaks"], "d-spacing peak file")
120 | register_loaders(xrd_peaks_loader, xrd_dpeaks_loader)
121 |
--------------------------------------------------------------------------------
/radie/plugins/visualizations/ui/tga.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # Form implementation generated from reading ui file 'C:\Users\a2nldzz\Documents\repos\radie\radie\plugins\visualizations\ui\tga.ui'
4 | #
5 | # Created by: PyQt5 UI code generator 5.9.2
6 | #
7 | # WARNING! All changes made in this file will be lost!
8 |
9 | from PyQt5 import QtCore, QtGui, QtWidgets
10 |
11 | class Ui_TGA(object):
12 | def setupUi(self, TGA):
13 | TGA.setObjectName("TGA")
14 | TGA.resize(1000, 561)
15 | self.verticalLayout = QtWidgets.QVBoxLayout(TGA)
16 | self.verticalLayout.setContentsMargins(3, 3, 3, 3)
17 | self.verticalLayout.setObjectName("verticalLayout")
18 | self.splitter = QtWidgets.QSplitter(TGA)
19 | self.splitter.setOrientation(QtCore.Qt.Horizontal)
20 | self.splitter.setObjectName("splitter")
21 | self.layoutWidget = QtWidgets.QWidget(self.splitter)
22 | self.layoutWidget.setObjectName("layoutWidget")
23 | self.verticalLayout_left = QtWidgets.QVBoxLayout(self.layoutWidget)
24 | self.verticalLayout_left.setContentsMargins(0, 0, 0, 0)
25 | self.verticalLayout_left.setObjectName("verticalLayout_left")
26 | self.listView_datasets = DFListView(self.layoutWidget)
27 | self.listView_datasets.setObjectName("listView_datasets")
28 | self.verticalLayout_left.addWidget(self.listView_datasets)
29 | self.formLayout_parameters = QtWidgets.QFormLayout()
30 | self.formLayout_parameters.setObjectName("formLayout_parameters")
31 | self.comboBox_y1 = QtWidgets.QComboBox(self.layoutWidget)
32 | self.comboBox_y1.setObjectName("comboBox_y1")
33 | self.formLayout_parameters.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.comboBox_y1)
34 | self.label_2 = QtWidgets.QLabel(self.layoutWidget)
35 | self.label_2.setObjectName("label_2")
36 | self.formLayout_parameters.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.label_2)
37 | self.comboBox_y2 = QtWidgets.QComboBox(self.layoutWidget)
38 | self.comboBox_y2.setObjectName("comboBox_y2")
39 | self.formLayout_parameters.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.comboBox_y2)
40 | self.label_3 = QtWidgets.QLabel(self.layoutWidget)
41 | self.label_3.setObjectName("label_3")
42 | self.formLayout_parameters.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.label_3)
43 | self.checkBox_normalizeWeight = QtWidgets.QCheckBox(self.layoutWidget)
44 | self.checkBox_normalizeWeight.setObjectName("checkBox_normalizeWeight")
45 | self.formLayout_parameters.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.checkBox_normalizeWeight)
46 | self.comboBox_x = QtWidgets.QComboBox(self.layoutWidget)
47 | self.comboBox_x.setObjectName("comboBox_x")
48 | self.formLayout_parameters.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.comboBox_x)
49 | self.label = QtWidgets.QLabel(self.layoutWidget)
50 | self.label.setObjectName("label")
51 | self.formLayout_parameters.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.label)
52 | self.verticalLayout_left.addLayout(self.formLayout_parameters)
53 | self.plotWidget = PlotWidget(self.splitter)
54 | self.plotWidget.setObjectName("plotWidget")
55 | self.verticalLayout.addWidget(self.splitter)
56 |
57 | self.retranslateUi(TGA)
58 | QtCore.QMetaObject.connectSlotsByName(TGA)
59 |
60 | def retranslateUi(self, TGA):
61 | _translate = QtCore.QCoreApplication.translate
62 | TGA.setWindowTitle(_translate("TGA", "TGA"))
63 | self.label_2.setText(_translate("TGA", "Primary Vertical Axis"))
64 | self.label_3.setText(_translate("TGA", "Secondary Vertical Axis"))
65 | self.checkBox_normalizeWeight.setText(_translate("TGA", "Normalize weight"))
66 | self.label.setText(_translate("TGA", "Horizontal Axis"))
67 |
68 | from radie.qt.plotlist import DFListView
69 | from radie.qt.plotwidget import PlotWidget
70 |
--------------------------------------------------------------------------------
/radie/plugins/visualizations/ui/powderdiffraction.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | PowderDiffraction
4 |
5 |
6 |
7 | 0
8 | 0
9 | 1000
10 | 561
11 |
12 |
13 |
14 | PowderDiffraction
15 |
16 |
17 |
18 | 3
19 |
20 |
21 | 3
22 |
23 |
24 | 3
25 |
26 |
27 | 3
28 |
29 | -
30 |
31 |
32 | Qt::Horizontal
33 |
34 |
35 |
36 |
-
37 |
38 |
39 | -
40 |
41 |
-
42 |
43 |
44 | -
45 |
46 |
47 | X-Stagger (%)
48 |
49 |
50 |
51 | -
52 |
53 |
54 | Y-Stagger (%)
55 |
56 |
57 |
58 | -
59 |
60 |
61 | Normalize intensity
62 |
63 |
64 |
65 | -
66 |
67 |
68 | 1000.000000000000000
69 |
70 |
71 | 5.000000000000000
72 |
73 |
74 |
75 | -
76 |
77 |
78 | 1000.000000000000000
79 |
80 |
81 | 5.000000000000000
82 |
83 |
84 |
85 | -
86 |
87 |
88 | 6
89 |
90 |
91 | 0.010000000000000
92 |
93 |
94 | 0.100000000000000
95 |
96 |
97 | 1.540000000000000
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 | PlotWidget
113 | QGraphicsView
114 |
115 |
116 |
117 | DFListView
118 | QTreeView
119 |
120 |
121 |
122 |
123 |
124 |
125 |
--------------------------------------------------------------------------------
/radie/plugins/structures/powderdiffraction.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | from collections import OrderedDict
3 |
4 | from radie.structures import StructuredDataFrame, register_data_structures
5 |
6 | CuKa1 = 1.5405980 # angstroms
7 | CuKa2 = 1.5444260 # angstroms
8 | CuKa = (2 * CuKa1 + CuKa2) / 3 # scaled avg of CuKa1 and 2, =1.541874
9 | CuKb1 = 1.392250 # angstroms
10 |
11 |
12 | def calc_Q(twotheta, wavelength):
13 | """
14 | Calculate the length of the reciprocal lattice vector given a diffraction angle and wavelength
15 |
16 | Parameters
17 | ----------
18 | twotheta : np.ndarray, float
19 | diffraction angle in degrees
20 | wavelength : float
21 | radiation wavelength in angstroms
22 |
23 | Returns
24 | -------
25 | Q : np.ndarray, float
26 | reciprocal lattice vector length
27 |
28 | """
29 | return 4 * np.pi / wavelength * np.sin(np.deg2rad(twotheta / 2))
30 |
31 |
32 | def calc_twotheta(Q, wavelength):
33 | """
34 | Calcuate twotheta at a given wavelength from the length of the reciprocal lattice vector `Q`
35 |
36 | Parameters
37 | ----------
38 | Q : np.ndarray, float
39 | the length of the reciprocal lattice vector
40 | wavelength : float
41 | the desired wavelength in angstroms
42 |
43 | Returns
44 | -------
45 | twotheta : np.ndarray, float
46 | diffraction angle in degrees
47 |
48 | """
49 | return np.rad2deg(2 * np.arcsin(Q * wavelength / (2 * np.pi)))
50 |
51 |
52 | def convert_wavelength(wl1, wl2, twotheta1):
53 | """
54 | Convert twotheta values to those for another wavelength
55 |
56 | Parameters
57 | ----------
58 | wl1 : float
59 | the original wavelength in angstroms
60 | wl2 : float
61 | the new wavelength in angstroms
62 | twotheta1 : np.ndarray, float
63 | the original twotheta values in degrees
64 |
65 | Returns
66 | -------
67 | twotheta2 : np.ndarray, float
68 | the new twotheta values in degrees
69 |
70 | """
71 | rad1 = np.deg2rad(twotheta1)
72 | rad2 = 2 * np.arcsin(wl2 / wl1 * np.sin(rad1 / 2))
73 | return np.rad2deg(rad2)
74 |
75 |
76 | def calc_d_spacing(x, wavelength=None):
77 | """
78 | Calculate the d spacing given the x values, where x is either twotheta or the reciprocal lattice vector length Q
79 |
80 | Parameters
81 | ----------
82 | x : np.ndarray, float
83 | Q or twotheta. if wavelength is provided, x is twotheta in degrees, otherwise Q
84 | wavelength : float, optional
85 | the wavelength
86 |
87 | Returns
88 | -------
89 | d_spacing : np.ndarray, float
90 | the lattice spacing in Angstroms
91 |
92 | """
93 |
94 | if wavelength is None:
95 | d_spacing = np.pi / x
96 | else:
97 | d_spacing = wavelength / (2 * np.sin(np.deg2rad(x) / 2))
98 |
99 | return d_spacing
100 |
101 |
102 | class PowderDiffraction(StructuredDataFrame):
103 | """
104 | StructuredDataFrame for holding Powder diffraction data, valid for any bragg type diffraction measurements:
105 | Benchtop XRD, Synchrotron XRD, Neutron Diffraction
106 | """
107 |
108 | label = "Powder Diffraction"
109 |
110 | _required_metadata = StructuredDataFrame._required_metadata.copy()
111 | _required_metadata.update({
112 | "wavelength": CuKa,
113 | "source": "",
114 | })
115 |
116 | _x = "twotheta"
117 | _y = "intensity"
118 |
119 | _required_columns = OrderedDict((
120 | ("twotheta", float),
121 | ("intensity", float),
122 | ))
123 | _column_properties = ["Q", "d_spacing"]
124 |
125 | @property
126 | def Q(self):
127 | """return Q, the length of the reciprocal lattice vector"""
128 | if self.metadata["wavelength"] is None:
129 | raise ValueError("Must specify a wavelength to determine Q")
130 | srs = calc_Q(self.twotheta, self.metadata["wavelength"])
131 | srs.name = "Q"
132 | return srs
133 |
134 | def twotheta_at_wavelength(self, wavelength):
135 | return convert_wavelength(self.metadata["wavelength"], wavelength, self.twotheta)
136 |
137 | @property
138 | def d_spacing(self):
139 | """return d-spacing calculated from n=1 in the Bragg equation n * lambda = 2 * d sin(theta)"""
140 | if self.metadata["wavelength"] is None:
141 | raise ValueError("Must specify a wavelength to determine d-spacing")
142 | srs = calc_d_spacing(self.twotheta, self.metadata["wavelength"])
143 | srs.name = "d_spacing"
144 | return srs
145 |
146 | register_data_structures(PowderDiffraction)
147 |
--------------------------------------------------------------------------------
/radie/qt/resources/icons/radie.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
93 |
--------------------------------------------------------------------------------
/executables/embedded_runtime_launcher.cpp:
--------------------------------------------------------------------------------
1 | /* source code for a python application launcher
2 | *
3 | * This code is primarily intended for use in a suite of applications
4 | * with an embedded python runtime available
5 | *
6 | * The code creates a launcher that will start
7 | * a python application by injecting the proper command line arguments
8 | * that would normally be expected by python.exe. The command line
9 | * arguments fed to this launcher then become the subsequent
10 | *
11 | * The pythonXX.dll is loaded dynamically at run time, rather than at load
12 | * time. This allows us to specify the location of the dll within the code
13 | * allowing more flexibility when packaging a python appication with the
14 | * python environment
15 | *
16 | */
17 |
18 | // requires _CRT_SECURE_NO_WARNINGS flag to compile deprecated path operations
19 |
20 | // replace #include "stdafx.h"
21 | #pragma once
22 | #include // #include "targetver.h"
23 |
24 | #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
25 | // Windows Header Files:
26 | #include
27 |
28 | // C RunTime Header Files
29 | #include
30 | #include
31 | #include
32 | #include
33 | // end include stdfax.h
34 |
35 | //#include "Windows.h"
36 | #include "Shlwapi.h"
37 | // #include "Python.h" // don't need this as we are dynamically loading the library of choice
38 | #include
39 | #include
40 | #include
41 |
42 | #pragma comment(lib, "Shlwapi.lib")
43 | #pragma warning(disable:4996) // # _CRT_SECURE_NO_DEPRECIATE
44 |
45 | wchar_t runtime_dir[] = L"\\runtime";
46 | wchar_t applications_dir[] = L"\\apps";
47 | wchar_t python_dll[] = L"\\python36.dll";
48 |
49 | // define the pythonXX.dll function call signature for the Py_Main function
50 | typedef int(__stdcall *py_main_function)(int, wchar_t**);
51 | typedef void(__stdcall *py_setpath_function)(const wchar_t *);
52 |
53 | using namespace std;
54 |
55 | #ifdef WINGUI
56 | int APIENTRY wWinMain(
57 | _In_ HINSTANCE hInstance,
58 | _In_opt_ HINSTANCE hPrevInstance,
59 | _In_ LPWSTR lpCmdLine,
60 | _In_ int nCmdShow
61 | ) {
62 | int argc = __argc;
63 | wchar_t **argv = __wargv;
64 | #else
65 | int wmain(int argc, wchar_t **argv) {
66 | #endif
67 |
68 | // determine the path of the executable so we know the absolute path
69 | // of the python runtime and application directories
70 | wchar_t executable_name[MAX_PATH];
71 | if (GetModuleFileName(NULL, executable_name, MAX_PATH) == 0)
72 | return 1;
73 |
74 | wchar_t executable_dir[MAX_PATH];
75 | if (GetModuleFileName(NULL, executable_dir, MAX_PATH) == 0)
76 | return 1;
77 | PathRemoveFileSpec(executable_dir);
78 | wstring executable_dir_string(executable_dir);
79 |
80 | // When the launcher is not run from within the same directory as the
81 | // pythonXX.dll, we have to set the PYTHONHOME environment variable in
82 | // order to correctly use the right python environment
83 | wstring python_home(L"PYTHONHOME=" + executable_dir_string + runtime_dir);
84 | _wputenv(python_home.c_str());
85 |
86 | // by setting PYTHONPATH we overwrite any system settings, and we can also
87 | // set a separate directory for our code that we want isolated from the runtime
88 | wstring python_path(L"PYTHONPATH=" + executable_dir_string + applications_dir);
89 | _wputenv(python_path.c_str());
90 |
91 | // put the python runtime at the front of the path
92 | wstringstream ss;
93 | ss << "PATH=" << executable_dir << runtime_dir << ";" << getenv("PATH");
94 | wstring path_string(ss.str());
95 | _wputenv(path_string.c_str());
96 |
97 | // dynamically load the python dll
98 | wstring python_dll_path(executable_dir_string + runtime_dir + python_dll);
99 | HINSTANCE hGetProcIDDLL = LoadLibrary(python_dll_path.c_str());
100 | py_main_function Py_Main = (py_main_function)GetProcAddress(hGetProcIDDLL, "Py_Main");
101 | py_setpath_function Py_SetPath = (py_setpath_function)GetProcAddress(hGetProcIDDLL, "Py_SetPath");
102 |
103 |
104 | // here we inject the python application launching commands into the arguments array
105 | int newargc;
106 | newargc = argc + 1;
107 | wchar_t **newargv = new wchar_t*[newargc];
108 |
109 | newargv[0] = argv[0];
110 | newargv[1] = executable_name;
111 | // newargv[1] = argv[0]; // call executable as the python zipapp
112 | for (int i = 1; i < argc; i++) {
113 | newargv[i + 2] = argv[i];
114 | }
115 |
116 | //just a little debug check
117 | //for (int i=0;i
2 |
3 | XYScatter
4 |
5 |
6 |
7 | 0
8 | 0
9 | 1000
10 | 561
11 |
12 |
13 |
14 | DataFrames
15 |
16 |
17 |
18 | 3
19 |
20 |
21 | 3
22 |
23 |
24 | 3
25 |
26 |
27 | 3
28 |
29 | -
30 |
31 |
32 | Qt::Horizontal
33 |
34 |
35 |
36 |
-
37 |
38 |
39 | -
40 |
41 |
-
42 |
43 |
-
44 |
45 | line
46 |
47 |
48 | -
49 |
50 | points
51 |
52 |
53 |
54 |
55 | -
56 |
57 |
58 | Scatter Alpha
59 |
60 |
61 |
62 | -
63 |
64 |
65 | 255
66 |
67 |
68 | 255
69 |
70 |
71 | Qt::Horizontal
72 |
73 |
74 |
75 | -
76 |
77 |
78 | 255
79 |
80 |
81 |
82 |
83 |
84 | -
85 |
86 |
87 | Legend
88 |
89 |
90 | true
91 |
92 |
93 |
-
94 |
95 |
96 | DF Name
97 |
98 |
99 | true
100 |
101 |
102 |
103 | -
104 |
105 |
106 | Item Label
107 |
108 |
109 | false
110 |
111 |
112 |
113 | -
114 |
115 |
116 | Y-Column
117 |
118 |
119 | true
120 |
121 |
122 |
123 |
124 |
125 |
126 | -
127 |
128 |
-
129 |
130 |
131 | X-Label
132 |
133 |
134 |
135 | -
136 |
137 |
138 | -
139 |
140 |
141 | Y-Label
142 |
143 |
144 |
145 | -
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 | PlotWidget
160 | QGraphicsView
161 |
162 |
163 |
164 | DFXYListView
165 | QTreeView
166 |
167 |
168 |
169 |
170 |
171 |
172 |
--------------------------------------------------------------------------------
/radie/plugins/loaders/powderdiffraction_gsas.py:
--------------------------------------------------------------------------------
1 | """Define loader objects for gsas-type files"""
2 |
3 | import io
4 | import os
5 | import sys
6 | import traceback
7 |
8 | import numpy as np
9 | import pandas as pd
10 |
11 | from radie import exceptions
12 | from radie import loaders
13 | from radie.plugins.structures import powderdiffraction
14 |
15 |
16 | def parse_gsas_header(fid):
17 | """a couple of files have the same header structure, so let's read them in one place
18 |
19 | Parameters
20 | ----------
21 | fid : io.TextIOBase
22 |
23 | Returns
24 | -------
25 | header : dict
26 | wavelength : float
27 | data_start_line : int
28 | bank_line : str
29 |
30 | """
31 |
32 | fid.seek(0)
33 | headers = dict()
34 | bank_line = None
35 | first_data_line = None
36 |
37 | try:
38 | fid.readline()
39 | except UnicodeDecodeError:
40 | raise exceptions.IncorrectFileType
41 |
42 | for i, line in enumerate(fid):
43 | if line.startswith("#"):
44 | meta_pair = [value.strip() for value in line[1:].strip().split('=')]
45 | if len(meta_pair) == 2:
46 | headers[meta_pair[0]] = meta_pair[1]
47 | elif line.startswith("BANK"):
48 | bank_line = line
49 | first_data_line = i + 2
50 | break
51 | else:
52 | raise exceptions.IncorrectFileType("unrecognized header structure for .fxye files")
53 |
54 | try:
55 | wavelength = float(headers["Calibrated wavelength"])
56 | except KeyError:
57 | raise exceptions.IncorrectFileType("could not find the wavelength")
58 |
59 | return headers, wavelength, first_data_line, bank_line
60 |
61 |
62 | def load_fxye(fname):
63 | """.fxye is an Argonne National Labs made file format for powder diffraction that is GSAS compatible
64 |
65 | A more natural csv-type datastructure, as opposed to a GSAS raw file. Three Columns of data are provided, twotheta
66 | (in centidegrees), intensity and uncertainty
67 |
68 | Parameters
69 | ----------
70 | fname : str
71 | filename
72 |
73 | Returns
74 | -------
75 | powderdiffraction.PowderDiffraction
76 |
77 | Notes
78 | -----
79 | from the `11-BM file formats`_ page:
80 |
81 | GSAS supports many formats for powder diffraction data input. The .fxye format for GSAS has an intial header line
82 | followed by a variable number of comment lines (prefixed by a # character), then an addtional header line. The data
83 | are listed next in three columns: 1st column is 2θ position (in centidegrees, i.e. degrees × 100), 2nd is intensity,
84 | and 3rd is standard uncertainty for the intensity values (esd). This format may not be supported by all software
85 | that claims to read GSAS input files (see GSAS .raw format below)
86 |
87 | When GSAS format files (.fxye or .raw) are requested from the web site, the appropriate GSAS instrument parameter
88 | file (.prm) is generated and included.
89 |
90 | .. _`11-BM file formats`:
91 | http://11bm.xray.aps.anl.gov/filetypes.html
92 |
93 | """
94 |
95 | with open(fname, "r") as fid:
96 | vals = parse_gsas_header(fid)
97 | headers, wavelength, first_data_line, bank_line = vals
98 |
99 | name = headers.get("User sample name", None)
100 | if not name:
101 | name = os.path.basename(fname)
102 |
103 | try:
104 | df = pd.read_csv(fname, header=None, delimiter=" ", skiprows=first_data_line)
105 | df.dropna(axis=("index", "columns"), how="all", inplace=True)
106 | except Exception:
107 | raise exceptions.IncorrectFileType("\n".join(traceback.format_exception(sys.exc_info())))
108 |
109 | if not len(df.columns) == 3:
110 | raise exceptions.IncorrectFileType
111 |
112 | if not all(dtype in (float, int) for dtype in df.dtypes):
113 | raise exceptions.IncorrectFileType
114 |
115 | col_names = dict(zip(df.columns, ("twotheta", "intensity", "uncertainty")))
116 | df.rename(columns=col_names, inplace=True)
117 | df["twotheta"] /= 100
118 |
119 | return powderdiffraction.PowderDiffraction(df, name=name, wavelength=wavelength, source="undefined")
120 |
121 |
122 | def load_raw(fname):
123 | with open(fname, "r") as fid:
124 | vals = parse_gsas_header(fid)
125 | if not vals:
126 | raise exceptions.IncorrectFileType
127 |
128 | headers, wavelength, first_data_line, bank_line = vals
129 | bank = bank_line.split()
130 | try:
131 | num_points = int(bank[2])
132 | angle_start = float(bank[5]) / 100
133 | angle_step = float(bank[6]) / 100
134 | except Exception:
135 | raise exceptions.IncorrectFileType
136 |
137 | arr = pd.read_csv(fid, header=None, delim_whitespace=True).values.flatten()[:num_points * 2]
138 |
139 | angle_stop = angle_start + angle_step * (num_points - 1)
140 |
141 | twotheta = np.linspace(angle_start, angle_stop, num_points, dtype=float)
142 | intensity = arr[0::2]
143 | uncertainty = arr[1::2]
144 |
145 | name = headers.get("User sample name", None)
146 | if not name:
147 | name = os.path.basename(fname)
148 |
149 | return powderdiffraction.PowderDiffraction(
150 | data=np.vstack((twotheta, intensity, uncertainty)).T,
151 | columns=["twotheta", "intensity", "uncertainty"],
152 | name=name,
153 | wavelength=wavelength
154 | )
155 |
156 |
157 | fxye_loader = loaders.Loader(load_fxye, powderdiffraction.PowderDiffraction, (".fxye"), "GSAS .fxye")
158 | gsas_loader = loaders.Loader(load_raw, powderdiffraction.PowderDiffraction,
159 | (".raw", ".gsas", ".gsa", ".gs"), "GSAS raw")
160 |
161 | loaders.register_loaders(fxye_loader, gsas_loader)
162 |
--------------------------------------------------------------------------------
/radie/plugins/visualizations/icons/powderdiffraction.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
135 |
--------------------------------------------------------------------------------
/radie/plugins/loaders/powderdiffraction_rigaku.py:
--------------------------------------------------------------------------------
1 | """define loader objects that return PowderDiffraction Data Structures"""
2 | import io
3 | import math
4 |
5 | import numpy as np
6 | import pandas as pd
7 |
8 | from radie import exceptions
9 | from radie.loaders import Loader, register_loaders
10 | from radie.plugins.structures.powderdiffraction import PowderDiffraction, CuKa, CuKa1, CuKa2
11 |
12 |
13 | def ras_meta_value(line: bytes, return_type: type=str):
14 | value = line[line.find(b" "):].strip().replace(b'"', b"")
15 | if return_type is str:
16 | return value.decode("ascii")
17 | else:
18 | return return_type(value)
19 |
20 |
21 | def asc_meta_value(line: str, return_type: type=str):
22 | val = line.split("=")[1].strip()
23 | if return_type is str:
24 | return val
25 | else:
26 | return return_type(val)
27 |
28 |
29 | def load_asc(fname, name=None):
30 | """
31 | .ras file output from Rigaku XRD. Tested with files from MiniFlex system, which seem to be bytes-like
32 |
33 | Parameters
34 | ----------
35 | fname : str
36 | filename
37 | name : str
38 | measurement identifier
39 |
40 | Returns
41 | -------
42 | PowderDiffraction
43 |
44 | """
45 |
46 | with open(fname, "r") as fid:
47 | lines = fid.readlines()
48 |
49 | # --- begin file check --- #
50 | if not all((
51 | lines[0].startswith("*TYPE"),
52 | lines[8].startswith("*GONIO"),
53 | lines[41].startswith("*BEGIN"),
54 | lines[77].startswith("*COUNT"),
55 | )):
56 | exceptions.IncorrectFileType
57 |
58 | wavelength1 = asc_meta_value(lines[23], float)
59 | wavelength2 = asc_meta_value(lines[24], float)
60 | if wavelength1 == 1.54059 and wavelength2 == 1.54441:
61 | wavelength = CuKa
62 | else:
63 | wavelength = wavelength1
64 | sample_name = asc_meta_value(lines[2], str) # type: str
65 | start = asc_meta_value(lines[43], float) # type: float
66 | stop = asc_meta_value(lines[44], float) # type: float
67 | num_points = asc_meta_value(lines[77], int) # type: int
68 | source = asc_meta_value(lines[23], str) # type: str
69 |
70 | twotheta = np.linspace(start, stop, num_points, dtype=float)
71 | data_lines = int(math.ceil(num_points / 4))
72 | datastart = 78
73 | intensities = list()
74 | try:
75 | for i in range(datastart, datastart + data_lines):
76 | for point in map(float, lines[i].split(", ")):
77 | intensities.append(point)
78 | except Exception:
79 | raise exceptions.LoaderException
80 |
81 | return PowderDiffraction(
82 | data=np.array((twotheta, intensities), dtype=float).T, columns=("twotheta", "intensity"),
83 | name=sample_name, wavelength=wavelength, source=source
84 | )
85 |
86 |
87 | def load_ras(fname, name=None):
88 | """
89 | .ras file output from Rigaku XRD. Tested with files from MiniFlex system, which seem to be bytes-like
90 |
91 | Parameters
92 | ----------
93 | fname : str
94 | filename
95 | name : str
96 | measurement identifier
97 |
98 | Returns
99 | -------
100 | df_xrd : PowderDiffraction
101 | PowderDiffraction StructuredDataFrame based on XRD data
102 |
103 | """
104 |
105 | with open(fname, "rb") as fid:
106 | lines = fid.readlines()
107 |
108 | wavelength = None # type: float
109 | wavelength1 = None # type: float
110 | wavelength2 = None # type: float
111 | sample_name = "xray diffaction data"
112 | start = None # type: float
113 | stop = None # type: float
114 | step = None # type: int
115 | data_start = 0
116 |
117 | for i, line in enumerate(lines):
118 | if line.startswith(b"*HW_XG_WAVE_LENGTH_ALPHA1 "):
119 | wavelength1 = ras_meta_value(line, float)
120 | elif line.startswith(b"*HW_XG_WAVE_LENGTH_ALPHA2 "):
121 | wavelength2 = ras_meta_value(line, float)
122 | elif line.startswith(b"*FILE_SAMPLE "):
123 | sample_name = ras_meta_value(line, str)
124 | elif line.startswith(b"*MEAS_SCAN_STEP "):
125 | step = ras_meta_value(line, float)
126 | elif line.startswith(b"*MEAS_SCAN_START "):
127 | start = ras_meta_value(line, float)
128 | elif line.startswith(b"*MEAS_SCAN_STOP "):
129 | stop = ras_meta_value(line, float)
130 | elif line.startswith(b"*RAS_INT_START"):
131 | data_start = i + 1
132 | break
133 |
134 | if (
135 | wavelength1 is None or
136 | start is None or
137 | stop is None or
138 | step is None or
139 | data_start == 0
140 | ):
141 | raise exceptions.IncorrectFileType
142 |
143 | if wavelength1 == 1.540593 and wavelength2 == 1.544414:
144 | wavelength = CuKa
145 | else:
146 | wavelength = wavelength1
147 |
148 | n_points = int((stop - start) / step + 1)
149 | data_stop = data_start + n_points
150 | data_lines = [line.strip().decode("ascii") for line in lines[data_start:data_stop]]
151 |
152 | data = "\n".join(data_lines)
153 |
154 | if not name:
155 | name = sample_name
156 |
157 | data_buff = io.StringIO("twotheta intensity uncertainty\n" + data)
158 | df_xrd = PowderDiffraction(pd.read_csv(data_buff, sep=" "),
159 | name=name,
160 | wavelength=wavelength,
161 | source="CuKa",
162 | xunit="deg",
163 | yunit="counts")
164 |
165 | return df_xrd
166 |
167 |
168 | rigaku_ras_loader = Loader(load_ras, PowderDiffraction, [".ras"], "Rigaku XRD")
169 | rigaku_asc_loader = Loader(load_asc, PowderDiffraction, [".asc"], "Rigaku XRD")
170 |
171 | register_loaders(
172 | rigaku_ras_loader,
173 | rigaku_asc_loader,
174 | )
175 |
--------------------------------------------------------------------------------
/radie/qt/visualizations/series.py:
--------------------------------------------------------------------------------
1 | """plot a line of a single pandas series"""
2 |
3 | import os
4 |
5 | import numpy as np
6 | from PyQt5 import QtCore, QtWidgets
7 | import pyqtgraph as pg
8 |
9 | from ...structures.structureddataframe import StructuredDataFrame
10 | from .. import colors, cfg, dpi
11 | from .. import plotwidget
12 | from .. import plotlist
13 | from .base import Visualization
14 |
15 |
16 | class DFItem(plotlist.DFItem):
17 | """an item class with pyqtgraph xy curve handles of type PlotDataItem"""
18 | def __init__(self, ref, item_list, name=None):
19 | super(DFItem, self).__init__(ref, item_list, name)
20 | self.plotDataItem = pg.PlotDataItem()
21 | self.color = None
22 | self.values = None
23 |
24 | def setText(self, value):
25 | self.text = value
26 | self.plotDataItem.setData(name=value)
27 | self.plotDataItem.updateItems()
28 |
29 | def calculateValue(self):
30 | """calculate the histogram values, given the number of bins
31 |
32 | Parameters
33 | ----------
34 | bins : int
35 | density : bool
36 |
37 | """
38 | self.values = self.x_data()
39 |
40 |
41 | class SeriesVisualization(Visualization):
42 | """A generic class providing the visualization of a column of data as evenly-spaced points"""
43 |
44 | name = "Series Line"
45 | description = "A generic visualization of Series of values"
46 | _icon_image = os.path.join(cfg.icon_path, "seriesvisualization.svg")
47 |
48 | def __init__(self, parent=None):
49 | super(SeriesVisualization, self).__init__(parent)
50 |
51 | # --- setupUi --- #
52 | self.resize(1000, 600)
53 | self.setWindowTitle("Series")
54 | self.vlay_main = QtWidgets.QVBoxLayout(self)
55 | self.vlay_main.setContentsMargins(3, 3, 3, 3)
56 | self.splitter = QtWidgets.QSplitter(self)
57 | self.splitter.setOrientation(QtCore.Qt.Horizontal)
58 | self.dataListWidget = QtWidgets.QWidget(self.splitter)
59 | self.vlay_dataListWidget = QtWidgets.QVBoxLayout(self.dataListWidget)
60 | self.vlay_dataListWidget.setContentsMargins(0, 0, 0, 0)
61 | self.treeView_datasets = plotlist.DFSeriesListView(self.dataListWidget)
62 | self.vlay_dataListWidget.addWidget(self.treeView_datasets)
63 | self.hlay_parameters = QtWidgets.QHBoxLayout()
64 | self.vlay_dataListWidget.addLayout(self.hlay_parameters)
65 | self.plotWidget = plotwidget.PlotWidget(self.splitter)
66 | self.vlay_main.addWidget(self.splitter)
67 |
68 | self.splitter.setStretchFactor(0, 0)
69 | self.splitter.setStretchFactor(1, 1)
70 | self.splitter.setSizes(dpi.width_by_height(280, 720))
71 | # --- end setupUi --- #
72 |
73 | self.treeView_datasets.setItemClass(DFItem)
74 | self.treeView_datasets.supportedClasses = self.supportedClasses
75 |
76 | self.treeView_datasets.model().itemsAdded.connect(self.addCurves)
77 | self.treeView_datasets.model().itemAccessorChanged.connect(self.itemDataChanged)
78 | self.treeView_datasets.model().itemsDeleted.connect(self.processNewLayout)
79 | self.treeView_datasets.model().itemToggled.connect(self.itemToggled)
80 | self.treeView_datasets.model().itemTextUpdated.connect(self.plotWidget.plotItem.resetLegend)
81 | # self.checkBox_plotDensity.stateChanged.connect(self.recalculateValues)
82 | # self.spinBox_bins.editingFinished.connect(self.recalculateValues)
83 |
84 | self._colors = None
85 | self.resetColors()
86 |
87 | def nextColor(self):
88 | return next(self._colors)
89 |
90 | def resetColors(self):
91 | self._colors = colors.colors()
92 |
93 | def recalculateValues(self):
94 | for item in self.treeView_datasets.iterItems():
95 | item.calculateValue()
96 | item.plotDataItem.setData(x=np.arange(len(item.values)), y=item.values)
97 |
98 | def itemDataChanged(self, item: DFItem):
99 | if not item.isChecked():
100 | return
101 | item.calculateValue()
102 | item.plotDataItem.setData(x=np.arange(len(item.values)), y=item.values)
103 |
104 | def processNewLayout(self):
105 | self.plotWidget.plotItem.clear()
106 | self.plotWidget.plotItem.resetLegend()
107 | for item in self.treeView_datasets.iterItems():
108 | self.plotWidget.addItem(item.plotDataItem)
109 |
110 | def itemToggled(self, item):
111 | """
112 | process checking/unchecking of a StructuredDataFrame in the plot
113 |
114 | Parameters
115 | ----------
116 | item : DFItem
117 | """
118 | if item.checkState:
119 | item.plotDataItem.setData(x=np.arange(len(item.values)), y=item.values)
120 | else:
121 | item.plotDataItem.setData(name=None, stepMode=False)
122 | item.plotDataItem.clear()
123 | self.plotWidget.plotItem.resetLegend()
124 |
125 | def addCurves(self, items):
126 | """
127 | Parameters
128 | ----------
129 | items : list of DFItem
130 | """
131 | for item in items: # type: DFItem
132 | item.color = self.nextColor()
133 | item.calculateValue()
134 | item.plotDataItem.setData(x=np.arange(len(item.values)), y=item.values, pen=item.color)
135 | self.plotWidget.plotItem.addItem(item.plotDataItem)
136 |
137 |
138 | def test():
139 | import sys
140 | from ..masterdftree import DFReference
141 | from .. import cfg
142 | from .. import functions as fn
143 | app = fn.instantiate_app()
144 | fn.reset_excepthook()
145 | cfg.set_dpi_scaling()
146 |
147 | rng = np.random.RandomState(10) # deterministic random data
148 | a = np.hstack((rng.normal(size=1000), rng.normal(loc=5, scale=2, size=1000)))
149 | b = np.hstack((rng.normal(size=1000, scale=2), rng.normal(loc=5, scale=1, size=1000)))
150 | data = np.vstack((a, b)).T
151 | df = StructuredDataFrame(data, columns=("a", "b"), name="Deterministic Random Data")
152 | ref1 = DFReference(df, None)
153 |
154 | plot = SeriesVisualization()
155 | plot.treeView_datasets.addDataFrames(ref1, ref1)
156 | plot.show()
157 |
158 | sys.exit(app.exec_())
159 |
--------------------------------------------------------------------------------
/radie/qt/resources/icons/exiticon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
137 |
--------------------------------------------------------------------------------
/radie/qt/ui/xyscatter.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # Form implementation generated from reading ui file 'C:/Coding/Python/radie/radie/qt/ui\xyscatter.ui'
4 | #
5 | # Created by: PyQt5 UI code generator 5.9.2
6 | #
7 | # WARNING! All changes made in this file will be lost!
8 |
9 | from PyQt5 import QtCore, QtGui, QtWidgets
10 |
11 | class Ui_XYScatter(object):
12 | def setupUi(self, XYScatter):
13 | XYScatter.setObjectName("XYScatter")
14 | XYScatter.resize(1000, 561)
15 | self.verticalLayout_2 = QtWidgets.QVBoxLayout(XYScatter)
16 | self.verticalLayout_2.setContentsMargins(3, 3, 3, 3)
17 | self.verticalLayout_2.setObjectName("verticalLayout_2")
18 | self.splitter = QtWidgets.QSplitter(XYScatter)
19 | self.splitter.setOrientation(QtCore.Qt.Horizontal)
20 | self.splitter.setObjectName("splitter")
21 | self.layoutWidget = QtWidgets.QWidget(self.splitter)
22 | self.layoutWidget.setObjectName("layoutWidget")
23 | self.verticalLayout = QtWidgets.QVBoxLayout(self.layoutWidget)
24 | self.verticalLayout.setContentsMargins(0, 0, 0, 0)
25 | self.verticalLayout.setObjectName("verticalLayout")
26 | self.treeView_datasets = DFXYListView(self.layoutWidget)
27 | self.treeView_datasets.setObjectName("treeView_datasets")
28 | self.verticalLayout.addWidget(self.treeView_datasets)
29 | self.horizontalLayout = QtWidgets.QHBoxLayout()
30 | self.horizontalLayout.setObjectName("horizontalLayout")
31 | self.comboBox_lineStyle = QtWidgets.QComboBox(self.layoutWidget)
32 | self.comboBox_lineStyle.setObjectName("comboBox_lineStyle")
33 | self.comboBox_lineStyle.addItem("")
34 | self.comboBox_lineStyle.addItem("")
35 | self.horizontalLayout.addWidget(self.comboBox_lineStyle)
36 | self.label_alpha = QtWidgets.QLabel(self.layoutWidget)
37 | self.label_alpha.setObjectName("label_alpha")
38 | self.horizontalLayout.addWidget(self.label_alpha)
39 | self.slider_alpha = QtWidgets.QSlider(self.layoutWidget)
40 | self.slider_alpha.setMaximum(255)
41 | self.slider_alpha.setProperty("value", 255)
42 | self.slider_alpha.setOrientation(QtCore.Qt.Horizontal)
43 | self.slider_alpha.setObjectName("slider_alpha")
44 | self.horizontalLayout.addWidget(self.slider_alpha)
45 | self.label_alphaValue = QtWidgets.QLabel(self.layoutWidget)
46 | self.label_alphaValue.setObjectName("label_alphaValue")
47 | self.horizontalLayout.addWidget(self.label_alphaValue)
48 | self.verticalLayout.addLayout(self.horizontalLayout)
49 | self.groupBox_Legend = QtWidgets.QGroupBox(self.layoutWidget)
50 | self.groupBox_Legend.setCheckable(True)
51 | self.groupBox_Legend.setObjectName("groupBox_Legend")
52 | self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.groupBox_Legend)
53 | self.horizontalLayout_2.setObjectName("horizontalLayout_2")
54 | self.checkBox_legend_dfname = QtWidgets.QCheckBox(self.groupBox_Legend)
55 | self.checkBox_legend_dfname.setChecked(True)
56 | self.checkBox_legend_dfname.setObjectName("checkBox_legend_dfname")
57 | self.horizontalLayout_2.addWidget(self.checkBox_legend_dfname)
58 | self.checkBox_legend_label = QtWidgets.QCheckBox(self.groupBox_Legend)
59 | self.checkBox_legend_label.setChecked(False)
60 | self.checkBox_legend_label.setObjectName("checkBox_legend_label")
61 | self.horizontalLayout_2.addWidget(self.checkBox_legend_label)
62 | self.checkBox_legend_ycolumn = QtWidgets.QCheckBox(self.groupBox_Legend)
63 | self.checkBox_legend_ycolumn.setChecked(True)
64 | self.checkBox_legend_ycolumn.setObjectName("checkBox_legend_ycolumn")
65 | self.horizontalLayout_2.addWidget(self.checkBox_legend_ycolumn)
66 | self.verticalLayout.addWidget(self.groupBox_Legend)
67 | self.formLayout_plotOptions = QtWidgets.QFormLayout()
68 | self.formLayout_plotOptions.setObjectName("formLayout_plotOptions")
69 | self.label_xlabel = QtWidgets.QLabel(self.layoutWidget)
70 | self.label_xlabel.setObjectName("label_xlabel")
71 | self.formLayout_plotOptions.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label_xlabel)
72 | self.lineEdit_xlabel = QtWidgets.QLineEdit(self.layoutWidget)
73 | self.lineEdit_xlabel.setObjectName("lineEdit_xlabel")
74 | self.formLayout_plotOptions.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.lineEdit_xlabel)
75 | self.label_ylabel = QtWidgets.QLabel(self.layoutWidget)
76 | self.label_ylabel.setObjectName("label_ylabel")
77 | self.formLayout_plotOptions.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label_ylabel)
78 | self.lineEdit_ylabel = QtWidgets.QLineEdit(self.layoutWidget)
79 | self.lineEdit_ylabel.setObjectName("lineEdit_ylabel")
80 | self.formLayout_plotOptions.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.lineEdit_ylabel)
81 | self.verticalLayout.addLayout(self.formLayout_plotOptions)
82 | self.plotWidget = PlotWidget(self.splitter)
83 | self.plotWidget.setObjectName("plotWidget")
84 | self.verticalLayout_2.addWidget(self.splitter)
85 |
86 | self.retranslateUi(XYScatter)
87 | QtCore.QMetaObject.connectSlotsByName(XYScatter)
88 |
89 | def retranslateUi(self, XYScatter):
90 | _translate = QtCore.QCoreApplication.translate
91 | XYScatter.setWindowTitle(_translate("XYScatter", "DataFrames"))
92 | self.comboBox_lineStyle.setItemText(0, _translate("XYScatter", "line"))
93 | self.comboBox_lineStyle.setItemText(1, _translate("XYScatter", "points"))
94 | self.label_alpha.setText(_translate("XYScatter", "Scatter Alpha"))
95 | self.label_alphaValue.setText(_translate("XYScatter", "255"))
96 | self.groupBox_Legend.setTitle(_translate("XYScatter", "Legend"))
97 | self.checkBox_legend_dfname.setText(_translate("XYScatter", "DF Name"))
98 | self.checkBox_legend_label.setText(_translate("XYScatter", "Item Label"))
99 | self.checkBox_legend_ycolumn.setText(_translate("XYScatter", "Y-Column"))
100 | self.label_xlabel.setText(_translate("XYScatter", "X-Label"))
101 | self.label_ylabel.setText(_translate("XYScatter", "Y-Label"))
102 |
103 | from radie.qt.plotlist import DFXYListView
104 | from radie.qt.plotwidget import PlotWidget
105 |
--------------------------------------------------------------------------------
/radie/plugins/visualizations/icons/particlesizedistribution.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
132 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Rapid Data Import Environment (radie)
2 |
3 | Copyright (©) 3M Company, 2018,
4 |
5 | License: GPL version 2
6 |
7 | Radie is a python package for experimental data primarily using pandas
8 | for data structures
9 |
10 | The point of radie is to remove the pain of loading and visualizing
11 | experimental data. It is not meant for complex plotting or pretty
12 | pictures, but rather a focus on rapidly converging experimental data
13 | stored in files into a single place for rapid visualization.
14 |
15 |
16 | ## GUI Demo
17 |
18 | 
19 |
20 | The screen capture above shows the loading of three different experimental files (Rigaku XRD file, Particle Size Distribution and an IDEA VSM file) simultaneously into the program with drag and drop using the OS file explorer. From there we initialize new visualizations and then drag the datasets into the visualizations. Each visualization is tailored to a certain goal, be it the particularities of vieweing XRD files, PSD files, or the generic needs of plotting columnar data in an XY-scatter plot. The whole process only takes a few seconds and allows the user to rapidly view and display any kind of experimental data, provided an appropriate loader plugin has been written.
21 |
22 |
23 | ## Python Usage
24 |
25 | ```python
26 | import radie as rd
27 | df = rd.DataStructure(data=[1, 2, 3], name="data frame")
28 | print(df.metadata["name"])
29 | print(df.metadata["date"])
30 |
31 | csv_df = rd.load_file("my_random_data.csv") # unspecified csv data
32 |
33 | pow_df = rd.load_file("powder_diffraction_measurement.ras") # powder diffraction data
34 | print(pow_df.metadata["name"])
35 |
36 | vsm_df = rd.load_file("magnetization_v_field.txt") # VSM measurement
37 | print(vsm_df.metadata["name"])
38 |
39 | vsm_df.savetxt('my_vsm_file.df', overwrite=True) # save as a csv with metadata
40 | # in a commented json block
41 | vsm_reread = rd.load_file('my_vsm_file.df') # will read in savetxt output with
42 | # proper class and metadata info
43 | ```
44 |
45 |
46 | ## Launch PyQt Gui
47 |
48 | ```shell
49 | python -m radie.qt.viewer
50 | ```
51 |
52 |
53 | ## Requirements
54 | - numpy
55 | - pandas
56 |
57 | additionally for the gui application:
58 | - pyqt >= 5.6
59 | - pyqtgraph >= 0.10
60 |
61 | optional
62 | - pywin32 (Windows only)
63 |
64 |
65 | ## Installation
66 |
67 | pip install radie
68 |
69 | to use the PyQt Gui you must also have PyQt5 and pyqtgraph installed:
70 |
71 | pip install radie PyQt5 pyqtgraph
72 |
73 | for the latest updates, clone this repo and add radie/ to your PythonPath
74 |
75 | On Windows, running the `install_windows_shortcut.py` script will install
76 | shorcuts for radie using the same python executables that are used to
77 | run the install script. This requires pywin32.
78 |
79 |
80 | ## Highlights
81 |
82 | - Core Features:
83 | - StructuredDataFrame - base class, just a pandas DataFrame with some
84 | restrictions, metadata and templates for more specific
85 | sub-classes
86 | - Common text file format - This is just a csv file where the metadata
87 | attached to the .metadata property is converted to a json object
88 | and stored in a commented block at the top.
89 | - CSV importer that under some structure assumptions attempts to:
90 | - automatically determine delimiter
91 | - automatically find the csv data-block
92 | - automatically determine headers
93 | - ignore preceding metadata
94 | - Qt-based Gui Viewer with Drag and Drop handling of files and
95 | data-set comparison
96 | - Extensible because (nearly) everything is a plugin
97 | - StructuredDataFrame subclasses specify structured data
98 | (add-your-own!)
99 | - Custom Visualizations of Structured Data, based on whatever fits
100 | into QMdiSubwindow (I use PyQtGraph)
101 | - File-loaders written for each supported file-type, register into
102 | the system so that radie.load\_file can automatically detect
103 | and load registered file-types
104 | - \*\*GUI save files keep all your rapid analysis in one place
105 | each plugin specifies its own save data in json format and all
106 | DataStructures have a common save format
107 |
108 | \*\* Planned, but not yet implemented
109 |
110 |
111 | ## Currently Supported File-types
112 |
113 | - Powder Diffraction:
114 | - Rigaku (.asc, .ras)
115 | - Bruker (.raw (v2))
116 | - GSAS (.raw, .gsas, .fxye)
117 | - Vibrating Sample Magnetometer:
118 | - Lakeshore (.dat, .txt (Field v Moment))
119 | - Particle Size Distribution:
120 | - Horiba LA-960 (.csv)
121 | - Thermogravimetric Analysis
122 | - TA Instruments Q500 (.001, .002, .003)
123 | - Differential Scanning Calorimetry (DSC)
124 | - TA Instruments Q2000 (.001, .002, .003)
125 |
126 |
127 | ## Vision
128 |
129 | Radie is not a replacement for Origin, Igor, QtiPlot or similar gui
130 | scientific plotting/analysis packages. Instead Radie has the
131 | following goals:
132 |
133 | 1. import data files, with drag and drop and automatic file-type
134 | detection
135 | - powder diffraction data
136 | - spectrum data
137 | - any data that maps to a Pandas StructuredDataFrame
138 | 2. rapid comparison of datasets into automatically generated
139 | visualizations with drag and drop
140 | - I don't want to plot things, I want to visualize them, the
141 | software should plot for me
142 | 3. reasonable interoperability with python for more complex operations
143 | - matplotlib, bokeh, whatever, I want to do the quick
144 | visualizations and comparisons in a gui, and I will do all of
145 | the complicated analysis and crazy figure plotting in something
146 | more flexible like a Jupyter Notebook
147 | - strong clipboard integration
148 | - common file-saving scheme for all types of data
149 | 4. saving my collected datasets and visualizations
150 | - Origin/QtiPlot software is not my cup of tea for actually
151 | analyzing data but its nice to save your collection of data and
152 | plots in a single spot
153 | - Jupyter Notebooks / Matplotlib are much nicer when it comes to
154 | analyzing data but all of that flexibility can screw things up
155 | in the future, it would be nice to have it all in one
156 | re-producible place that I can come back to at any time with a
157 | double click of the mouse
158 | 5. reasonable exporting to excel
159 | - Excel is the most wide-spread format for sharing/visualizing 1-D
160 | datasets and these datasets are literally everywhere. A quick
161 | button to make an excel file to share with the non-programmers
162 | out there is critical (currently Windows only)
163 |
--------------------------------------------------------------------------------
/radie/plugins/visualizations/xyscatter_docdemo.py.txt:
--------------------------------------------------------------------------------
1 | """solely here as a template to demonstrate in the documentation"""
2 | import os
3 |
4 | from PyQt5 import QtCore, QtWidgets
5 | import pyqtgraph as pg
6 |
7 | from radie.qt.visualizations import base, register_visualizations
8 | from radie.qt import dpi, cfg, colors, plotlist
9 | from radie.qt.plotlist import DFXYListView
10 | from radie.qt.plotwidget import PlotWidget
11 | from radie.qt import functions as fn
12 |
13 |
14 | class DFItem(plotlist.DFItem):
15 | """an item class with pyqtgraph xy curve handles of type PlotDataItem"""
16 | def __init__(self, ref, item_list, name=None):
17 | super(DFItem, self).__init__(ref, item_list, name)
18 | self.plotDataItem = pg.PlotDataItem()
19 | self.color = None
20 | self.plotDataItem.setData(
21 | x=self.x_data(),
22 | y=self.y_data(),
23 | name=self.text
24 | )
25 |
26 | def setText(self, value):
27 | self.text = value
28 | self.plotDataItem.setData(name=value)
29 | self.plotDataItem.updateItems()
30 |
31 |
32 | class XYScatterDemo(base.Visualization):
33 | """A generic XY scatter visualization"""
34 | name = "XY Scatter Demo"
35 | description = "A generic visualization of XY curves from StructuredDataFrame Series"
36 | _icon_image = os.path.join(cfg.icon_path, "xyscatter.svg")
37 |
38 | def __init__(self, parent=None):
39 | super().__init__(parent)
40 | self.setupUi()
41 |
42 | self.treeView_datasets.setItemClass(DFItem) # required before the list will accept any drops
43 | self.treeView_datasets.model().itemsAdded.connect(self.addCurves)
44 | self.treeView_datasets.model().itemAccessorChanged.connect(self.itemDataChanged)
45 | self.treeView_datasets.model().itemsDeleted.connect(self.processNewLayout)
46 | self.treeView_datasets.model().rowsMoved.connect(self.processNewLayout)
47 | self.treeView_datasets.model().itemToggled.connect(self.itemToggled)
48 | self.treeView_datasets.model().itemTextUpdated.connect(self.plotWidget.plotItem.resetLegend)
49 |
50 | self.lineEdit_xlabel.textChanged.connect(self.setXLabel)
51 | self.lineEdit_ylabel.textChanged.connect(self.setYLabel)
52 |
53 | self._colors = None
54 | self.resetColors()
55 |
56 | def setupUi(self):
57 | self.setWindowTitle("XY Scatter Plot")
58 | self.resize(800, 450)
59 |
60 | self.verticalLayout_main = QtWidgets.QVBoxLayout(self)
61 | self.verticalLayout_main.setContentsMargins(3, 3, 3, 3)
62 | self.splitter = QtWidgets.QSplitter(self)
63 | self.splitter.setOrientation(QtCore.Qt.Horizontal)
64 | self.layoutWidget = QtWidgets.QWidget(self.splitter) # left half of the splitter
65 | self.verticalLayout = QtWidgets.QVBoxLayout(self.layoutWidget)
66 | self.verticalLayout.setContentsMargins(0, 0, 0, 0)
67 | self.treeView_datasets = DFXYListView(self.layoutWidget)
68 | self.verticalLayout.addWidget(self.treeView_datasets)
69 | self.formLayout_plotOptions = QtWidgets.QFormLayout()
70 | self.label_xlabel = QtWidgets.QLabel("X-Label", self.layoutWidget)
71 | self.formLayout_plotOptions.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label_xlabel)
72 | self.lineEdit_xlabel = QtWidgets.QLineEdit(self.layoutWidget)
73 | self.formLayout_plotOptions.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.lineEdit_xlabel)
74 | self.lineEdit_ylabel = QtWidgets.QLineEdit(self.layoutWidget)
75 | self.formLayout_plotOptions.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.lineEdit_ylabel)
76 | self.label_ylabel = QtWidgets.QLabel("Y-Label", self.layoutWidget)
77 | self.formLayout_plotOptions.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label_ylabel)
78 | self.verticalLayout.addLayout(self.formLayout_plotOptions)
79 | self.plotWidget = PlotWidget(self.splitter)
80 | self.verticalLayout_main.addWidget(self.splitter)
81 |
82 | # not part of the QtDesigner output, but necessary for good appearance
83 | self.splitter.setStretchFactor(0, 0)
84 | self.splitter.setStretchFactor(1, 1)
85 | self.splitter.setSizes(dpi.width_by_height(280, 720))
86 |
87 | def setAxisLabel(self, axis: str, text: str):
88 | if not text:
89 | self.plotWidget.plotItem.showLabel(axis, False)
90 | return
91 | self.plotWidget.setLabel(axis=axis, text=text)
92 |
93 | def setXLabel(self, text: str):
94 | self.setAxisLabel("bottom", text)
95 |
96 | def setYLabel(self, text: str):
97 | self.setAxisLabel("left", text)
98 |
99 | def nextColor(self):
100 | return next(self._colors)
101 |
102 | def resetColors(self):
103 | self._colors = colors.colors()
104 |
105 | def itemDataChanged(self, item: DFItem):
106 | if not item.isChecked():
107 | return
108 |
109 | item.plotDataItem.setData(
110 | x=item.x_data(),
111 | y=item.y_data()
112 | )
113 |
114 | def itemToggled(self, item):
115 | if item.checkState:
116 | item.plotDataItem.setData(
117 | x=item.x_data(),
118 | y=item.y_data(),
119 | name=item.text
120 | )
121 | else:
122 | item.plotDataItem.setData(name=None)
123 | item.plotDataItem.clear()
124 | self.plotWidget.plotItem.resetLegend()
125 |
126 | def processNewLayout(self):
127 | self.plotWidget.plotItem.clear()
128 | self.plotWidget.plotItem.resetLegend()
129 | for item in self.treeView_datasets.iterItems():
130 | self.plotWidget.addItem(item.plotDataItem)
131 |
132 | def addCurves(self, items):
133 | """main function for adding new curves to the plot
134 |
135 | Parameters
136 | ----------
137 | items : list of DFItem
138 | """
139 | for item in items:
140 | item.color = self.nextColor()
141 | item.plotDataItem.setData(pen=item.color)
142 | item.plotDataItem.updateItems() # required if setData is only used for styles
143 | self.plotWidget.plotItem.addItem(item.plotDataItem)
144 |
145 |
146 | register_visualizations(XYScatterDemo)
147 |
148 | if __name__ == "__main__":
149 | import sys
150 | from radie.plugins import examples
151 | from radie.qt.masterdftree import DFReference
152 | app = fn.instantiate_app()
153 | fn.reset_excepthook() # PyQt5 exists silently, sucks for debugging
154 |
155 | df1 = examples.example_powderdiffraction(); df1.metadata["name"] = "xrd1"
156 | df2 = examples.example_powderdiffraction(); df2.metadata["name"] = "xrd2"
157 | df2["intensity"] += 10; df2["intensity"] *= 1.1
158 | ref1 = DFReference(df1, None)
159 | ref2 = DFReference(df2, None)
160 |
161 | plot = XYScatterDemo()
162 | plot.treeView_datasets.addDataFrames(ref1, ref2)
163 | plot.show()
164 |
165 | sys.exit(app.exec_())
166 |
--------------------------------------------------------------------------------
/radie/qt/visualizations/histogram.py:
--------------------------------------------------------------------------------
1 | """plot a histogram of a single pandas series"""
2 |
3 | import os
4 |
5 | import numpy as np
6 | from PyQt5 import QtCore, QtWidgets
7 | import pyqtgraph as pg
8 |
9 | from ...structures.structureddataframe import StructuredDataFrame
10 | from .. import colors, cfg, dpi
11 | from .. import plotwidget
12 | from .. import plotlist
13 | from .base import Visualization
14 |
15 |
16 | class DFItem(plotlist.DFItem):
17 | """an item class with pyqtgraph xy curve handles of type PlotDataItem"""
18 | def __init__(self, ref, item_list, name=None):
19 | super(DFItem, self).__init__(ref, item_list, name)
20 | self.plotDataItem = pg.PlotDataItem()
21 | self.color = None
22 | self.density = None
23 | self.bins = None
24 |
25 | def setText(self, value):
26 | self.text = value
27 | self.plotDataItem.setData(name=value)
28 | self.plotDataItem.updateItems()
29 |
30 | def calculateHistogram(self, bins, density):
31 | """calculate the histogram values, given the number of bins
32 |
33 | Parameters
34 | ----------
35 | bins : int
36 | density : bool
37 |
38 | """
39 | self.density, self.bins = np.histogram(self.x_data(), bins, density=density)
40 |
41 |
42 | class Histogram(Visualization):
43 | """A generic class providing the visualization of a column of data as a binned histogram
44 |
45 | This visualization is intended for use where we have populations of data and we want to compare the histgrams
46 | of different variables in relation to eachother. The list widget provides a single combobox to select which
47 | column of data will be binned and visualized
48 |
49 | """
50 |
51 | name = "Histogram"
52 | description = "A generic visualization of histograms for a Series of values"
53 | _icon_image = os.path.join(cfg.icon_path, "histogramicon.svg")
54 |
55 | def __init__(self, parent=None):
56 | super(Histogram, self).__init__(parent)
57 |
58 | # --- setupUi --- #
59 | self.resize(1000, 600)
60 | self.setWindowTitle("Histogram")
61 | self.vlay_main = QtWidgets.QVBoxLayout(self)
62 | self.vlay_main.setContentsMargins(3, 3, 3, 3)
63 | self.splitter = QtWidgets.QSplitter(self)
64 | self.splitter.setOrientation(QtCore.Qt.Horizontal)
65 | self.dataListWidget = QtWidgets.QWidget(self.splitter)
66 | self.vlay_dataListWidget = QtWidgets.QVBoxLayout(self.dataListWidget)
67 | self.vlay_dataListWidget.setContentsMargins(0, 0, 0, 0)
68 | self.treeView_datasets = plotlist.DFSeriesListView(self.dataListWidget)
69 | self.vlay_dataListWidget.addWidget(self.treeView_datasets)
70 | self.hlay_parameters = QtWidgets.QHBoxLayout()
71 | self.checkBox_plotDensity = QtWidgets.QCheckBox("Probability Density")
72 | self.checkBox_plotDensity.setCheckState(QtCore.Qt.Checked)
73 | self.hlay_parameters.addWidget(self.checkBox_plotDensity)
74 | self.spinBox_bins = QtWidgets.QSpinBox()
75 | self.spinBox_bins.setValue(10)
76 | self.spinBox_bins.setMaximum(50)
77 | self.spinBox_bins.setMinimum(3)
78 | self.hlay_parameters.addWidget(self.spinBox_bins)
79 | self.vlay_dataListWidget.addLayout(self.hlay_parameters)
80 | self.plotWidget = plotwidget.PlotWidget(self.splitter)
81 | self.vlay_main.addWidget(self.splitter)
82 |
83 | self.splitter.setStretchFactor(0, 0)
84 | self.splitter.setStretchFactor(1, 1)
85 | self.splitter.setSizes(dpi.width_by_height(280, 720))
86 | # --- end setupUi --- #
87 |
88 | self.treeView_datasets.setItemClass(DFItem)
89 | self.treeView_datasets.supportedClasses = self.supportedClasses
90 |
91 | self.treeView_datasets.model().itemsAdded.connect(self.addCurves)
92 | self.treeView_datasets.model().itemAccessorChanged.connect(self.itemDataChanged)
93 | self.treeView_datasets.model().itemsDeleted.connect(self.processNewLayout)
94 | self.treeView_datasets.model().itemToggled.connect(self.itemToggled)
95 | self.treeView_datasets.model().itemTextUpdated.connect(self.plotWidget.plotItem.resetLegend)
96 | self.checkBox_plotDensity.stateChanged.connect(self.recalculateHistograms)
97 | self.spinBox_bins.editingFinished.connect(self.recalculateHistograms)
98 |
99 | self._colors = None
100 | self.resetColors()
101 |
102 | def nextColor(self):
103 | return next(self._colors)
104 |
105 | def resetColors(self):
106 | self._colors = colors.colors()
107 |
108 | def recalculateHistograms(self):
109 | for item in self.treeView_datasets.iterItems():
110 | item.calculateHistogram(self.spinBox_bins.value(), self.checkBox_plotDensity.isChecked())
111 | item.plotDataItem.setData(x=item.bins, y=item.density, stepMode=True)
112 |
113 | def itemDataChanged(self, item: DFItem):
114 | if not item.isChecked():
115 | return
116 | item.calculateHistogram(self.spinBox_bins.value(), self.checkBox_plotDensity.isChecked())
117 | item.plotDataItem.setData(x=item.bins, y=item.density, stepMode=True)
118 |
119 | def processNewLayout(self):
120 | self.plotWidget.plotItem.clear()
121 | self.plotWidget.plotItem.resetLegend()
122 | for item in self.treeView_datasets.iterItems():
123 | self.plotWidget.addItem(item.plotDataItem)
124 |
125 | def itemToggled(self, item):
126 | """
127 | process checking/unchecking of a StructuredDataFrame in the plot
128 |
129 | Parameters
130 | ----------
131 | item : DFItem
132 | """
133 | if item.checkState:
134 | item.plotDataItem.setData(item.bins, item.density, stepMode=True, pen=item.color, name=item.text)
135 | else:
136 | item.plotDataItem.setData(name=None, stepMode=False)
137 | item.plotDataItem.clear()
138 | self.plotWidget.plotItem.resetLegend()
139 |
140 | def addCurves(self, items):
141 | """
142 | Parameters
143 | ----------
144 | items : list of DFItem
145 | """
146 | for item in items: # type: DFItem
147 | item.color = self.nextColor()
148 | item.calculateHistogram(self.spinBox_bins.value(), density=self.checkBox_plotDensity.isChecked())
149 | item.plotDataItem.setData(item.bins, item.density, stepMode=True, pen=item.color, name=item.text)
150 | self.plotWidget.plotItem.addItem(item.plotDataItem)
151 |
152 |
153 | def test():
154 | import sys
155 | from ..masterdftree import DFReference
156 | from .. import cfg
157 | from .. import functions as fn
158 | app = fn.instantiate_app()
159 | fn.reset_excepthook()
160 | cfg.set_dpi_scaling()
161 |
162 | rng = np.random.RandomState(10) # deterministic random data
163 | a = np.hstack((rng.normal(size=1000), rng.normal(loc=5, scale=2, size=1000)))
164 | b = np.hstack((rng.normal(size=1000, scale=2), rng.normal(loc=5, scale=1, size=1000)))
165 | data = np.vstack((a, b)).T
166 | df = StructuredDataFrame(data, columns=("a", "b"), name="Deterministic Random Data")
167 | ref1 = DFReference(df, None)
168 |
169 | plot = Histogram()
170 | plot.treeView_datasets.addDataFrames(ref1, ref1)
171 | plot.show()
172 |
173 | sys.exit(app.exec_())
174 |
--------------------------------------------------------------------------------
/radie/qt/resources/icons/histogramicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
158 |
--------------------------------------------------------------------------------
/radie/plugins/loaders/psd_LA960.py:
--------------------------------------------------------------------------------
1 | """Loader for csv output of Horiba LA-960 particle size analyzer"""
2 | import numpy as np
3 | import re
4 |
5 | from radie import exceptions
6 | from radie.loaders import Loader, register_loaders
7 | from radie.plugins.structures.psd import PSD
8 | from radie.util import iso_date_string
9 |
10 |
11 | class Parameter(dict):
12 | def __init__(self, *args, **kwargs):
13 | dict.__init__(self, *args, **kwargs)
14 | self.reset_defaults(**kwargs)
15 |
16 | def reset_defaults(self, **kwargs):
17 | d = {'value': None,
18 | 'type': str,
19 | 'key': None,
20 | 'unit': None,
21 | 'delimiter': None,
22 | 'raw_line': None,
23 | 'raw_value': None,
24 | 'format': '',
25 | 'padding': True,
26 | 'merge_consecutive_delimiters': True,
27 | 'value_has_whitespace': True}
28 | self.update(d)
29 | self.update(**kwargs)
30 |
31 | def __str__(self):
32 | if self.get('value') is not None:
33 | if self.get('unit') is not None:
34 | return '{key}:{value:{format}} {unit}'.format(**self)
35 | else:
36 | return '{key}:{value:{format}}'.format(**self)
37 |
38 | def read_line(self, line):
39 | self['raw_value'] = line
40 | self['value'] = self['type'](line)
41 | return self['value']
42 |
43 |
44 | class KeyValParameter(Parameter):
45 | def __init__(self, key=None, **kwargs):
46 | Parameter.__init__(self, **kwargs)
47 | self['key'] = key
48 | if self.get('raw_line') is not None:
49 | self.read_line(self['raw_line'])
50 |
51 | def read_line(self, line):
52 | if self.check_line(line):
53 | try:
54 | items = line.split(self['delimiter'])
55 | self['raw_value'] = self['delimiter'].join(items[1:])
56 | return self.update_value()
57 | except (TypeError, ValueError, AttributeError):
58 | self['raw_value'] = None
59 | self['value'] = None
60 |
61 | def update_value(self):
62 | valstr = self['raw_value'].strip()
63 | if self.get('unit', False):
64 | if self['unit'] in valstr:
65 | iunit = valstr.index(self['unit'])
66 | valstr = valstr[:iunit]
67 | try:
68 | self['value'] = self['type'](valstr)
69 | except (ValueError, AttributeError):
70 | self['value'] = None
71 | return self['value']
72 |
73 | def check_line(self, line):
74 | if self.get('key') is not None:
75 | return line.strip().startswith(self['key'])
76 | else:
77 | return False
78 |
79 |
80 | class RegexParameter(Parameter):
81 | def __init__(self, regex=None, **kwargs):
82 | Parameter.__init__(self, **kwargs)
83 | if regex is None:
84 | self.gen_regex()
85 | else:
86 | self['regex'] = regex
87 | if self.get('raw_line') is not None:
88 | self.read_line(self['raw_line'])
89 |
90 | def gen_regex(self):
91 | # pdb.set_trace()
92 | key = re.escape(self.get('key')) if self.get('key') else ''
93 | if self.get('delimiter') is None:
94 | delimiter = r'\s+'
95 | else:
96 | delimiter = '({0})'.format(re.escape(self['delimiter']))
97 | if self.get('merge_consecutive_delimiters', False):
98 | delimiter += '+'
99 | unit = re.escape(self['unit']) if self.get('unit') is not None else ''
100 | padding = r'\s*?' if self.get('padding') is not None else ''
101 | if self.get('value_has_whitespace', False):
102 | raw_value = r'(?P.+)'
103 | else:
104 | raw_value = r'(?P\w+)'
105 |
106 | regex = r'{key}{padding}{delimiter}{padding}{raw_value}.*{unit}'.format(key=key, delimiter=delimiter,
107 | unit=unit, padding=padding,
108 | raw_value=raw_value)
109 | self['regex'] = regex
110 | return regex
111 |
112 | def read_line(self, line):
113 | self['raw_line'] = line
114 | self['m'] = re.compile(self['regex'])
115 | mo = self['m'].search(line.strip())
116 | if mo is not None:
117 | self.update(mo.groupdict())
118 | self.update_value()
119 | else:
120 | self['value'] = None
121 | return self['value']
122 |
123 | def update_value(self):
124 | try:
125 | self['value'] = self['type'](self['raw_value'].strip())
126 | except ValueError:
127 | self['value'] = None
128 | return self['value']
129 |
130 |
131 | def load_csv(fname):
132 | """
133 |
134 | Parameters
135 | ----------
136 | fname : file path
137 |
138 | Returns
139 | -------
140 | df_psd : PSD
141 | PSD StructuredDataFrame
142 | """
143 |
144 | starting_lines = [
145 | "Median size",
146 | "Mean size",
147 | "Variance",
148 | "St. Dev.",
149 | "Mode size",
150 | "Span",
151 | "Geo. mean size",
152 | "Geo. variance",
153 | "Diameter on cumulative",
154 | "D10",
155 | "D90",
156 | "D(v,0.1)",
157 | "D(v,0.5)",
158 | "D(v,0.9)",]
159 |
160 | # Open while checking for expected format,
161 | # Want to fail as fast as possible
162 | lines = []
163 | with open(fname, "r") as reader:
164 | for i, line in enumerate(reader):
165 | if i < len(starting_lines):
166 | if not line.startswith(starting_lines[i]):
167 | raise exceptions.IncorrectFileType()
168 | lines.append(line)
169 |
170 | # The Horiba can output with a user defined delimiter character
171 | # commas and tabs are currently supported
172 | if ',' in lines[0]:
173 | delimiter = ','
174 | elif '\t' in lines[0]:
175 | delimiter = '\t'
176 | else:
177 | raise exceptions.IncorrectFileType("only commas and tabs are supported as the delimiter")
178 |
179 | metadata = {}
180 | parameters = {}
181 | parameters['sample'] = RegexParameter(key='Sample Name', delimiter=delimiter)
182 | parameters['lot'] = RegexParameter(key='Lot Number', delimiter=delimiter)
183 |
184 | lines = [line.strip() for line in lines]
185 |
186 | while not lines[0].startswith('Diameter (\xb5m)'):
187 | line = lines.pop(0)
188 | for k, p in parameters.items():
189 | if line.startswith(p['key']):
190 | try:
191 | value = p.read_line(line)
192 | if value is not None:
193 | break
194 | except:
195 | pass
196 |
197 | if parameters['sample']['value'] is None:
198 | parameters['sample']['value'] = 'PSD_{}'.format(iso_date_string())
199 |
200 | for k, p in parameters.items():
201 | metadata[k] = p['value']
202 | metadata['name'] = parameters['sample']['value']
203 |
204 | lines.pop(0)
205 | # The last two lines are blank/null values
206 | data = np.array([[float(x) for x in line.split(delimiter)] for line in lines[:-2]])
207 |
208 | df_psd = PSD(data=data[:,:2],
209 | columns=['diameter','frequency'],
210 | **metadata)
211 | return df_psd
212 |
213 | LA960_csv_loader = Loader(load_csv, PSD, [".csv"], "Horiba LA-960")
214 |
215 | register_loaders(
216 | LA960_csv_loader,
217 | )
218 |
219 |
220 |
--------------------------------------------------------------------------------
/radie/qt/classes.py:
--------------------------------------------------------------------------------
1 | """a module with some custom QtWidget Classes"""
2 |
3 | from PyQt5 import QtWidgets, QtCore, QtGui
4 |
5 |
6 | class MdiArea(QtWidgets.QMdiArea):
7 |
8 | urlsDropped = QtCore.pyqtSignal(object)
9 |
10 | def __init__(self, parent=None):
11 | super(MdiArea, self).__init__(parent)
12 | self.setAcceptDrops(True)
13 |
14 | def dragEnterEvent(self, event):
15 | if event.mimeData().hasUrls:
16 | event.acceptProposedAction()
17 | else:
18 | super(MdiArea, self).dragEnterEvent(event)
19 |
20 | def dragMoveEvent(self, event):
21 | super(MdiArea, self).dragMoveEvent(event)
22 |
23 | def dropEvent(self, dropEvent):
24 | """
25 | :type dropEvent: QtGui.QDropEvent
26 | :return:
27 | """
28 | mime = dropEvent.mimeData()
29 | source = dropEvent.source()
30 |
31 | if mime.hasUrls():
32 | self.urlsDropped.emit(mime)
33 |
34 |
35 | class TextWarning(QtWidgets.QDialog):
36 | def __init__(self, text, parent=None):
37 | super(TextWarning, self).__init__(parent)
38 | self.resize(400, 300)
39 | self.gridLayout = QtWidgets.QGridLayout(self)
40 | self.verticalLayout = QtWidgets.QVBoxLayout()
41 | self.textBrowser = QtWidgets.QTextBrowser(self)
42 | self.verticalLayout.addWidget(self.textBrowser)
43 | self.buttonBox = QtWidgets.QDialogButtonBox(self)
44 | self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
45 | self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
46 | self.verticalLayout.addWidget(self.buttonBox)
47 | self.gridLayout.addLayout(self.verticalLayout, 0, 0, 1, 1)
48 |
49 | self.buttonBox.accepted.connect(self.accept)
50 | self.buttonBox.rejected.connect(self.reject)
51 |
52 | if type(text) in (list, tuple):
53 | text = "\n".join(text)
54 | self.textBrowser.setText(text)
55 | self.setWindowTitle("Warning")
56 |
57 |
58 | class WarningMsgBox(QtWidgets.QDialog):
59 | def __init__(self, text, title="Warning", parent=None):
60 | super(WarningMsgBox, self).__init__(parent)
61 | self.resize(500, 400)
62 | self.verticalLayout = QtWidgets.QVBoxLayout(self)
63 | self.verticalLayout.setContentsMargins(0, 0, 0, -1)
64 | self.textBrowser = QtWidgets.QTextBrowser(self)
65 | self.verticalLayout.addWidget(self.textBrowser)
66 | self.buttonBox = QtWidgets.QDialogButtonBox(self)
67 | self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
68 | self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Ok)
69 | self.verticalLayout.addWidget(self.buttonBox)
70 | self.buttonBox.accepted.connect(self.accept)
71 | self.buttonBox.rejected.connect(self.reject)
72 | self.setWindowTitle(title)
73 |
74 | if type(text) in (list, tuple):
75 | text = "\n".join(text)
76 | self.textBrowser.setText(text)
77 |
78 |
79 | class Spoiler(QtWidgets.QWidget):
80 | def __init__(self, parent=None, title='', animationDuration=300):
81 | """
82 | References:
83 | # Adapted from c++ version
84 | http://stackoverflow.com/questions/32476006/how-to-make-an-expandable-collapsable-section-widget-in-qt
85 | """
86 | super(Spoiler, self).__init__(parent=parent)
87 |
88 | self.animationDuration = 300
89 | self.toggleAnimation = QtCore.QParallelAnimationGroup()
90 | self.contentArea = QtWidgets.QScrollArea()
91 | self.headerLine = QtWidgets.QFrame()
92 | self.toggleButton = QtWidgets.QToolButton()
93 | self.mainLayout = QtWidgets.QGridLayout()
94 |
95 | toggleButton = self.toggleButton
96 | toggleButton.setStyleSheet("QToolButton { border: none; }")
97 | toggleButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
98 | toggleButton.setArrowType(QtCore.Qt.RightArrow)
99 | toggleButton.setText(str(title))
100 | toggleButton.setCheckable(True)
101 | toggleButton.setChecked(False)
102 |
103 | headerLine = self.headerLine
104 | headerLine.setFrameShape(QtWidgets.QFrame.HLine)
105 | headerLine.setFrameShadow(QtWidgets.QFrame.Sunken)
106 | headerLine.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Maximum)
107 |
108 | self.contentArea.setStyleSheet("QScrollArea { background-color: white; border: none; }")
109 | self.contentArea.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
110 | # start out collapsed
111 | self.contentArea.setMaximumHeight(0)
112 | self.contentArea.setMinimumHeight(0)
113 | # let the entire widget grow and shrink with its content
114 | toggleAnimation = self.toggleAnimation
115 | toggleAnimation.addAnimation(QtCore.QPropertyAnimation(self, "minimumHeight"))
116 | toggleAnimation.addAnimation(QtCore.QPropertyAnimation(self, "maximumHeight"))
117 | toggleAnimation.addAnimation(QtCore.QPropertyAnimation(self.contentArea, "maximumHeight"))
118 | # don't waste space
119 | mainLayout = self.mainLayout
120 | mainLayout.setVerticalSpacing(0)
121 | mainLayout.setContentsMargins(0, 0, 0, 0)
122 | row = 0
123 | mainLayout.addWidget(self.toggleButton, row, 0, 1, 1, QtCore.Qt.AlignLeft)
124 | mainLayout.addWidget(self.headerLine, row, 2, 1, 1)
125 | row += 1
126 | mainLayout.addWidget(self.contentArea, row, 0, 1, 3)
127 | self.setLayout(self.mainLayout)
128 |
129 | def start_animation(checked):
130 | arrow_type = QtCore.Qt.DownArrow if checked else QtCore.Qt.RightArrow
131 | direction = QtCore.QAbstractAnimation.Forward if checked else QtCore.QAbstractAnimation.Backward
132 | toggleButton.setArrowType(arrow_type)
133 | self.toggleAnimation.setDirection(direction)
134 | self.toggleAnimation.start()
135 |
136 | self.toggleButton.clicked.connect(start_animation)
137 |
138 | def setContentLayout(self, contentLayout):
139 | # Not sure if this is equivalent to self.contentArea.destroy()
140 | self.contentArea.destroy()
141 | self.contentArea.setLayout(contentLayout)
142 | collapsedHeight = self.sizeHint().height() - self.contentArea.maximumHeight()
143 | contentHeight = contentLayout.sizeHint().height()
144 | for i in range(self.toggleAnimation.animationCount()-1):
145 | spoilerAnimation = self.toggleAnimation.animationAt(i)
146 | spoilerAnimation.setDuration(self.animationDuration)
147 | spoilerAnimation.setStartValue(collapsedHeight)
148 | spoilerAnimation.setEndValue(collapsedHeight + contentHeight)
149 | contentAnimation = self.toggleAnimation.animationAt(self.toggleAnimation.animationCount() - 1)
150 | contentAnimation.setDuration(self.animationDuration)
151 | contentAnimation.setStartValue(0)
152 | contentAnimation.setEndValue(contentHeight)
153 |
154 |
155 | class LineEdit(QtWidgets.QLineEdit):
156 | def __init__(self, heightx=None, widthx=None, parent=None):
157 | super(LineEdit, self).__init__(parent)
158 | self.heightx = 1 if heightx is None else heightx
159 | self.widthx = 1 if widthx is None else widthx
160 | self.size = super(LineEdit, self).sizeHint()
161 | self.size.setHeight(self.size.height() * self.heightx)
162 | self.size.setWidth(self.size.width() * self.widthx)
163 |
164 | def sizeHint(self):
165 | return self.size
166 |
167 |
168 | class LineEditNumeric(LineEdit):
169 | def __init__(self, parent=None):
170 | super(LineEditNumeric, self).__init__(widthx=0.5, parent=parent)
171 | self.setValidator(QtGui.QDoubleValidator())
172 |
173 | def value(self):
174 | return float(self.text())
175 |
176 | def setValue(self, val):
177 | self.setText("{:0.08g}".format(val))
178 |
--------------------------------------------------------------------------------