├── 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 |
radie.qt.plotwidget
51 |
52 | 53 | DFListView 54 | QTreeView 55 |
radie.qt.plotlist
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 | 19 | 21 | 41 | 43 | 44 | 46 | image/svg+xml 47 | 49 | 50 | 51 | 52 | 53 | 58 | 62 | 67 | 68 | 69 | 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 | 19 | 21 | 41 | 43 | 44 | 46 | image/svg+xml 47 | 49 | 50 | 51 | 52 | 53 | 58 | 62 | 68 | 74 | 80 | 81 | 82 | 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 |
radie.qt.plotwidget
92 |
93 | 94 | DFListView 95 | QTreeView 96 |
radie.qt.plotlist
97 |
98 |
99 | 100 | 101 |
102 | -------------------------------------------------------------------------------- /radie/qt/resources/icons/saveicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 57 | 62 | 68 | 73 | 74 | 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 | 19 | 21 | 41 | 43 | 44 | 46 | image/svg+xml 47 | 49 | 50 | 51 | 52 | 53 | 58 | 62 | 67 | 70 | 77 | 84 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /radie/qt/resources/icons/xyscatter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 41 | 43 | 44 | 46 | image/svg+xml 47 | 49 | 50 | 51 | 52 | 53 | 58 | 62 | 68 | 74 | 80 | 86 | 92 | 93 | 94 | 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 |
radie.qt.plotwidget
115 |
116 | 117 | DFListView 118 | QTreeView 119 |
radie.qt.plotlist
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 | 19 | 21 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 57 | 63 | 69 | x 80 | y 91 | 92 | 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 |
radie.qt.plotwidget
162 |
163 | 164 | DFXYListView 165 | QTreeView 166 |
radie.qt.plotlist
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 | 19 | 21 | 41 | 43 | 44 | 46 | image/svg+xml 47 | 49 | 50 | 51 | 52 | 53 | 58 | 62 | 68 | 76 | 84 | 92 | 100 | 108 | 116 | 124 | 132 | 133 | 134 | 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 | 19 | 21 | 24 | 29 | 30 | 33 | 38 | 39 | 42 | 47 | 48 | 51 | 56 | 57 | 60 | 65 | 66 | 67 | 86 | 88 | 89 | 91 | image/svg+xml 92 | 94 | 95 | 96 | 97 | 98 | 103 | 107 | 110 | 114 | 127 | 132 | 133 | 134 | 135 | 136 | 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 | 19 | 21 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 57 | 64 | 69 | 74 | 79 | 84 | 89 | 94 | 99 | 104 | 109 | 114 | 119 | 124 | 130 | 131 | 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 | ![Alt Text](https://raw.githubusercontent.com/dvincentwest/radie-demos/master/radie-demo.gif) 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 | 19 | 21 | 24 | 29 | 30 | 33 | 38 | 39 | 42 | 47 | 48 | 51 | 56 | 57 | 60 | 65 | 66 | 67 | 86 | 88 | 89 | 91 | image/svg+xml 92 | 94 | 95 | 96 | 97 | 98 | 103 | 107 | 110 | 114 | 117 | 121 | 127 | 133 | 139 | 145 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 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 | --------------------------------------------------------------------------------