From 061cd64873f7eb8cdcba282816820e775b401ac6 Mon Sep 17 00:00:00 2001 From: Rhys Thomas Date: Thu, 8 Jan 2026 20:38:21 +0000 Subject: [PATCH 01/18] feat: update docs - Use pydata theme - like matplotlib, scipy etc - Update sphinx and update requirements - Use sphinx.ext.apidoc instead of legacy apidoc call --- docs/Makefile | 5 - docs/source/_static/custom-icons.js | 19 + docs/source/conf.py | 154 +-- docs/source/howtouse.ipynb | 1516 +++++++++++++++++++++++++++ docs/source/index.rst | 4 +- pyproject.toml | 8 +- 6 files changed, 1631 insertions(+), 75 deletions(-) create mode 100644 docs/source/_static/custom-icons.js create mode 100644 docs/source/howtouse.ipynb diff --git a/docs/Makefile b/docs/Makefile index f22e981..043b44c 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -20,11 +20,6 @@ help: @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) docs: - @echo "Building API documentation to '/source/defdap'" - sphinx-apidoc -feMT ../defdap -o ./source/defdap - @echo "Auto-generation of API documentation finished. " \ - "The generated files are in '/source/defdap'" - @echo " " @echo "Cleaning build folder" make clean @echo " " diff --git a/docs/source/_static/custom-icons.js b/docs/source/_static/custom-icons.js new file mode 100644 index 0000000..0c33d26 --- /dev/null +++ b/docs/source/_static/custom-icons.js @@ -0,0 +1,19 @@ +FontAwesome.library.add( + /** + * Custom icon definitions + * + * see https://pydata-sphinx-theme.readthedocs.io/en/latest/user_guide/header-links.html#svg-image-icons + */ + { + prefix: "fa-custom", + iconName: "pypi", + icon: [ + 17.313, + 19.807, + [], + "e001", + // https://simpleicons.org/icons/pypi.svg + "m10.383 0.2-3.239 1.1769 3.1883 1.1614 3.239-1.1798zm-3.4152 1.2411-3.2362 1.1769 3.1855 1.1614 3.2369-1.1769zm6.7177 0.00281-3.2947 1.2009v3.8254l3.2947-1.1988zm-3.4145 1.2439-3.2926 1.1981v3.8254l0.17548-0.064132 3.1171-1.1347zm-6.6564 0.018325v3.8247l3.244 1.1805v-3.8254zm10.191 0.20931v2.3137l3.1777-1.1558zm3.2947 1.2425-3.2947 1.1988v3.8254l3.2947-1.1988zm-8.7058 0.45739c0.00929-1.931e-4 0.018327-2.977e-4 0.027485 0 0.25633 0.00851 0.4263 0.20713 0.42638 0.49826 1.953e-4 0.38532-0.29327 0.80469-0.65542 0.93662-0.36226 0.13215-0.65608-0.073306-0.65613-0.4588-6.28e-5 -0.38556 0.2938-0.80504 0.65613-0.93662 0.068422-0.024919 0.13655-0.038114 0.20156-0.039466zm5.2913 0.78369-3.2947 1.1988v3.8247l3.2947-1.1981zm-10.132 1.239-3.2362 1.1769 3.1883 1.1614 3.2362-1.1769zm6.7177 0.00213-3.2926 1.2016v3.8247l3.2926-1.2009zm-3.4124 1.2439-3.2947 1.1988v3.8254l3.2947-1.1988zm-6.6585 0.016195v3.8275l3.244 1.1805v-3.8254zm16.9 0.21143-3.2947 1.1988v3.8247l3.2947-1.1981zm-3.4145 1.2411-3.2926 1.2016v3.8247l3.2926-1.2009zm-3.4145 1.2411-3.2926 1.2016v3.8247l3.2926-1.2009zm-3.4124 1.2432-3.2947 1.1988v3.8254l3.2947-1.1988zm-6.6585 0.019027v3.8247l3.244 1.1805v-3.8254zm13.485 1.4497-3.2947 1.1988v3.8247l3.2947-1.1981zm-3.4145 1.2411-3.2926 1.2016v3.8247l3.2926-1.2009zm2.4018 0.38127c0.0093-1.83e-4 0.01833-3.16e-4 0.02749 0 0.25633 0.0085 0.4263 0.20713 0.42638 0.49826 1.97e-4 0.38532-0.29327 0.80469-0.65542 0.93662-0.36188 0.1316-0.65525-0.07375-0.65542-0.4588-1.95e-4 -0.38532 0.29328-0.80469 0.65542-0.93662 0.06842-0.02494 0.13655-0.03819 0.20156-0.03947zm-5.8142 0.86403-3.244 1.1805v1.4201l3.244 1.1805z", + ], + } +); diff --git a/docs/source/conf.py b/docs/source/conf.py index 3d79157..5240aa1 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -6,13 +6,19 @@ import shutil import sys -# -- Path setup -------------------------------------------------------------- +# ----------------------------------------------------------------------------- +# Path setup +# ----------------------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath('../../')) # Reference the root directory so autodocs can find the python modules +# ----------------------------------------------------------------------------- +# Generate 'how to use' page from example notebook +# ----------------------------------------------------------------------------- + # Copy the example notebook into the docs source shutil.copyfile('../../notebooks/example_notebook.ipynb', 'howtouse.ipynb') @@ -34,13 +40,27 @@ with open('howtouse.ipynb', "w") as f: f.write(new_text) -# -- Project information ----------------------------------------------------- +nbsphinx_allow_errors = True +nbsphinx_execute = 'always' +nbsphinx_kernel_name = 'python3' + +nbsphinx_prolog = """ +This page was built from the example_notebook Jupyter notebook available on `Github `_ + +.. image:: https://mybinder.org/badge_logo.svg + :target: https://mybinder.org/v2/gh/MechMicroMan/DefDAP/master?filepath=example_notebook.ipynb + +---- +""" + +# ----------------------------------------------------------------------------- +# Project information +# ----------------------------------------------------------------------------- project = 'DefDAP' copyright = '2023, Mechanics of Microstructures Group at The University of Manchester' author = 'Michael D. Atkinson, Rhys Thomas, João Quinta da Fonseca' - def get_version(): ver_path = '../../defdap/_version.py' main_ns = {} @@ -55,41 +75,28 @@ def get_version(): # The short X.Y version version = '.'.join(release.split('.')[:2]) -# -- General configuration --------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' +# ----------------------------------------------------------------------------- +# General configuration +# ----------------------------------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', + 'sphinx.ext.apidoc', 'sphinx.ext.coverage', 'sphinx.ext.mathjax', 'sphinx.ext.viewcode', 'sphinx.ext.githubpages', 'sphinx.ext.napoleon', 'sphinx.ext.intersphinx', + 'sphinx.ext.autosummary', 'sphinx_autodoc_typehints', - 'sphinx_rtd_theme', + 'pydata_sphinx_theme', 'nbsphinx' ] -nbsphinx_allow_errors = True -nbsphinx_execute = 'always' -nbsphinx_kernel_name = 'python3' - -nbsphinx_prolog = """ -This page was built from the example_notebook Jupyter notebook available on `Github `_ - -.. image:: https://mybinder.org/badge_logo.svg - :target: https://mybinder.org/v2/gh/MechMicroMan/DefDAP/master?filepath=example_notebook.ipynb - ----- -""" - napoleon_use_param = True # Add any paths that contain templates here, relative to this directory. @@ -118,66 +125,85 @@ def get_version(): # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' +# ----------------------------------------------------------------------------- +# Options for HTML these +# ----------------------------------------------------------------------------- -# -- Options for HTML output ------------------------------------------------- +html_theme = "pydata_sphinx_theme" -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = "sphinx_rtd_theme" - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# html_theme_options = { - 'collapse_navigation': False, - 'sticky_navigation': True, - 'navigation_depth': 4, - 'includehidden': True, - 'titles_only': False + "icon_links": [ + { + "name": "GitHub", + "url": "https://github.com/MechMicroMan/DefDAP", + "icon": "fa-brands fa-square-github", + "type": "fontawesome" + }, + { + "name": "PyPI", + "url": "https://pypi.org/project/defdap", + "icon": "fa-custom fa-pypi", # defined in file `_static/custom-icons.js` + } + ], + "use_edit_page_button": True, } -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". +html_context = { + "github_user": "MechMicroMan", + "github_repo": "DefDAP", + "github_version": "master", + "doc_path": "docs/source", +} + +html_static_path = ["_static"] +html_js_files = [ + ("custom-icons.js", {"defer": "defer"}), +] + html_static_path = ['_static'] +html_copy_source = False -# -- Options for HTMLHelp output --------------------------------------------- +# ----------------------------------------------------------------------------- +# Options for HTMLHelp output +# ----------------------------------------------------------------------------- # Output file base name for HTML help builder. htmlhelp_basename = 'DefDAPdoc' +# ----------------------------------------------------------------------------- +# Autodoc +# ----------------------------------------------------------------------------- -# -- Generate API docs during sphinx-build (for readthedocs) ------------------ - -# Determine if on RTD -ON_RTD = (os.environ.get('READTHEDOCS') == 'True') - -def run_apidoc(_): - - from sphinx.ext import apidoc +autodoc_member_order = 'bysource' +autodoc_default_options = { + 'inherited-members': True, +} - api_args = [ - '--force', - '--separate', # Put each module on seperate page - '--no-toc', # No table of contents - '../../defdap', # Module path - '-o', # Directory to output.. - '../source/defdap' # here - ] +# ----------------------------------------------------------------------------- +# Apidoc +# ----------------------------------------------------------------------------- + +apidoc_modules = [ + { + 'path': '../../defdap', + 'destination': 'defdap', + 'separate_modules': True, + 'module_first': False, + 'automodule_options': {'members', 'show-inheritance', 'undoc-members'} + } +] - # Invoke apidoc - apidoc.main(api_args) +# ----------------------------------------------------------------------------- +# Autosummary +# ----------------------------------------------------------------------------- -def setup(app): - if ON_RTD: - app.connect('builder-inited', run_apidoc) +autosummary_generate = True -# -- Extension configuration ------------------------------------------------- +# ----------------------------------------------------------------------------- +# Intersphinx +# ----------------------------------------------------------------------------- -autodoc_member_order = 'bysource' intersphinx_mapping = {'python': ('https://docs.python.org/3.7/', None), 'numpy': ('https://numpy.org/doc/stable/', None), 'scipy': ('https://docs.scipy.org/doc/scipy/reference/', None), diff --git a/docs/source/howtouse.ipynb b/docs/source/howtouse.ipynb new file mode 100644 index 0000000..2c3da31 --- /dev/null +++ b/docs/source/howtouse.ipynb @@ -0,0 +1,1516 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "# How to use\n", + "\n", + "These pages will outline basic usage of DefDAP, including loading a DIC and EBSD map, linking them with homologous points and producing maps" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## Load in packages" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "DefDAP is split into modules for processing EBSD (`defdap.ebsd`) and HRDIC (`defdap.hrdic`) data. There are also modules for manpulating orientations (`defdap.quat`) and creating custom figures (`defdap.plotting`) which is introduced later. We also import some of the usual suspects of the python scientific stack: `numpy` and `matplotlib`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "\n", + "import defdap.hrdic as hrdic\n", + "import defdap.ebsd as ebsd\n", + "from defdap.quat import Quat\n", + "\n", + "# try tk, qt, osx (if using mac) or notebook for interactive plots. If none work, use inline\n", + "%matplotlib widget" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## Load in a HRDIC map" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "dic_filepath = \"../../tests/data/testDataDIC.txt\"\n", + "dic_map = hrdic.Map(dic_filepath)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "### Set the scale of the map\n", + "This is defined as the pixel size in the DIC pattern images, measured in microns per pixel." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "field_width = 20 # microns\n", + "num_pixels = 2048\n", + "dic_map.set_scale(field_width / num_pixels)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "### Plot the map with a scale bar" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "dic_map.plot_map('max_shear', vmin=0, vmax=0.10, plot_scale_bar=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can print out the names of all data currently available in the map. Try plotting different data, `plot_map` has a parameter `component` that is either an int `0` or tuple of ints `(0,1)` for tensor components or a named component such as `'norm'`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(dic_map.data)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "### Crop the map\n", + "HRDIC maps often contain spurious data at the edges which should be removed before performing any analysis. The crop is defined by the number of points to remove from each edge of the map. Note that the test data doesn not require cropping as it is a subset of a larger dataset." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "dic_map.set_crop(left=0, right=0, top=0, bottom=0)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "### Statistics\n", + "Some simple statistics such as the minimum, mean and maximum of the effective shear strain, E11 and E22 components can be printed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "dic_map.print_stats_table(percentiles=[0, 50, 100], components=['max_shear', 'e'])" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "### Set the location of the DIC pattern images \n", + "The pattern images are used later to define the position of homologous material points. The path is relative to the directory set when loading in the map. The second parameter is the pixel binning factor of the image relative to the DIC sub-region size i.e. the number of pixels in the image across a single datapoint in the DIC map. We recommend binning the pattern images by the same factor as the DIC sub-region size, doing so enhances the contrast between microstructure features." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# set the path of the pattern image, this is relative to the location of the DIC data file\n", + "dic_map.set_pattern(\"testDataPat.bmp\", 1)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## Load in an EBSD map\n", + "Currently, OxfordBinary (a .crc and .cpr file pair), OxfordText (.ctf file), EdaxAng (.ang file) or PythonDict (Python dictionary) filetypes are supported. The crystal structure and slip systems are automatically loaded for each phase in the map. The orientation in the EBSD are converted to a quaternion representation so calculations can be applied later." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "ebsd_map = ebsd.Map(\"../../tests/data/testDataEBSD.cpr\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "A list of detected phases and crystal structures can be printed" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "for i, phase in enumerate(ebsd_map.phases):\n", + " print(i+1)\n", + " print(phase)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "A list of the slip planes, colours and slip directions can be printed" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "ebsd_map.phases[0].print_slip_systems()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "### Plot the EBSD map\n", + "Using an Euler colour mapping or inverse pole figure colouring with the sample reference direction passed as a vector." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "ebsd_map.plot_map('euler_angle', 'all_euler', plot_scale_bar=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "ebsd_map.plot_map('orientation', 'IPF_x', plot_scale_bar=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "A KAM map can also be plotted as follows" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "ebsd_map.plot_map('KAM', vmin=0, vmax=2*np.pi/180)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "### Detect grains in the EBSD\n", + "This is done in two stages: first bounaries are detected in the map as any point with a misorientation to a neighbouring point greater than a critical value (`boundDef` in degrees). A flood fill type algorithm is then applied to segment the map into grains, with any grains containining fewer than a critical number of pixels removed (`minGrainSize` in pixels). The data e.g. orientations associated with each grain are then stored (referenced strictly, the data isn't stored twice) in a grain object and a list of the grains is stored in the EBSD map (named `grainList`). This allows analysis routines to be applied to each grain in a map in turn." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "ebsd_map.data.generate('grain_boundaries', misori_tol=8)\n", + "ebsd_map.data.generate('grains', min_grain_size=10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now when we print the available data there is a section for dervied data, this comes from data defined at the grain level. This derived data can be from different sources and later you will see data shared between linked HRDIC and EBSD maps." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(ebsd_map.data)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can use this derived data as with other map data to plots maps, statistics or directly access the data as a numpy array." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The Schmid factors for each grain can be calculated and plotted. The `slip_systems` argument can be specified, to only calculate the Schmid factor for certain planes, otherwise the maximum for all slip systems is calculated.\n", + "\n", + "Try changing the loading direction." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "ebsd_map.calc_average_grain_schmid_factors(\n", + " load_vector=np.array([1,0,0]), \n", + " slip_systems=None\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "ebsd_map.plot_average_grain_schmid_factors_map()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "### Single grain analysis\n", + "The `locate_grain` method allows interactive selection of a grain of intereset to apply any analysis to. Clicking on grains in the map will highlight the grain and print out the grain ID (position in the grain list) of the grain." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ebsd_map.locate_grain()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(f'Grain ID of last selected grain: {ebsd_map.sel_grain.grain_id}')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "A built-in example is to calculate the average orientation of the grain and plot this orientation in a IPF" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "grain_id = 48\n", + "grain = ebsd_map[grain_id]\n", + "grain.calc_average_ori() # stored as a quaternion named grain.refOri\n", + "print(grain.ref_ori)\n", + "grain.plot_ref_ori(direction=[0, 0, 1])" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "The spread of orientations in a given grain can also be plotted on an IPF" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "plot = grain.plot_ori_spread(direction=np.array([0, 0, 1]), c='b', s=1, alpha=0.2)\n", + "grain.plot_ref_ori(direction=[0, 0, 1], c='k', plot=plot)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "The unit cell for the average grain orientation can also be ploted" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "grain.plot_unit_cell()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "Printing a list of the slip plane indices, angle of slip plane intersection with the screen (defined as counter-clockwise from upwards), colour defined for the slip plane and also the slip directions and corresponding Schmid factors, is also built in" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "grain.print_slip_traces()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "A second built-in example is to calcuate the grain misorientation, specifically the grain reference orientation deviation (GROD). This shows another feature of the `locate_grain` method, which stores the last selected grain in a variable called `sel_grain` in the EBSD map." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "if ebsd_map.sel_grain is None: \n", + " ebsd_map.sel_grain = ebsd_map[57]\n", + " \n", + "ebsd_map.sel_grain.build_mis_ori_list()\n", + "ebsd_map.sel_grain.plot_mis_ori(plot_scale_bar=True, vmin=0, vmax=3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also explore and visulaise all data available for a grain" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "grain_id = 40\n", + "grain = ebsd_map[grain_id]\n", + "print(grain.data)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "grain.plot_map('band_contrast')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "### Multi grain analysis\n", + "Once an analysis routine has been prototyped for a single grain it can be applied to all the grains in a map using a loop over the grains and any results added to a list for use later. Of couse you could also apply to a smaller subset of grains as well." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "grain_av_oris = []\n", + "for grain in ebsd_map:\n", + " grain.calc_average_ori()\n", + " grain_av_oris.append(grain.ref_ori)\n", + "\n", + "# Plot all the grain orientations in the map\n", + "Quat.plot_ipf(grain_av_oris, [0, 0, 1], ebsd_map.crystal_sym, marker='o', s=10)\n", + "plt.tight_layout()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "Some common grain analysis routines are built into the EBSD map object, including:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "ebsd_map.calc_grain_av_oris()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "ebsd_map.calc_grain_mis_ori()\n", + "ebsd_map.plot_mis_ori_map(vmin=0, vmax=5, plot_gbs=True, plot_scale_bar=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "There are also methods for plotting GND density, phases and boundaries. All of the plotting functions in DefDAP use the same parameters to modify the plot, examples seen so far are `plot_gbs`, `plotScaleBar`, `vmin`, `vmax`." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## Linking the HRDIC and EBSD\n", + "### Define homologous points\n", + "To register the two datasets, homologous points (points at the same material location) within each map are used to estimate a transformation between the two frames the data are defined in. The homologous points are selected manually using an interactive tool within DefDAP. To select homologous call the method `set_homog_point` on each of the data maps, which will open a plot window with a button labelled 'save point' in the bottom right. You select a point by right clicking on the map, adjust the position with the arrow and accept the point by with the save point button. Then select the same location in the other map. Note that as we set the location of the pattern image for the HRDIC map that the points can be selected on the pattern image rather than the strain data.\n", + "\n", + "Select 3-4 homologous points in spread over each map in the same order." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "dic_map.set_homog_point(map_name=\"pattern\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "ebsd_map.set_homog_point()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "The points are stored as a list of tuples `(x, y)` in each of the maps. This means the points can be set from previous values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "dic_map.frame.homog_points" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "ebsd_map.frame.homog_points" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "Here are some example homologous points for this data, after setting these by running the cells below you can view the locations in the maps by running the `set_homog_point` methods (above) again. These will not be in the correct location if the crop values of the HRDIC map have been changed from 0." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "dic_map.frame.homog_points = [\n", + " (36, 72), \n", + " (279, 27), \n", + " (162, 174), \n", + " (60, 157)\n", + "]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "ebsd_map.frame.homog_points = [\n", + " (68, 95), \n", + " (308, 45), \n", + " (191, 187), \n", + " (89, 174)\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "### Link the maps\n", + "Finally the two data maps are linked. The type of transform between the two frames can be affine, projective, polynomial.\n", + "\n", + "Try the different projections and see if the make any difference to the percieved transoformation for the points you selected." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "dic_map.link_ebsd_map(ebsd_map, transform_type=\"affine\")\n", + "# dic_map.link_ebsd_map(ebsd_map, transform_type=\"projective\")\n", + "# dic_map.link_ebsd_map(ebsd_map, transform_type=\"polynomial\", order=2)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "### Show the transformation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "from skimage import transform as tf\n", + "\n", + "data = np.zeros((2000, 2000), dtype=float)\n", + "data[500:1500, 500:1500] = 1.\n", + "transform = dic_map.experiment.get_frame_transform(dic_map.frame, ebsd_map.frame)\n", + "dataWarped = tf.warp(data, transform)\n", + "\n", + "fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8,4))\n", + "ax1.set_title('Reference')\n", + "ax1.imshow(data)\n", + "ax2.set_title('Transformed')\n", + "ax2.imshow(dataWarped)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "### Segment into grains\n", + "The HRDIC map can now be segmented into grains using the grain boundaries detected in the EBSD map. Analysis rountines can then be applied to individual grain, as with the EBSD grains. The grain finding process will also attempt to link the grains between the EBSD and HRDIC and each grain in the HRDIC has a reference (`ebsdGrain`) to the corrosponding grain in the EBSD map." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "dic_map.data.generate('grains', min_grain_size=10)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dic_map.plot_map(\n", + " 'max_shear', vmin=0, vmax=0.10, \n", + " plot_scale_bar=True, plot_gbs='pixel'\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "Now, a grain can also be selected interactively in the DIC map, in the same way a grain can be selected from an EBSD map. If `display_grain` is set to true, then a plot shows the map segmented for the grain with coloured lines to display the slip trace direction of the set slip systems (see colours `grain.print_slip_traces()`) and black lines marking direction of slip bands detected in the grain." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "dic_map.locate_grain(display_grain=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## Plotting examples" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "Some of the plotting features are shown in examples below." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Built-in plots\n", + "These are the plotting functions you have been using so far, run by calling methods of the data objects. Each method returns a plot object that can be used to modify the plot after it has been created." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "plot = dic_map.plot_map(\n", + " 'max_shear', vmin=0, vmax=0.10, \n", + " plot_scale_bar=True, plot_gbs='line'\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "plot = ebsd_map.plot_map(\n", + " 'euler_angle', component='all_euler',\n", + " plot_scale_bar=True, plot_gbs=True,\n", + " highlight_grains=[10, 20, 45], highlight_alpha=0.9, highlight_colours=['r']\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "dic_grain_id = 42\n", + "dic_grain = dic_map[dic_grain_id]\n", + "\n", + "plot = dic_grain.plot_map(\n", + " 'max_shear', plot_scale_bar=True, \n", + " plot_slip_traces=True, plot_slip_bands=True\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "### IPF plotting" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "This plot will show the positions of selected grains in an IPF pole figure, with the marker size representing grain area and mean effective shear strain." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# For all grains in the DIC map\n", + "\n", + "# Make an array of quaternions\n", + "grain_oris = [grain.ebsd_grain.ref_ori for grain in dic_map]\n", + "\n", + "# Make an array of grain area\n", + "grain_areas = np.array([len(grain) for grain in dic_map]) * dic_map.scale**2\n", + "\n", + "# Scaling the grain area, so that the maximum size of a marker is 200 points^2\n", + "grain_area_scaling = 200. / grain_areas.max()\n", + "\n", + "# Make an array of mean effective shear strain\n", + "grain_strains = [np.array(grain.data.max_shear).mean() for grain in dic_map]\n", + "\n", + "plot = Quat.plot_ipf(\n", + " grain_oris, direction=[1,0,0], sym_group='cubic', \n", + " marker='o', s=grain_areas*grain_area_scaling, \n", + " c=grain_strains, vmin=0, vmax=0.018, cmap='viridis'\n", + ")\n", + "plot.add_colour_bar(label='Mean effective shear strain')\n", + "plot.add_legend(scaling=grain_area_scaling)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# For selected grains in the DIC map\n", + "\n", + "# Select grains from the DIC map\n", + "dic_grain_ids = [\n", + " 2, 5, 7, 9, 15, 17, 18, 23, 29, 32, \n", + " 33, 37, 40, 42, 49, 50, 51, 54, 58, 60\n", + "]\n", + "\n", + "# Make an array of quaternions\n", + "grain_oris = np.array([\n", + " dic_map[grain_id].ebsd_grain.ref_ori for grain_id in dic_grain_ids\n", + "])\n", + "\n", + "# Make an array of grain area\n", + "grain_areas = np.array([\n", + " len(dic_map[grain_id]) for grain_id in dic_grain_ids\n", + "]) * dic_map.scale**2\n", + "\n", + "# Scaling the grain area, so that the maximum size of a marker is 200 points^2\n", + "grain_area_scaling = 200. / grain_areas.max()\n", + "\n", + "# Make an array of mean effective shear strain\n", + "grain_strains = np.array([\n", + " np.mean(dic_map[grain].data.max_shear) for grain in dic_grain_ids\n", + "])\n", + "\n", + "plot = Quat.plot_ipf(\n", + " grain_oris, direction=[1,0,0], sym_group='cubic', \n", + " marker='o', s=grain_areas*grain_area_scaling, \n", + " c=grain_strains, vmin=0, vmax=0.018, cmap='viridis'\n", + ")\n", + "plot.add_colour_bar(label='Mean effective shear strain')\n", + "plot.add_legend(scaling=grain_area_scaling)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "### Create your own" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "from defdap.plotting import MapPlot, GrainPlot, HistPlot" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "map_data = dic_map.data['e'][0,0]\n", + "map_data = dic_map.crop(map_data)\n", + "\n", + "plot = MapPlot.create(\n", + " dic_map, map_data,\n", + " vmin=-0.1, vmax=0.1, plot_colour_bar=True, cmap=\"seismic\",\n", + " plot_gbs=True, dilate_boundaries=True, boundary_colour='black'\n", + ")\n", + "\n", + "plot.add_scale_bar()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "### Functions for grain averaging and grain segmentation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "plot = dic_map.plot_grain_data_map(\n", + " map_data,\n", + " vmin=-0.06, vmax=0.06, plot_colour_bar=True,\n", + " cmap=\"seismic\", clabel=\"Axial strain ($e_11$)\",\n", + " plot_scale_bar=True\n", + ")\n", + "\n", + "plot.add_grain_boundaries(dilate=True, colour=\"white\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "plot = dic_map.plot_grain_data_ipf(\n", + " np.array((1,0,0)), map_data, marker='o', \n", + " vmin=-0.06, vmax=0.06, plot_colour_bar=True, \n", + " clabel=\"Axial strain ($e_11$)\", cmap=\"seismic\",\n", + ")\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "dic_grain_id = 42\n", + "dic_grain = dic_map[dic_grain_id]\n", + "\n", + "plot = dic_grain.plot_grain_data(\n", + " map_data, \n", + " vmin=-0.1, vmax=0.1, plot_colour_bar=True, \n", + " clabel=\"Axial strain ($e_11$)\", cmap=\"seismic\",\n", + " plot_scale_bar=True\n", + ")\n", + "\n", + "plot.add_slip_traces()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "### Composite plots" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "By utilising some additional functionality within matplotlib, composite plots can be produced." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "from matplotlib import gridspec" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# Create a figure with 3 sets of axes\n", + "fig = plt.figure(figsize=(8, 4))\n", + "gs = gridspec.GridSpec(2, 2, width_ratios=[3, 1],\n", + " wspace=0.15, hspace=0.15, \n", + " left=0.02, right=0.98,\n", + " bottom=0.12, top=0.95) \n", + "ax0 = plt.subplot(gs[:, 0])\n", + "ax1 = plt.subplot(gs[0, 1])\n", + "ax2 = plt.subplot(gs[1, 1])\n", + "\n", + "\n", + "# add a strain map\n", + "plot0 = dic_map.plot_map(\n", + " map_name='max_shear',\n", + " ax=ax0, fig=fig, \n", + " vmin=0, vmax=0.08, plot_scale_bar=True, \n", + " plot_gbs=True, dilate_boundaries=True\n", + ")\n", + "\n", + "# add an IPF of grain orientations\n", + "dic_oris = []\n", + "for grain in dic_map:\n", + " if len(grain) > 20:\n", + " dic_oris.append(grain.ref_ori)\n", + "plot1 = Quat.plot_ipf(\n", + " dic_oris, np.array((1,0,0)), 'cubic', \n", + " ax=ax1, fig=fig, s=10\n", + ")\n", + "\n", + "# add histrogram of strain values\n", + "plot2 = HistPlot.create(\n", + " dic_map.crop(dic_map.data.max_shear),\n", + " ax=ax2, fig=fig, marker='o', markersize=2,\n", + " axes_type=\"logy\", bins=50, range=(0,0.06)\n", + ")\n", + "plot2.ax.set_xlabel(\"Effective shear strain\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "Figures can be saved to raster (png, jpg, ..) and vector formats (eps, svg), the format is guessed from the file extension given. The last displayed figure can be saved using:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "#plt.savefig(\"test_save_fig.png\", dpi=200)\n", + "#plt.savefig(\"test_save_fig.eps\", dpi=200)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "fig, ((ax0, ax1), (ax2, ax3)) = plt.subplots(2, 2, figsize=(8, 6))\n", + "\n", + "dic_grain_id = 42\n", + "dic_grain = dic_map[dic_grain_id]\n", + "\n", + "# add a strain map\n", + "plot0 = dic_grain.plot_map(\n", + " 'max_shear',\n", + " ax=ax0, fig=fig, \n", + " vmin=0, vmax=0.08, plot_scale_bar=True,\n", + " plot_slip_traces=True\n", + ")\n", + "\n", + "\n", + "# add a misorientation\n", + "ebsd_grain = dic_grain.ebsd_grain\n", + "plot1 = ebsd_grain.plot_mis_ori(component=0, ax=ax1, fig=fig, vmin=0, vmax=1, clabel=\"GROD\", plot_scale_bar=True)\n", + "\n", + "\n", + "# add an IPF\n", + "plot2 = ebsd_grain.plot_ori_spread(\n", + " direction=np.array((1,0,0)), c='b', s=1, alpha=0.2,\n", + " ax=ax2, fig=fig\n", + ")\n", + "ebsd_grain.plot_ref_ori(\n", + " direction=np.array((1,0,0)), c='k', s=100, plot=plot2\n", + ")\n", + "\n", + "# add histrogram of strain values\n", + "plot3 = HistPlot.create(\n", + " dic_map.crop(dic_map.data.max_shear),\n", + " ax=ax3, fig=fig,\n", + " axes_type=\"logy\", bins=50, range=(0,0.06))\n", + " \n", + "plot3.ax.set_xlabel(\"Effective shear strain\")\n", + "\n", + "\n", + "plt.tight_layout()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Exercise" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Calculate the z component of rigid body rotation, $$ \\omega_3 = \\frac{F_{12}-F_{21}}{2}, $$ for the HRDIC map. (HINT: the deformation gradient (F) can be accessed as numpy array with `dic_map.data.f`)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Plot $\\omega_3$ as a map with grain boundaries and a scale bar. Choose an appropiate colourmap (see https://matplotlib.org/stable/gallery/color/colormap_reference.html) and scale. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Make a figure containing a map of $\\omega_3$ and a map of misorientaion for a single grain in the DIC map." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Calculate grain average $\\omega_3$ and then plot these on a IPF." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "defdap", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/source/index.rst b/docs/source/index.rst index 0b8a712..165e808 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -31,5 +31,5 @@ Contents howtouse contributing papers - license - modules \ No newline at end of file + modules + license \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 2682908..391dbba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,10 +55,10 @@ testing = [ "pytest_cases", ] docs = [ - "sphinx==5.0.2", - "sphinx_rtd_theme==0.5.0", - "sphinx_autodoc_typehints==1.11.1", - "nbsphinx==0.9.3", + "sphinx==9.1.0", + "pydata_sphinx_theme", + "sphinx_autodoc_typehints==3.6.2", + "nbsphinx==0.9.8", "ipykernel", "pandoc", "ipympl", From e32b38cc0f94b550f93183fd3d1f26d58a70fb35 Mon Sep 17 00:00:00 2001 From: Rhys Thomas Date: Thu, 8 Jan 2026 22:07:47 +0000 Subject: [PATCH 02/18] feat: Rearrange docs --- docs/source/conf.py | 15 +- docs/source/howtouse.ipynb | 1516 ------------------------ docs/source/index.rst | 3 +- docs/source/installation.rst | 14 - docs/source/userguide.rst | 10 + docs/source/userguide/installation.rst | 33 + 6 files changed, 53 insertions(+), 1538 deletions(-) delete mode 100644 docs/source/howtouse.ipynb delete mode 100644 docs/source/installation.rst create mode 100644 docs/source/userguide.rst create mode 100644 docs/source/userguide/installation.rst diff --git a/docs/source/conf.py b/docs/source/conf.py index 5240aa1..3e7d575 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -20,24 +20,24 @@ # ----------------------------------------------------------------------------- # Copy the example notebook into the docs source -shutil.copyfile('../../notebooks/example_notebook.ipynb', 'howtouse.ipynb') +shutil.copyfile('../../notebooks/example_notebook.ipynb', 'userguide/howtouse.ipynb') # Open file -with open('howtouse.ipynb') as f: +with open('userguide/howtouse.ipynb') as f: old_text = f.read() # change %matplotlib to inline new_text = old_text.replace('%matplotlib tk', r'%matplotlib inline') # change directory so that paths still work -new_text = new_text.replace('../tests/data/', r'../../tests/data/') +new_text = new_text.replace('../tests/data/', r'../../../tests/data/') # Change title to 'How to use' new_text = new_text.replace('DefDAP Example notebook', r'How to use') new_text = new_text.replace('This notebook', r'These pages') # Write back to notebook -with open('howtouse.ipynb', "w") as f: +with open('userguide/howtouse.ipynb', "w") as f: f.write(new_text) nbsphinx_allow_errors = True @@ -58,7 +58,7 @@ # ----------------------------------------------------------------------------- project = 'DefDAP' -copyright = '2023, Mechanics of Microstructures Group at The University of Manchester' +copyright = '2026, Mechanics of Microstructures Group at The University of Manchester' author = 'Michael D. Atkinson, Rhys Thomas, João Quinta da Fonseca' def get_version(): @@ -94,7 +94,8 @@ def get_version(): 'sphinx.ext.autosummary', 'sphinx_autodoc_typehints', 'pydata_sphinx_theme', - 'nbsphinx' + 'nbsphinx', + 'sphinx_design' ] napoleon_use_param = True @@ -178,6 +179,7 @@ def get_version(): autodoc_member_order = 'bysource' autodoc_default_options = { 'inherited-members': True, + 'exclude_patterns': ['*base*'], } # ----------------------------------------------------------------------------- @@ -188,6 +190,7 @@ def get_version(): { 'path': '../../defdap', 'destination': 'defdap', + 'exclude_patterns': ['*base*'], 'separate_modules': True, 'module_first': False, 'automodule_options': {'members', 'show-inheritance', 'undoc-members'} diff --git a/docs/source/howtouse.ipynb b/docs/source/howtouse.ipynb deleted file mode 100644 index 2c3da31..0000000 --- a/docs/source/howtouse.ipynb +++ /dev/null @@ -1,1516 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "# How to use\n", - "\n", - "These pages will outline basic usage of DefDAP, including loading a DIC and EBSD map, linking them with homologous points and producing maps" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "## Load in packages" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "DefDAP is split into modules for processing EBSD (`defdap.ebsd`) and HRDIC (`defdap.hrdic`) data. There are also modules for manpulating orientations (`defdap.quat`) and creating custom figures (`defdap.plotting`) which is introduced later. We also import some of the usual suspects of the python scientific stack: `numpy` and `matplotlib`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "\n", - "import defdap.hrdic as hrdic\n", - "import defdap.ebsd as ebsd\n", - "from defdap.quat import Quat\n", - "\n", - "# try tk, qt, osx (if using mac) or notebook for interactive plots. If none work, use inline\n", - "%matplotlib widget" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "## Load in a HRDIC map" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "dic_filepath = \"../../tests/data/testDataDIC.txt\"\n", - "dic_map = hrdic.Map(dic_filepath)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "### Set the scale of the map\n", - "This is defined as the pixel size in the DIC pattern images, measured in microns per pixel." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "field_width = 20 # microns\n", - "num_pixels = 2048\n", - "dic_map.set_scale(field_width / num_pixels)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "### Plot the map with a scale bar" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "dic_map.plot_map('max_shear', vmin=0, vmax=0.10, plot_scale_bar=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can print out the names of all data currently available in the map. Try plotting different data, `plot_map` has a parameter `component` that is either an int `0` or tuple of ints `(0,1)` for tensor components or a named component such as `'norm'`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(dic_map.data)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "### Crop the map\n", - "HRDIC maps often contain spurious data at the edges which should be removed before performing any analysis. The crop is defined by the number of points to remove from each edge of the map. Note that the test data doesn not require cropping as it is a subset of a larger dataset." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "dic_map.set_crop(left=0, right=0, top=0, bottom=0)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "### Statistics\n", - "Some simple statistics such as the minimum, mean and maximum of the effective shear strain, E11 and E22 components can be printed." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "dic_map.print_stats_table(percentiles=[0, 50, 100], components=['max_shear', 'e'])" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "### Set the location of the DIC pattern images \n", - "The pattern images are used later to define the position of homologous material points. The path is relative to the directory set when loading in the map. The second parameter is the pixel binning factor of the image relative to the DIC sub-region size i.e. the number of pixels in the image across a single datapoint in the DIC map. We recommend binning the pattern images by the same factor as the DIC sub-region size, doing so enhances the contrast between microstructure features." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "# set the path of the pattern image, this is relative to the location of the DIC data file\n", - "dic_map.set_pattern(\"testDataPat.bmp\", 1)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "## Load in an EBSD map\n", - "Currently, OxfordBinary (a .crc and .cpr file pair), OxfordText (.ctf file), EdaxAng (.ang file) or PythonDict (Python dictionary) filetypes are supported. The crystal structure and slip systems are automatically loaded for each phase in the map. The orientation in the EBSD are converted to a quaternion representation so calculations can be applied later." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "ebsd_map = ebsd.Map(\"../../tests/data/testDataEBSD.cpr\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "A list of detected phases and crystal structures can be printed" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "for i, phase in enumerate(ebsd_map.phases):\n", - " print(i+1)\n", - " print(phase)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "A list of the slip planes, colours and slip directions can be printed" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "ebsd_map.phases[0].print_slip_systems()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "### Plot the EBSD map\n", - "Using an Euler colour mapping or inverse pole figure colouring with the sample reference direction passed as a vector." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "ebsd_map.plot_map('euler_angle', 'all_euler', plot_scale_bar=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "ebsd_map.plot_map('orientation', 'IPF_x', plot_scale_bar=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "A KAM map can also be plotted as follows" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "ebsd_map.plot_map('KAM', vmin=0, vmax=2*np.pi/180)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "### Detect grains in the EBSD\n", - "This is done in two stages: first bounaries are detected in the map as any point with a misorientation to a neighbouring point greater than a critical value (`boundDef` in degrees). A flood fill type algorithm is then applied to segment the map into grains, with any grains containining fewer than a critical number of pixels removed (`minGrainSize` in pixels). The data e.g. orientations associated with each grain are then stored (referenced strictly, the data isn't stored twice) in a grain object and a list of the grains is stored in the EBSD map (named `grainList`). This allows analysis routines to be applied to each grain in a map in turn." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "ebsd_map.data.generate('grain_boundaries', misori_tol=8)\n", - "ebsd_map.data.generate('grains', min_grain_size=10)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now when we print the available data there is a section for dervied data, this comes from data defined at the grain level. This derived data can be from different sources and later you will see data shared between linked HRDIC and EBSD maps." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(ebsd_map.data)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can use this derived data as with other map data to plots maps, statistics or directly access the data as a numpy array." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The Schmid factors for each grain can be calculated and plotted. The `slip_systems` argument can be specified, to only calculate the Schmid factor for certain planes, otherwise the maximum for all slip systems is calculated.\n", - "\n", - "Try changing the loading direction." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "ebsd_map.calc_average_grain_schmid_factors(\n", - " load_vector=np.array([1,0,0]), \n", - " slip_systems=None\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "ebsd_map.plot_average_grain_schmid_factors_map()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "### Single grain analysis\n", - "The `locate_grain` method allows interactive selection of a grain of intereset to apply any analysis to. Clicking on grains in the map will highlight the grain and print out the grain ID (position in the grain list) of the grain." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ebsd_map.locate_grain()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(f'Grain ID of last selected grain: {ebsd_map.sel_grain.grain_id}')" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "A built-in example is to calculate the average orientation of the grain and plot this orientation in a IPF" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "grain_id = 48\n", - "grain = ebsd_map[grain_id]\n", - "grain.calc_average_ori() # stored as a quaternion named grain.refOri\n", - "print(grain.ref_ori)\n", - "grain.plot_ref_ori(direction=[0, 0, 1])" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "The spread of orientations in a given grain can also be plotted on an IPF" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "plot = grain.plot_ori_spread(direction=np.array([0, 0, 1]), c='b', s=1, alpha=0.2)\n", - "grain.plot_ref_ori(direction=[0, 0, 1], c='k', plot=plot)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "The unit cell for the average grain orientation can also be ploted" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "grain.plot_unit_cell()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "Printing a list of the slip plane indices, angle of slip plane intersection with the screen (defined as counter-clockwise from upwards), colour defined for the slip plane and also the slip directions and corresponding Schmid factors, is also built in" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "grain.print_slip_traces()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "A second built-in example is to calcuate the grain misorientation, specifically the grain reference orientation deviation (GROD). This shows another feature of the `locate_grain` method, which stores the last selected grain in a variable called `sel_grain` in the EBSD map." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "if ebsd_map.sel_grain is None: \n", - " ebsd_map.sel_grain = ebsd_map[57]\n", - " \n", - "ebsd_map.sel_grain.build_mis_ori_list()\n", - "ebsd_map.sel_grain.plot_mis_ori(plot_scale_bar=True, vmin=0, vmax=3)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can also explore and visulaise all data available for a grain" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "grain_id = 40\n", - "grain = ebsd_map[grain_id]\n", - "print(grain.data)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "grain.plot_map('band_contrast')" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "### Multi grain analysis\n", - "Once an analysis routine has been prototyped for a single grain it can be applied to all the grains in a map using a loop over the grains and any results added to a list for use later. Of couse you could also apply to a smaller subset of grains as well." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "grain_av_oris = []\n", - "for grain in ebsd_map:\n", - " grain.calc_average_ori()\n", - " grain_av_oris.append(grain.ref_ori)\n", - "\n", - "# Plot all the grain orientations in the map\n", - "Quat.plot_ipf(grain_av_oris, [0, 0, 1], ebsd_map.crystal_sym, marker='o', s=10)\n", - "plt.tight_layout()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "Some common grain analysis routines are built into the EBSD map object, including:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "ebsd_map.calc_grain_av_oris()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "ebsd_map.calc_grain_mis_ori()\n", - "ebsd_map.plot_mis_ori_map(vmin=0, vmax=5, plot_gbs=True, plot_scale_bar=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "There are also methods for plotting GND density, phases and boundaries. All of the plotting functions in DefDAP use the same parameters to modify the plot, examples seen so far are `plot_gbs`, `plotScaleBar`, `vmin`, `vmax`." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "## Linking the HRDIC and EBSD\n", - "### Define homologous points\n", - "To register the two datasets, homologous points (points at the same material location) within each map are used to estimate a transformation between the two frames the data are defined in. The homologous points are selected manually using an interactive tool within DefDAP. To select homologous call the method `set_homog_point` on each of the data maps, which will open a plot window with a button labelled 'save point' in the bottom right. You select a point by right clicking on the map, adjust the position with the arrow and accept the point by with the save point button. Then select the same location in the other map. Note that as we set the location of the pattern image for the HRDIC map that the points can be selected on the pattern image rather than the strain data.\n", - "\n", - "Select 3-4 homologous points in spread over each map in the same order." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "dic_map.set_homog_point(map_name=\"pattern\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "ebsd_map.set_homog_point()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "The points are stored as a list of tuples `(x, y)` in each of the maps. This means the points can be set from previous values." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "dic_map.frame.homog_points" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "ebsd_map.frame.homog_points" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "Here are some example homologous points for this data, after setting these by running the cells below you can view the locations in the maps by running the `set_homog_point` methods (above) again. These will not be in the correct location if the crop values of the HRDIC map have been changed from 0." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "dic_map.frame.homog_points = [\n", - " (36, 72), \n", - " (279, 27), \n", - " (162, 174), \n", - " (60, 157)\n", - "]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "ebsd_map.frame.homog_points = [\n", - " (68, 95), \n", - " (308, 45), \n", - " (191, 187), \n", - " (89, 174)\n", - "]" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "### Link the maps\n", - "Finally the two data maps are linked. The type of transform between the two frames can be affine, projective, polynomial.\n", - "\n", - "Try the different projections and see if the make any difference to the percieved transoformation for the points you selected." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "dic_map.link_ebsd_map(ebsd_map, transform_type=\"affine\")\n", - "# dic_map.link_ebsd_map(ebsd_map, transform_type=\"projective\")\n", - "# dic_map.link_ebsd_map(ebsd_map, transform_type=\"polynomial\", order=2)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "### Show the transformation" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "from skimage import transform as tf\n", - "\n", - "data = np.zeros((2000, 2000), dtype=float)\n", - "data[500:1500, 500:1500] = 1.\n", - "transform = dic_map.experiment.get_frame_transform(dic_map.frame, ebsd_map.frame)\n", - "dataWarped = tf.warp(data, transform)\n", - "\n", - "fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8,4))\n", - "ax1.set_title('Reference')\n", - "ax1.imshow(data)\n", - "ax2.set_title('Transformed')\n", - "ax2.imshow(dataWarped)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "### Segment into grains\n", - "The HRDIC map can now be segmented into grains using the grain boundaries detected in the EBSD map. Analysis rountines can then be applied to individual grain, as with the EBSD grains. The grain finding process will also attempt to link the grains between the EBSD and HRDIC and each grain in the HRDIC has a reference (`ebsdGrain`) to the corrosponding grain in the EBSD map." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "dic_map.data.generate('grains', min_grain_size=10)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dic_map.plot_map(\n", - " 'max_shear', vmin=0, vmax=0.10, \n", - " plot_scale_bar=True, plot_gbs='pixel'\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "Now, a grain can also be selected interactively in the DIC map, in the same way a grain can be selected from an EBSD map. If `display_grain` is set to true, then a plot shows the map segmented for the grain with coloured lines to display the slip trace direction of the set slip systems (see colours `grain.print_slip_traces()`) and black lines marking direction of slip bands detected in the grain." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "dic_map.locate_grain(display_grain=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "## Plotting examples" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "Some of the plotting features are shown in examples below." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Built-in plots\n", - "These are the plotting functions you have been using so far, run by calling methods of the data objects. Each method returns a plot object that can be used to modify the plot after it has been created." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "plot = dic_map.plot_map(\n", - " 'max_shear', vmin=0, vmax=0.10, \n", - " plot_scale_bar=True, plot_gbs='line'\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "plot = ebsd_map.plot_map(\n", - " 'euler_angle', component='all_euler',\n", - " plot_scale_bar=True, plot_gbs=True,\n", - " highlight_grains=[10, 20, 45], highlight_alpha=0.9, highlight_colours=['r']\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "dic_grain_id = 42\n", - "dic_grain = dic_map[dic_grain_id]\n", - "\n", - "plot = dic_grain.plot_map(\n", - " 'max_shear', plot_scale_bar=True, \n", - " plot_slip_traces=True, plot_slip_bands=True\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "### IPF plotting" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "This plot will show the positions of selected grains in an IPF pole figure, with the marker size representing grain area and mean effective shear strain." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "# For all grains in the DIC map\n", - "\n", - "# Make an array of quaternions\n", - "grain_oris = [grain.ebsd_grain.ref_ori for grain in dic_map]\n", - "\n", - "# Make an array of grain area\n", - "grain_areas = np.array([len(grain) for grain in dic_map]) * dic_map.scale**2\n", - "\n", - "# Scaling the grain area, so that the maximum size of a marker is 200 points^2\n", - "grain_area_scaling = 200. / grain_areas.max()\n", - "\n", - "# Make an array of mean effective shear strain\n", - "grain_strains = [np.array(grain.data.max_shear).mean() for grain in dic_map]\n", - "\n", - "plot = Quat.plot_ipf(\n", - " grain_oris, direction=[1,0,0], sym_group='cubic', \n", - " marker='o', s=grain_areas*grain_area_scaling, \n", - " c=grain_strains, vmin=0, vmax=0.018, cmap='viridis'\n", - ")\n", - "plot.add_colour_bar(label='Mean effective shear strain')\n", - "plot.add_legend(scaling=grain_area_scaling)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "# For selected grains in the DIC map\n", - "\n", - "# Select grains from the DIC map\n", - "dic_grain_ids = [\n", - " 2, 5, 7, 9, 15, 17, 18, 23, 29, 32, \n", - " 33, 37, 40, 42, 49, 50, 51, 54, 58, 60\n", - "]\n", - "\n", - "# Make an array of quaternions\n", - "grain_oris = np.array([\n", - " dic_map[grain_id].ebsd_grain.ref_ori for grain_id in dic_grain_ids\n", - "])\n", - "\n", - "# Make an array of grain area\n", - "grain_areas = np.array([\n", - " len(dic_map[grain_id]) for grain_id in dic_grain_ids\n", - "]) * dic_map.scale**2\n", - "\n", - "# Scaling the grain area, so that the maximum size of a marker is 200 points^2\n", - "grain_area_scaling = 200. / grain_areas.max()\n", - "\n", - "# Make an array of mean effective shear strain\n", - "grain_strains = np.array([\n", - " np.mean(dic_map[grain].data.max_shear) for grain in dic_grain_ids\n", - "])\n", - "\n", - "plot = Quat.plot_ipf(\n", - " grain_oris, direction=[1,0,0], sym_group='cubic', \n", - " marker='o', s=grain_areas*grain_area_scaling, \n", - " c=grain_strains, vmin=0, vmax=0.018, cmap='viridis'\n", - ")\n", - "plot.add_colour_bar(label='Mean effective shear strain')\n", - "plot.add_legend(scaling=grain_area_scaling)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "### Create your own" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "from defdap.plotting import MapPlot, GrainPlot, HistPlot" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "map_data = dic_map.data['e'][0,0]\n", - "map_data = dic_map.crop(map_data)\n", - "\n", - "plot = MapPlot.create(\n", - " dic_map, map_data,\n", - " vmin=-0.1, vmax=0.1, plot_colour_bar=True, cmap=\"seismic\",\n", - " plot_gbs=True, dilate_boundaries=True, boundary_colour='black'\n", - ")\n", - "\n", - "plot.add_scale_bar()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "### Functions for grain averaging and grain segmentation" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "plot = dic_map.plot_grain_data_map(\n", - " map_data,\n", - " vmin=-0.06, vmax=0.06, plot_colour_bar=True,\n", - " cmap=\"seismic\", clabel=\"Axial strain ($e_11$)\",\n", - " plot_scale_bar=True\n", - ")\n", - "\n", - "plot.add_grain_boundaries(dilate=True, colour=\"white\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "plot = dic_map.plot_grain_data_ipf(\n", - " np.array((1,0,0)), map_data, marker='o', \n", - " vmin=-0.06, vmax=0.06, plot_colour_bar=True, \n", - " clabel=\"Axial strain ($e_11$)\", cmap=\"seismic\",\n", - ")\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "dic_grain_id = 42\n", - "dic_grain = dic_map[dic_grain_id]\n", - "\n", - "plot = dic_grain.plot_grain_data(\n", - " map_data, \n", - " vmin=-0.1, vmax=0.1, plot_colour_bar=True, \n", - " clabel=\"Axial strain ($e_11$)\", cmap=\"seismic\",\n", - " plot_scale_bar=True\n", - ")\n", - "\n", - "plot.add_slip_traces()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "### Composite plots" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "By utilising some additional functionality within matplotlib, composite plots can be produced." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "from matplotlib import gridspec" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "# Create a figure with 3 sets of axes\n", - "fig = plt.figure(figsize=(8, 4))\n", - "gs = gridspec.GridSpec(2, 2, width_ratios=[3, 1],\n", - " wspace=0.15, hspace=0.15, \n", - " left=0.02, right=0.98,\n", - " bottom=0.12, top=0.95) \n", - "ax0 = plt.subplot(gs[:, 0])\n", - "ax1 = plt.subplot(gs[0, 1])\n", - "ax2 = plt.subplot(gs[1, 1])\n", - "\n", - "\n", - "# add a strain map\n", - "plot0 = dic_map.plot_map(\n", - " map_name='max_shear',\n", - " ax=ax0, fig=fig, \n", - " vmin=0, vmax=0.08, plot_scale_bar=True, \n", - " plot_gbs=True, dilate_boundaries=True\n", - ")\n", - "\n", - "# add an IPF of grain orientations\n", - "dic_oris = []\n", - "for grain in dic_map:\n", - " if len(grain) > 20:\n", - " dic_oris.append(grain.ref_ori)\n", - "plot1 = Quat.plot_ipf(\n", - " dic_oris, np.array((1,0,0)), 'cubic', \n", - " ax=ax1, fig=fig, s=10\n", - ")\n", - "\n", - "# add histrogram of strain values\n", - "plot2 = HistPlot.create(\n", - " dic_map.crop(dic_map.data.max_shear),\n", - " ax=ax2, fig=fig, marker='o', markersize=2,\n", - " axes_type=\"logy\", bins=50, range=(0,0.06)\n", - ")\n", - "plot2.ax.set_xlabel(\"Effective shear strain\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "Figures can be saved to raster (png, jpg, ..) and vector formats (eps, svg), the format is guessed from the file extension given. The last displayed figure can be saved using:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "#plt.savefig(\"test_save_fig.png\", dpi=200)\n", - "#plt.savefig(\"test_save_fig.eps\", dpi=200)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "fig, ((ax0, ax1), (ax2, ax3)) = plt.subplots(2, 2, figsize=(8, 6))\n", - "\n", - "dic_grain_id = 42\n", - "dic_grain = dic_map[dic_grain_id]\n", - "\n", - "# add a strain map\n", - "plot0 = dic_grain.plot_map(\n", - " 'max_shear',\n", - " ax=ax0, fig=fig, \n", - " vmin=0, vmax=0.08, plot_scale_bar=True,\n", - " plot_slip_traces=True\n", - ")\n", - "\n", - "\n", - "# add a misorientation\n", - "ebsd_grain = dic_grain.ebsd_grain\n", - "plot1 = ebsd_grain.plot_mis_ori(component=0, ax=ax1, fig=fig, vmin=0, vmax=1, clabel=\"GROD\", plot_scale_bar=True)\n", - "\n", - "\n", - "# add an IPF\n", - "plot2 = ebsd_grain.plot_ori_spread(\n", - " direction=np.array((1,0,0)), c='b', s=1, alpha=0.2,\n", - " ax=ax2, fig=fig\n", - ")\n", - "ebsd_grain.plot_ref_ori(\n", - " direction=np.array((1,0,0)), c='k', s=100, plot=plot2\n", - ")\n", - "\n", - "# add histrogram of strain values\n", - "plot3 = HistPlot.create(\n", - " dic_map.crop(dic_map.data.max_shear),\n", - " ax=ax3, fig=fig,\n", - " axes_type=\"logy\", bins=50, range=(0,0.06))\n", - " \n", - "plot3.ax.set_xlabel(\"Effective shear strain\")\n", - "\n", - "\n", - "plt.tight_layout()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Exercise" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Calculate the z component of rigid body rotation, $$ \\omega_3 = \\frac{F_{12}-F_{21}}{2}, $$ for the HRDIC map. (HINT: the deformation gradient (F) can be accessed as numpy array with `dic_map.data.f`)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Plot $\\omega_3$ as a map with grain boundaries and a scale bar. Choose an appropiate colourmap (see https://matplotlib.org/stable/gallery/color/colormap_reference.html) and scale. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Make a figure containing a map of $\\omega_3$ and a map of misorientaion for a single grain in the DIC map." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Calculate grain average $\\omega_3$ and then plot these on a IPF." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - }, - "tags": [] - }, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "defdap", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.13" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/docs/source/index.rst b/docs/source/index.rst index 165e808..6f53a82 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -27,8 +27,7 @@ Contents .. toctree:: :maxdepth: 1 - installation - howtouse + userguide contributing papers modules diff --git a/docs/source/installation.rst b/docs/source/installation.rst deleted file mode 100644 index 8ffc856..0000000 --- a/docs/source/installation.rst +++ /dev/null @@ -1,14 +0,0 @@ -Installation -=========================== - -The defdap package is available from the Python Package Index (PyPI) and can be installed by executing the following command: :: - - pip install defdap - -The prerequisite packages should be installed automatically by pip, but if you want to manually install them, using the conda package manager for example, then run the following command: :: - - conda install scipy numpy matplotlib scikit-image scikit-learn pandas networkx - -If you are doing development work on the scripts, first clone the repository from GitHub. The package can then be installed in editable mode using pip with flag -e to create a "linked" .egg module, which means the module is loaded from the directory at runtime. This avoids having to reinstall every time changes are made. Run the following command from the root of the cloned repository: :: - - pip install -e . \ No newline at end of file diff --git a/docs/source/userguide.rst b/docs/source/userguide.rst new file mode 100644 index 0000000..b88ed9a --- /dev/null +++ b/docs/source/userguide.rst @@ -0,0 +1,10 @@ +User Guide +================= + +Guide on how to install and use DefDAP. + +.. toctree:: + :glob: + + userguide/installation + userguide/howtouse \ No newline at end of file diff --git a/docs/source/userguide/installation.rst b/docs/source/userguide/installation.rst new file mode 100644 index 0000000..0e964b8 --- /dev/null +++ b/docs/source/userguide/installation.rst @@ -0,0 +1,33 @@ +Installation +=========================== + +For most users, only looking to use DefDAP, the easiest installation method is via the Python Package Index (PyPI). +If you are doing development work on DefDAP, then we reccomend you first clone the repository from GitHub, then install the package in editable mode. + +.. tab-set:: + + .. tab-item:: PyPi + + The latest released version of DefDAP can be installed from PyPI by executing the following command: :: + + pip install defdap + + This will automatically install any required dependencies. + + .. tab-item:: Clone repository + + First, clone the repository from GitHub. You can do this multiple ways: + + - (Recomended for new users) Use the GitHub Desktop GUI (https://desktop.github.com/download/). + - If you are comfortable with the Git version control system, you can clone the repository with the command line. + - (Not reccomended) Download https://github/com/MechMicroMan/DefDAP as a ZIP file, then extract the contents to a folder on your computer. + + Using the first two methods mean that you can easily pull down updates from the main repository in the future + and you can also push any changes you make to your own fork of the repository. + + After cloning the repository, the package can then be installed in editable mode using pip with flag -e. + This avoids having to reinstall every time changes are made. + Run the following command from the root of the cloned repository: :: + + pip install -e . + From e12ad97110e017d9222d47dd025f7f687a7edb30 Mon Sep 17 00:00:00 2001 From: Rhys Thomas Date: Thu, 5 Feb 2026 17:25:34 +0000 Subject: [PATCH 03/18] chore: update docs --- docs/source/conf.py | 5 +---- docs/source/contributing.rst | 3 +-- docs/source/index.rst | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 3e7d575..85d2610 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -45,10 +45,7 @@ nbsphinx_kernel_name = 'python3' nbsphinx_prolog = """ -This page was built from the example_notebook Jupyter notebook available on `Github `_ - -.. image:: https://mybinder.org/badge_logo.svg - :target: https://mybinder.org/v2/gh/MechMicroMan/DefDAP/master?filepath=example_notebook.ipynb +This page was built from the example_notebook Jupyter notebook available on `Github `_ ---- """ diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst index 3e11037..7757628 100644 --- a/docs/source/contributing.rst +++ b/docs/source/contributing.rst @@ -6,8 +6,7 @@ We welcome any improvements made to this package, but please follow the guidelin Coding style ----------------- -In this project we (try) to follow the PEP8 standard for formatting python code with 2 notable exceptions: -- Function and variable names are in mixed case e.g myFirstFunction +In this project we (try) to follow the PEP8 standard for formatting python code with a notable exceptions: - The 79 character line length limit is seen more as a guideline than rule. Code is split across lines where possible and to improve readability. Line length should never excede 119 characters. All documentation should be split to lines of less than 73 characters. Repository Structure diff --git a/docs/source/index.rst b/docs/source/index.rst index 6f53a82..5269f58 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -16,7 +16,7 @@ If this software is used in the preparation of published work please cite: `Atkinson, Michael D, Thomas, Rhys, Harte, Allan, Crowther, Peter, & Quinta da Fonseca, João. (2020, May 4). DefDAP: Deformation Data Analysis in Python - v0.92 (Version 0.92). Zenodo. `_ -Licenses +License ========== This software is distributed under Apache License 2.0. For more details see the :doc:`License` page. From c14f7e13f86d170b8b779478d4a17254cc9ea53c Mon Sep 17 00:00:00 2001 From: Rhys Thomas Date: Fri, 6 Feb 2026 11:41:37 +0000 Subject: [PATCH 04/18] chore: further docs updates --- .gitignore | 1 + docs/source/conf.py | 8 +- docs/source/index.rst | 75 +++++++++++++---- docs/source/license.rst | 5 -- docs/source/modules.rst | 2 +- docs/source/userguide.rst | 5 +- docs/source/userguide/hrdic.rst | 144 ++++++++++++++++++++++++++++++++ 7 files changed, 210 insertions(+), 30 deletions(-) delete mode 100644 docs/source/license.rst create mode 100644 docs/source/userguide/hrdic.rst diff --git a/.gitignore b/.gitignore index c4895be..eb43bb4 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ venv/ docs/build # Sphinx autogenerated API documentation docs/source/defdap +docs/source/userguide/howtouse.ipynb # Pip install DefDAP.egg-info/ diff --git a/docs/source/conf.py b/docs/source/conf.py index 85d2610..8694c88 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -33,7 +33,7 @@ new_text = new_text.replace('../tests/data/', r'../../../tests/data/') # Change title to 'How to use' -new_text = new_text.replace('DefDAP Example notebook', r'How to use') +new_text = new_text.replace('DefDAP Example notebook', r'Example notebook') new_text = new_text.replace('This notebook', r'These pages') # Write back to notebook @@ -88,7 +88,6 @@ def get_version(): 'sphinx.ext.githubpages', 'sphinx.ext.napoleon', 'sphinx.ext.intersphinx', - 'sphinx.ext.autosummary', 'sphinx_autodoc_typehints', 'pydata_sphinx_theme', 'nbsphinx', @@ -194,11 +193,6 @@ def get_version(): } ] -# ----------------------------------------------------------------------------- -# Autosummary -# ----------------------------------------------------------------------------- - -autosummary_generate = True # ----------------------------------------------------------------------------- # Intersphinx diff --git a/docs/source/index.rst b/docs/source/index.rst index 5269f58..3b7c5ac 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,34 +1,79 @@ DefDAP Documentation -====================== +=================================================== .. figure:: ./_static/logo.png - :scale: 100 % + :scale: 80 % + :align: left :alt: DefDAP -DefDAP is a python library for correlating EBSD and HRDIC data. It was developed by Michael Atkinson and Rhys Thomas during their PhDs at the Univeristy of Manchester. -This documentation gives information about the latest version of DefDAP. +.. |pypi| image:: https://img.shields.io/pypi/v/defdap + :target: https://pypi.org/project/defdap + :alt: PyPI version -Citation -=========== +.. |python| image:: https://img.shields.io/pypi/pyversions/defdap + :target: https://pypi.org/project/defdap + :alt: Supported python versions -If this software is used in the preparation of published work please cite: +.. |docs| image:: https://readthedocs.org/projects/defdap/badge/?version=latest + :target: https://defdap.readthedocs.io/en/latest/?badge=latest + :alt: Documentation status -`Atkinson, Michael D, Thomas, Rhys, Harte, Allan, Crowther, Peter, & Quinta da Fonseca, João. (2020, May 4). DefDAP: Deformation Data Analysis in Python - v0.92 (Version 0.92). Zenodo. `_ +.. |doi| image:: https://zenodo.org/badge/DOI/10.5281/zenodo.3688096.svg + :target: https://doi.org/10.5281/zenodo.368809 + :alt: DOI + +DefDAP is a python library for correlating EBSD and HRDIC data, developed by Michael Atkinson and Rhys Thomas. + + +|pypi| |python| |docs| |doi| + +.. grid:: 4 + :gutter: 3 + + .. grid-item-card:: User guide + :link: userguide + :link-type: doc + :text-align: center + :shadow: lg + + :octicon:`gear;5em` -License -========== + .. grid-item-card:: Contributing + :link: contributing + :link-type: doc + :text-align: center + :shadow: lg -This software is distributed under Apache License 2.0. For more details see the :doc:`License` page. + :octicon:`workflow;5em` + + .. grid-item-card:: Papers + :link: papers + :link-type: doc + :text-align: center + :shadow: lg + + :octicon:`book;5em` + + .. grid-item-card:: API Reference + :link: modules + :link-type: doc + :text-align: center + :shadow: lg + + :octicon:`file-code;5em` + +If this software is used in the preparation of published work please cite: + +`Atkinson, Michael D, Thomas, Rhys, Harte, Allan, Crowther, Peter, & Quinta da Fonseca, João. (2020, May 4). DefDAP: Deformation Data Analysis in Python - v0.92 (Version 0.92). Zenodo. `_ -Contents -========== +This software is distributed under Apache License 2.0. .. toctree:: + :hidden: :maxdepth: 1 userguide contributing papers - modules - license \ No newline at end of file + modules \ No newline at end of file diff --git a/docs/source/license.rst b/docs/source/license.rst deleted file mode 100644 index a8206ea..0000000 --- a/docs/source/license.rst +++ /dev/null @@ -1,5 +0,0 @@ -License -=========================== - -.. include:: ../../LICENSE - :literal: \ No newline at end of file diff --git a/docs/source/modules.rst b/docs/source/modules.rst index c788edd..025104a 100644 --- a/docs/source/modules.rst +++ b/docs/source/modules.rst @@ -1,4 +1,4 @@ -API Documentation +API reference ================= Information on specific functions, classes, and methods. diff --git a/docs/source/userguide.rst b/docs/source/userguide.rst index b88ed9a..6bc0a7d 100644 --- a/docs/source/userguide.rst +++ b/docs/source/userguide.rst @@ -1,10 +1,11 @@ User Guide ================= -Guide on how to install and use DefDAP. +This is a narrative guide on how to install DefDAP and hwo to use the various classes. .. toctree:: - :glob: + :maxdepth: 1 userguide/installation + userguide/hrdic userguide/howtouse \ No newline at end of file diff --git a/docs/source/userguide/hrdic.rst b/docs/source/userguide/hrdic.rst new file mode 100644 index 0000000..33e000f --- /dev/null +++ b/docs/source/userguide/hrdic.rst @@ -0,0 +1,144 @@ +HRDIC Map (`defdap.hrdic.Map`) +=================================================== + +The HRDIC class in DefDAP provides tools for loading, processing, and analyzing DIC data. + +Supported Data Formats +---------------------- + +DefDAP supports loading data from various commercial and open-source software packages: + +.. list-table:: + :header-rows: 1 + :widths: 18 12 50 + + * - ``data_type`` + - Extension + - Description + * - ``davis`` + - ``.txt`` + - LaVision DaVis text files + * - ``openpivbinary`` + - ``.npz`` + - OpenPIV binary files + * - ``openpivtext`` + - ``.txt`` + - OpenPIV text files + * - ``pyvale`` + - ``.csv`` + - PyVale text files + +.. note:: + + Only files from version 8 of DaVis are currently supported. + In David, ensure the decimal point chartacter in exported files is set to dot, + by going to Project → Global Options → Export → Decimal Point Character and selecting 'Dot' + +Loading HRDIC Data +------------------ + +HRDIC data can be loaded (for example, from a LaVision DaVis test file) as follows: + +.. code-block:: python + + import defdap.hrdic as hrdic + + # Load HRDIC map from DaVis format + dic_map = hrdic.Map("path/to/dic_data.txt", data_type="davis") + +Data Structure +-------------- + +The HRDIC Map stores several key data structures as a :class:`defdap.utils.Datastore` object under the ``data`` attribute. +You can print a list of all the attributed stored ``print(dic_map.data)`` and a more detailed summmary is below: + +.. list-table:: + :header-rows: 1 + :widths: 8 24 + + * - Attribute + - Description + * - ``coordinate`` + - pixel coordinate grid for the DIC map. + * - ``displacement`` + - displacement field arrays (first element is x, second element is y). + * - ``e`` + - deformation gradient components (e.g. ``Exx`` is element [0][0] ``Eyy`` is element [1][1]). + * - ``f`` + - green strain components (e.g. ``Fxx`` is element [0][0] ``Fyy`` is element [1][1]). + * - ``max_shear`` + - maximum shear strain field. + * - ``pattern`` + - image/pattern data associated with DIC, set this with :class:`defdap.hridic.set_pattern`. + * - ``mask`` + - validity mask for data points. + * - ``proxigram`` + - ??? + + +- **Grains**: ``grains`` - grain/region objects derived from segmentation +- **Phase boundaries**: ``phase_boundaries`` - phase boundary features +- **Grain boundaries**: ``grain_boundaries`` - grain boundary features + +Setting and plotting pattern +----------------------------- + +The undeformed pattern image, from which the DIC data was derived can contain microstructural information which will be useful to link the HRDIC to EBSD data later. +First, scale the image down, ideally a factor of the interregation window size for the DIC data. +For example, if the DIC interregation window size was (16 x 16) pixels, then the pattern image should be scaled down by a factor of 16. +The path can then set with :class:`defdap.hridic.set_pattern`, where the second argument is the scaling factor of the pattern image relative to the DIC interregation window size. + +.. code-block:: python + + dic_map.set_pattern("pattern_image.bmp", 1) + +.. note:: + + Ensure this is the same image as the one used to generate the DIC data, + otherwise the pattern will not be correctly aligned with the DIC data and the subsequent correlation with EBSD data will be incorrect. + +.. note:: + + DefDAP calculates the expected size of the pattern image based on the size of the DIC map and the scaling factor, + so if the pattern image is not the expected size, an error will be raised. + +To plot the pattern image, use the :class:`defdap.hridic.plot_map` method with the argument 'pattern' + + dic_map.plot_map("pattern") + +Setting scale +------------------ + +HRDIC data is stored in a pixel-based coordinate system. +To set the scale of the map so that a scale bar in microns is plotted when the map is plotted: + +.. code-block:: python + + # Set the scale (micron per pixel) + dic_map.set_scale(scale=2.5) + +Setting crop +------------------ + +There are normally some anomalous points near the edges of a DIC map, +so it is often desirable to crop the map to a region of interest, using this command: + +.. code-block:: python + + # Crop to region of interest + dic_map.crop(left=100, right=100, top=100, bottom=100) + +Plotting and Visualization +--------------------------- + +To plot a maximum shear strain map, with scale bar: + +.. code-block:: python + + # Plot strain field + dic_map.plot_map('max_shear', vmin=0, vmax=0.1, plot_scale_bar=True) + +Further Reading +--------------- + +For detailed API documentation, see :doc:`../defdap/defdap.hrdic`. \ No newline at end of file From baadc639bef05e2c9aa1f0e690d6701c40d5cbc2 Mon Sep 17 00:00:00 2001 From: Rhys Thomas Date: Fri, 6 Feb 2026 13:54:06 +0000 Subject: [PATCH 05/18] chose: fix docs formatting bugs --- defdap/ebsd.py | 48 ++++++++++++++++++++++++------------------ defdap/file_writers.py | 2 +- defdap/hrdic.py | 8 ++++--- defdap/inspector.py | 2 +- defdap/quat.py | 9 ++++++-- defdap/utils.py | 19 +++++++++-------- 6 files changed, 51 insertions(+), 37 deletions(-) diff --git a/defdap/ebsd.py b/defdap/ebsd.py index 56ee7ee..cbd3a6f 100755 --- a/defdap/ebsd.py +++ b/defdap/ebsd.py @@ -50,31 +50,37 @@ class Map(base.Map): origin : tuple(int) Map origin (x, y). Used by linker class where origin is a homologue point of the maps. - data : defdap.utils.Datastore + Data storage object containing: + Must contain after loading data (maps): - phase : numpy.ndarray - 1-based, 0 is non-indexed points - euler_angle : numpy.ndarray - stored as (3, y_dim, x_dim) in radians + + - phase : numpy.ndarray + 1-based, 0 is non-indexed points + - euler_angle : numpy.ndarray + stored as (3, y_dim, x_dim) in radians + Generated data: - orientation : numpy.ndarray of defdap.quat.Quat - Quaterion for each point of map. Shape (y_dim, x_dim). - grain_boundaries : BoundarySet - phase_boundaries : BoundarySet - grains : numpy.ndarray of int - Map of grains. Grain numbers start at 1 here but everywhere else - grainID starts at 0. Regions that are smaller than the minimum - grain size are given value -2. Remnant boundary points are -1. - KAM : numpy.ndarray - Kernal average misorientaion map. - GND : numpy.ndarray - GND scalar map. - Nye_tensor : numpy.ndarray - 3x3 Nye tensor at each point. + + - orientation : numpy.ndarray of defdap.quat.Quat + Quaterion for each point of map. Shape (y_dim, x_dim). + - grain_boundaries : BoundarySet + - phase_boundaries : BoundarySet + - grains : numpy.ndarray of int + Map of grains. Grain numbers start at 1 here but everywhere else + grainID starts at 0. Regions that are smaller than the minimum + grain size are given value -2. Remnant boundary points are -1. + - KAM : numpy.ndarray + Kernal average misorientaion map. + - GND : numpy.ndarray + GND scalar map. + - Nye_tensor : numpy.ndarray + 3x3 Nye tensor at each point. + Derived data: - grain_data_to_map : numpy.ndarray - Grain list data to map data from all grains + + - grain_data_to_map : numpy.ndarray + Grain list data to map data from all grains """ MAPNAME = 'ebsd' diff --git a/defdap/file_writers.py b/defdap/file_writers.py index d7c85a6..ea1d440 100644 --- a/defdap/file_writers.py +++ b/defdap/file_writers.py @@ -37,7 +37,7 @@ def __init__(self) -> None: self.data_format = None @staticmethod - def get_writer(datatype: str) -> "Type[EBSDDataLoader]": + def get_writer(datatype: str) -> "Type[EBSDDataWriter]": if datatype is None: datatype = "OxfordText" diff --git a/defdap/hrdic.py b/defdap/hrdic.py index edf939c..064bcea 100755 --- a/defdap/hrdic.py +++ b/defdap/hrdic.py @@ -56,7 +56,7 @@ class Map(base.Map): ydim : int Size of map along y (from header). shape : tuple - Size of map (after cropping, like *Dim). + Size of map (after cropping, like ``*Dim``). corrVal : numpy.ndarray Correlation value. ebsd_map : defdap.ebsd.Map @@ -740,15 +740,17 @@ class Grain(base.Grain): EBSD map that this DIC grain belongs to. points_list : numpy.ndarray Start and end points for lines drawn using defdap.inspector.GrainInspector. - groups_list : + groups_list : list Groups, angles and slip systems detected for lines drawn using defdap.inspector.GrainInspector. - data : defdap.utils.Datastore Must contain after creating: + point : list of tuples (x, y) in cropped map + Generated data: + None Derived data: Map data to list data from the map the grain is part of diff --git a/defdap/inspector.py b/defdap/inspector.py index 8a77a39..17e70ba 100644 --- a/defdap/inspector.py +++ b/defdap/inspector.py @@ -166,7 +166,7 @@ def save_line(self, self.redraw_line() def group_lines(self, - grain: 'defdap.hrdic.Grain' = None): + grain: 'hrdic.Grain' = None): """ Group the lines drawn in the current grain item using a mean shift algorithm, save the average angle and then detect the active slip planes. diff --git a/defdap/quat.py b/defdap/quat.py index d3a8d00..1af9a6c 100755 --- a/defdap/quat.py +++ b/defdap/quat.py @@ -18,7 +18,12 @@ from defdap import plotting from defdap import defaults -from typing import Union, Tuple, List, Optional +from typing import Union, Tuple, List, Optional, TYPE_CHECKING + +if TYPE_CHECKING: + import matplotlib.figure + import matplotlib.axes + import defdap.crystal as crystal class Quat(object): @@ -513,7 +518,7 @@ def plot_ipf( def plot_unit_cell( self, - crystal_structure: 'defdap.crystal.CrystalStructure', + crystal_structure: 'crystal.CrystalStructure', OI: Optional[bool] = True, plot: Optional['plotting.CrystalPlot'] = None, fig: Optional['matplotlib.figure.Figure'] = None, diff --git a/defdap/utils.py b/defdap/utils.py index 4e4bf6e..4022d69 100644 --- a/defdap/utils.py +++ b/defdap/utils.py @@ -79,15 +79,16 @@ class Datastore(object): Storage for data and metadata, keyed by data name. Each item is a dict with at least a `data` key, all other items are metadata, possibly including: - type : str - Type of data stored: - `map` - at least a 2-axis array, trailing axes are spatial - order : int - Tensor order of the data - unit : str - Measurement unit the data is stored in - plot_params : dict - Dictionary of the default parameters used to plot + + type : str + Type of data stored: + `map` - at least a 2-axis array, trailing axes are spatial + order : int + Tensor order of the data + unit : str + Measurement unit the data is stored in + plot_params : dict + Dictionary of the default parameters used to plot _generators: dict Methods to generate derived data, keyed by tuple of data names that the method produces. From cf5b3ef2ca6c2f49ed1b6cbb45e198c207601dc0 Mon Sep 17 00:00:00 2001 From: Rhys Thomas Date: Fri, 6 Feb 2026 15:01:46 +0000 Subject: [PATCH 06/18] chore: update docs --- docs/source/_templates/layout.html | 6 ------ docs/source/modules.rst | 1 + 2 files changed, 1 insertion(+), 6 deletions(-) delete mode 100644 docs/source/_templates/layout.html diff --git a/docs/source/_templates/layout.html b/docs/source/_templates/layout.html deleted file mode 100644 index c381a6b..0000000 --- a/docs/source/_templates/layout.html +++ /dev/null @@ -1,6 +0,0 @@ -{% extends "!layout.html" %} - - {% block menu %} - {{ super() }} - Index - {% endblock %} \ No newline at end of file diff --git a/docs/source/modules.rst b/docs/source/modules.rst index 025104a..2c1b63a 100644 --- a/docs/source/modules.rst +++ b/docs/source/modules.rst @@ -5,5 +5,6 @@ Information on specific functions, classes, and methods. .. toctree:: :glob: + :maxdepth: 2 defdap/* \ No newline at end of file From f860bb4931b5299b1ac3127196bd8f35d6ad3cf4 Mon Sep 17 00:00:00 2001 From: Rhys Thomas Date: Fri, 6 Feb 2026 16:27:44 +0000 Subject: [PATCH 07/18] chore: Update crystal docstrings --- defdap/crystal.py | 115 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 81 insertions(+), 34 deletions(-) diff --git a/defdap/crystal.py b/defdap/crystal.py index b38c418..afcce9e 100755 --- a/defdap/crystal.py +++ b/defdap/crystal.py @@ -23,18 +23,46 @@ class Phase(object): + """ + Represents a crystallographic phase. + + Stores phase information including crystal structure, lattice parameters, + and associated slip systems. + + Attributes + ---------- + name : str + Name of the phase. + laue_group : int + Laue group number. + spaceGroup : int + Space group number. + lattice_params : tuple of float + Lattice parameters (a, b, c, alpha, beta, gamma) + where lengths are in angstrom and angles are in radians. + crystal_structure : defdap.crystal.CrystalStructure + Crystal structure object for the phase. + slip_systems : list or list of list of SlipSystem, optional + Slip systems for the phase. + slip_trace_colours : list of str, optional + Colors for slip plane traces. + + """ def __init__(self, name, laue_group, space_group, lattice_params): """ + Initialize a Phase object. + Parameters ---------- name : str - Name of the phase + Name of the phase. laue_group : int - Laue group + Laue group number (e.g., 9 for hexagonal, 11 for cubic). space_group : int - Space group - lattice_params : tuple - Lattice parameters in order (a,b,c,alpha,beta,gamma) + Space group number. + lattice_params : tuple of float + Lattice parameters in order (a, b, c, alpha, beta, gamma) + where lengths are in angstrom and angles are in radians. """ self.name = name @@ -71,7 +99,7 @@ def __init__(self, name, laue_group, space_group, lattice_params): def __str__(self): text = ("Phase: {:}\n Crystal structure: {:}\n Lattice params: " - "({:.2f}, {:.2f}, {:.2f}, {:.0f}, {:.0f}, {:.0f})\n" + "({:.3f} Å, {:.3f} Å, {:.3f} Å, {:.0f} °, {:.0f} °, {:.0f} °)\n" " Slip systems: {:}") return text.format(self.name, self.crystal_structure.name, *self.lattice_params[:3], @@ -80,12 +108,14 @@ def __str__(self): @property def c_over_a(self): + """Crystal c over a ratio for hexagonal crystals.""" if self.crystal_structure is crystalStructures['hexagonal']: return self.lattice_params[2] / self.lattice_params[0] return None def print_slip_systems(self): - """Print a list of slip planes (with colours) and slip directions. + """Print slip plane family and their associated colors. + For each slip plane, print corresponding slip directions. """ # TODO: this should be moved to static method of the SlipSystem class @@ -217,8 +247,11 @@ def __init__(self, name, symmetries, vertices, faces): class SlipSystem(object): - """Class used for defining and performing operations on a slip system. + """ + Class for defining and performing operations on a slip system. + Handles slip system operations including plane and direction transformations + for both cubic and hexagonal crystal structures. """ def __init__(self, slip_plane, slip_dir, crystal_structure, c_over_a=None): """Initialise a slip system object. @@ -282,42 +315,44 @@ def __str__(self): return self.slip_plane_label + self.slip_dir_label def __repr__(self): - return (f"SlipSystem(slipPlane={self.slip_plane_label}, " - f"slipDir={self.slip_dir_label}, " + return (f"SlipSystem(slip_plane={self.slip_plane_label}, " + f"slip_dir={self.slip_dir_label}, " f"symmetry={self.crystal_structure.name})") @property def slip_plane_label(self): - """Return the slip plane label. For example '(111)'. + """Slip plane label. Returns ------- str - Slip plane label. + Slip plane label in the format '(hkl)'. For example, '(111)'. """ return idc_to_string(self.plane_idc, '()') @property def slip_dir_label(self): - """Returns the slip direction label. For example '[110]'. + """Slip direction label. Returns ------- str - Slip direction label. + Slip direction label in the format '[uvw]'. For example, '[110]'. """ return idc_to_string(self.dir_idc, '[]') def generate_family(self): - """Generate the family of slip systems which this system belongs to. + """ + Generate the family of slip systems to which this system belongs by + applying symmetry operations to generate equivalent slip systems. Returns ------- - list of SlipSystem - The family of slip systems. - + set of SlipSystem + Symmetrically equivalent slip systems. + """ # symms = self.crystal_structure.symmetries @@ -365,32 +400,39 @@ def generate_family(self): @staticmethod def load(name, crystal_structure, c_over_a=None, group_by='plane'): """ - Load in slip systems from file. 3 integers for slip plane - normal and 3 for slip direction. Returns a list of list of slip - systems grouped by slip plane. + Reads slip system definitions from a text file. File should contain + Miller (or Miller-Bravais for hexagonal) indices: 3 (or 4) integers + for slip plane normal and 3 (or 4) for slip direction. Parameters ---------- name : str - Name of the slip system file (without file extension) - stored in the defdap install dir or path to a file. + Slip system file name (without extension) in defdap's + slip_systems directory, or full file path. crystal_structure : defdap.crystal.CrystalStructure Crystal structure of the slip systems. c_over_a : float, optional - C over a ratio for hexagonal crystals. + C over a ratio (required for hexagonal crystals). group_by : str, optional - How to group the slip systems, either by slip plane ('plane') - or slip system family ('family') or don't group (None). + Grouping method: 'plane' (default) to group by slip plane, + 'family' to group by slip system family, or None for no grouping. Returns ------- - list of list of SlipSystem - A list of list of slip systems grouped slip plane. + tuple + Slip systems and trace colors. + + - slip_systems : list of list of SlipSystem objects, + optionally grouped based on `group_by`. + - slip_trace_colours : list of str + RGB color codes for each slip plane group. Raises ------ + FileNotFoundError + If slip system file cannot be found. IOError - Raised if not 6/8 integers per line. + If file format is invalid (not 6/8 integers per line). """ # try and load from package dir first @@ -442,20 +484,25 @@ def load(name, crystal_structure, c_over_a=None, group_by='plane'): @staticmethod def group(slip_systems, group_by): """ - Groups slip systems by their slip plane. + Groups slip systems by their slip plane or family. Parameters ---------- slip_systems : list of SlipSystem - A list of slip systems. + List of slip systems to group. group_by : str - How to group the slip systems, either by slip plane ('plane') - or slip system family ('family'). + Grouping method - either 'plane' to group by slip plane + or 'family' to group by slip system equivalence family. Returns ------- list of list of SlipSystem - A list of list of grouped slip systems. + Grouped slip systems. + + Raises + ------ + ValueError + If `group_by` is not 'plane' or 'family'. """ if group_by.lower() == 'plane': From 8906a362d71c57c1f46153ff907fd67c1ac3ef71 Mon Sep 17 00:00:00 2001 From: Rhys Thomas Date: Mon, 9 Feb 2026 15:26:29 +0000 Subject: [PATCH 08/18] chore: more docs updates Add additional pages to user guide and contributing guide --- docs/source/_static/IPF_down.png | Bin 0 -> 8931 bytes docs/source/_static/IPF_up.png | Bin 0 -> 8812 bytes docs/source/conf.py | 4 +- docs/source/contributing.rst | 47 +------- docs/source/contributing/contributing.rst | 29 +++++ docs/source/contributing/docstrings.rst | 63 +++++++++++ docs/source/contributing/website.rst | 28 +++++ docs/source/userguide.rst | 7 +- docs/source/userguide/conventions.rst | 48 ++++++++ docs/source/userguide/ebsd.rst | 127 ++++++++++++++++++++++ docs/source/userguide/experiment.rst | 3 + docs/source/userguide/hrdic.rst | 82 +++++++++----- docs/source/userguide/inspector.rst | 24 ++++ docs/source/userguide/linking.rst | 3 + 14 files changed, 395 insertions(+), 70 deletions(-) create mode 100644 docs/source/_static/IPF_down.png create mode 100644 docs/source/_static/IPF_up.png create mode 100644 docs/source/contributing/contributing.rst create mode 100644 docs/source/contributing/docstrings.rst create mode 100644 docs/source/contributing/website.rst create mode 100644 docs/source/userguide/conventions.rst create mode 100644 docs/source/userguide/ebsd.rst create mode 100644 docs/source/userguide/experiment.rst create mode 100644 docs/source/userguide/inspector.rst create mode 100644 docs/source/userguide/linking.rst diff --git a/docs/source/_static/IPF_down.png b/docs/source/_static/IPF_down.png new file mode 100644 index 0000000000000000000000000000000000000000..a0b4438c84fcb136ed93b342b19e1282a5b9dc11 GIT binary patch literal 8931 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

EaWdbR>S!MU&VB_du z#T8^=hTpak3K6^r`r&raC*sUkC^c^0v;<~FZ7vS=V3>At38&#Tl+}w2Qr~Wwx5fS8;ry!#m{8OX`&~esT8mXK|ghRSCEeHoe)4Dbg zHU-9&g0O+2EIlOxci)n<)$KI(^!(>R4~R(V?t?~>I!|$mk~I?I8DU}Jw-3KQW;FAS zOW@Xi9{tM$2|GN;{fLp|07}{kW&x5^XV13PVK*Q^$ph@7itoM)hxLeh{4u1VrR_T9 z#tC^F>gDxkd^J1M)KG7u>Y(C=%dO5p$IAYDj6xG=4LbyfhJUf5oAyG zY|M(D%O)t`1U2xahACPZ^HlUWb=H~1#SR!sNO^Da2-U3;pulisLG!EI{$XcQ=k*oyY4qLw zMgoZXPLq*+v#@iGC52@5;OB~eE3{{*$}{42gIu5@fIc5oj_j>AQEF&vinFYh-O$i* zX02xudl=hIJj)J`Qs^zWx`9Y&I9w7Ek_lKpq^){IcLbobKa?4>fM(7ImP0v+fXE8~ zs@dAE2QRkwBtM@!n3%F<5<|tx8l9ZXh{ScNHW`!E^BGN(n3U+0Y5tBW2pow;gnkMj z;sA|}5S*rO&qq7~>@lJcVx>JV%gLpK`j|^S5e!45TnIi*kVz!JLOc4g#Ho=g+@@^@P&kzx$R)J2EO$!htCkOQJm+w*lwGfyfP;?(<`QJNJN> zPbs~SaZeGqOWG*IFJF?7NP_nxOr@NAH1y^LtSDw%3H=13G7;Zh40Hx)w$0WRWdX1DT8NE}ttFb}29O%ebBNjb_`*R;ZLD_)xo-b4 zYC65L30#$sn227R`n)hwiC>VyK8L|w`Kz0$B1lLAqzGFUYGs5(Wnf_Pv|e0yaVcNG zAvoOYi~$ogF+FV|r85TA8yDIK*1=d;<}^#MVRSG50%#)A05E- zDmX9@6+(6{-fpC;2LQa@hG=extJ`6}3+|LKfm#5@C9eJ1JzDJw>4Z=5NCRe)fX#pR zLM(PU=i%4v7Tenvo`sAX>HIMcwLqhngoWciepFiW zo8f>1oZ$iS4G(|ZG&!*~WY!8j1)^LezJr!74ICQnxB{5&zKlQGA8PnLswUXSLraY< zMu-^b9kB&aDt1sZkuXM7qYx)&l}iZ#CzmLAk14b%>xQRm92^>}Y)83ar6V%uoZnrW zM!~==T_fSrR|pN{?O{Rg(aMYZL{s=fef_VtIyBpy7 z7QW^XH~d|{krXfp>C+y_OOzK3r}SA*K{=x%hbx`fCMPFBSDZ+7t%Rc5`Y6nmWc3xs zk?$RUUI+3VerLxhktR@S-f?^j2Gcr8R!Hon8hLhdl#ti>J%4|m=JaPqvpO4L#EL^O zP8|p;;4277lP>;LINT{3z*wq`J0D^Nd3jFWE$!4CB~8{m~~$T)qPG1!#>}WNeupv2r6(AMgwnc`){gt?;vy| zXba{57?YNTh1|0Hr#JL(M7jc6!SZ!V%jrNGkHd9b*Vdj-&{lW=-nLtw3I%gPtqeI0 zC8fYdw`^rYj;V6c!dRFhLiXm_S@oztFX~GZ5wcyQEuiVVrlTakSJ+TO>z82R9@qQV zwEh=W64*`9{}YRgi+|6`d6h1x?}Y=QH9NqnBOv;fZP?z}m@drE5J}+)_-DkA+;mnL zyKV`cS_K@#!nZSqUG4$tJ{+%?r{&f&1@B<=UVETXQE5Ogo&%*3J=UBr4y%oYZ#nl|UdIuIB2z(ro$_%BV?&{?j45F5Xt<`VOu1=j{{4Bpv1>$y z!8oeanq5nL`}XHUG4{v$K{ov5WrB`-&n#u=-WVsnCUIA0RP1h5vj0_|G;aGzNpKLY zj=A$HD_38EC=+8EWpr}<1N-<$_}=LqvGuxd#y9XOy>1aN`;V7rw~8+Y8nL3$*!Ywl z3ivZZfe4L8|9VS6O&yJWZ zelH6?J~~h@(wF3+BMk@$(E5Ap^GhO9i|INsW@ctGrrU8td8!G2#MHduwfR^vF|pHU z&Io$#Z(}8yX=&TIi|U#;e|3+SThr_4=!7wfZ5E`njmpAzw+hdAZjm4D|INzCK+)4D z2ue@qJfJ^A!PxP>sDb43*DN{9kpfx=2M4F$!}&e^(bieiINYtc!$Z%Xb93j&$jZEW zCnl0#-_SjO`Lgc(LPxne9K^lOj*g6~s=XNQ3I!vE`HDNO!+`&ec_la`XgSekVfZovVy(3fhmiSIl-TJzx)5Qv7e zOlDFTaA!puw}*s;;6g%-zl2@N&dZx5^m>x;Y*|Mhth5Q@TW8` z*5)0bA5xVz>xjC7S{^I*m-5=zuzYqs6Z&FkdAu|zBZJj>b@H5VP!KT`=|c5FT9)(5 zcb}S6ql?>XGwF+q7Bw&a$lBN}n}y19CG`2>7*J5&Xge&8177lhux$eSK##tVatmva;BN zl}dxZyO+hVS1*g&wYIkhwYFmPo<8NiU83hF%ZgT8B5V0E7b*RvzdzD)C^tGaRk`$8 zuXgD(^5Wv+UtE;FTKaB8^z`%}9&^JNejFRvOb{lZj#iA1ijt9+KRZ!jOPKNa~;0rfqZpNUAIY> z8LzAC?8SynFG;Rb`s$QEn_XR1v9jXg)++oEwRYVs3*JMbsjb}+Ld(6rv0?OY^VbDh z+OG{>9u>9|U*uT4UR74Q1T^S}(%o?Vou7wFl=f*@nGDg80?5%ww4JE<3h&`iO;dXI zjDws(*sv{xwzaM8YopJJqQ}(IP@a083e(`wkWR7j{ha!U!)UnX!tvRbpz5`TgQ`D; zwem_zt>fcKuwcXTCvNbD=ME0}!=v_-RRaAITVyDxBmCjNt@wR+wY$*Szh1}3x0P8A zt#573!GWInQCPc*mXb=#d5|!eamSylTkyqi4GBp}fQEr<*&)#X_ySsbdKy~VfTAKk zYHDh|I@joLA9c4j+F43p{CRwMcvv8z-V#WjBxpv0f=v!qM{#AqnY=Ht9_6iZS?i-w zm1s&5GJmIA$vZncd-dv7ey7EU>UkI3cE-)~)$=GWUApw)NqJWpqeuYktqTvR5XK-9 zk|yaueL7Q`Zv#d-z251N9L-ehSb)IoA;%Bomz@lg0o0~c7bxshD)g1 z9(;hGpU1Y4i>^<5tWnd@+>?><9jE4d59JodBF*fJ!<{7|AyHD%@UrBhp{~a0=`r59 zbEmbn74@*z+4k+ge&$@J>D}0C;6qBO%SrcfS=G^e=wR>W$F2VF|T$6W(sr z{Oa84)2GL}<}!nre-32c>q(QGxY9+aNSDnZVs!xphh!mLjYUckIcu}-*8=8!j3p%{ zsKvn?(&*^u?cXEac&A^nz&-~%E2s7jxBCAb%$(R^-~N_kql#we;U_!ZN#2)ay|w>+%SLR@jiD`Q$^D|%Lp)qU;=va*qBPTZmdZU~%T}pHcIr!5sJ^$K5HH zWTqlD$-N)`!uHcCFJBT&yUvl9&S`LSQAJxV!_kvnz8UuUvkV2}E#KH!8db@^7aqTl zhi-vwS~<5WYQ2AaBh7o?5(V&ZUtd41Gq`|~7AG$fmzvrec1g%$w2&$JrY!+0KYx_h z!A`Mp8wp&JDYm&l#Hjf+)T6wr>ViL|6u-DiIUs4iY)ELRPNf}FLqkJPx^z15Z(V); zzs0P`u?pMEOt+o*#ot~BuI&LxB>P-q*6n0sMD*3H4n5LJDSasO@FA7-@g5uM#qZ%O zJUn4Pei%+pO)0CGH$XKHlv`_M;MWS|BAGcjv7T@Nx2lGRHJH7&AKBHe+|+f0)dt4J zUC32UFWlTq_w_~TS}vNpw>^XpzJo^5_xh{p>E)%<=q-t=oOb61;Qa8k_Vs*ka&pSR zaa^vQvtcnW7jVEP>(C8H-1iv-|DOvhn zj6Rj~_Lc%d6%vnV0YPyq3K)L>@1OSd*%mSH!&|GXt37W-Lh*Q0*>DE4-`J9f3}6&k z=b3BO*;;wNMd2!MLvQ(RZ#zMYw&~i}-*<2jcvvFJ1iS||(M!Hy7!wnNTmcGbmO|1f zdmK=`*o26Ph)1X7Gz$w0a%u3)@$oUjXyKPd3naQSp`9xRwd>u>J1rTLNieM{UdR6y zph*3qqNw^(#pM7`+@Pe#OU)@kc?pQ8-2)I{M4jx9Hro72?-+rdj3vRM&rmY=03-sZ zeHj^v2dFYGf0D#+M9|mQ_e{>owIztMeP}2aaObAoq=hqMP!6n(rtxU!a=+98&&ovQ zLTSGgq2c%rdCUU0YXnjNg}Hw}Ddmg9($yMVCv*eCW;eg5mAajl!cqe^8a?GywA1c|pC&>+f&-)lvV z>U)F}5>vdB;2K}4oKb;mIpw4(FP0A#R`vIJNKRCPPRyiyFjdPJhLNO2JF|b2NN^9$_zy&+H5a= zmFbkcLeHl!EEaA88eSs?XmMk2E^?^e!=>Tm*aL<1K}>9Hl7LBIu1+Nlayh_?Jv~Yd z`@gwM=WbnqrY7p{?lx(9Nwl}Wzu70gh8j~e1?2+#-;95?=>gR<4`fI%fUVR5%b<1# zBu7@HR5M5pdVN>F7~eVJJforWK)=@kMafT%Y|2 ziL18y#NB@Q5#ItWY-eXzx86*eot?c7Fz^$fZ6M;|d~&oq_xpF(zpaITo85f95p(?8 z>Nl|Df`Wps`%8IC!}&B5mG;E&4a`4&H2L==i&Cnps>aeN>I@ed%s5|s;Aj$`N`lE| z1V+-Xak~Cdw{rgPs6k#;RU!q`?N43@&Qgc#_W*f%Bd%##?yb+FJU2TzL3t&od=UUj zFC0uT@D6Tllm-SngzFHt8H*Y<@JU4gzQ{njbWTBxoay!jb8~Yb51TB7YC$-?OFADP z2U0L~`JN(_S5$0nYYPB;0>Kr_p$z|MTgG8QzE~JeoEj^$jD^y&85e?uvmpFr)=dX3prm4QB253fBmEi|m+{K? z@}5Ki|FSY+l&7bs>q`0PMPZAmRo0?FHFfps*}%)bF-y}A^2DOMht6W~f}mX$fbcI# zdrMeaS^{+yTYN>IKY#wBgzKlStT$F)-x58obtY1jy@EA^s+$pW_<0&Im%rZqukqS+ zy*3Zsmq?b#5i2t=P{Re9-OD(&PS`Gqp}5|49~H1*zR>AlB*7@Mii*OeR=eT|MbK7j83I70o2>{7%A( z6&h*6(=5k&sQXvGO!k-W07r2C^Rq!L^B{p-U0v6qG#nQPt^n2+ZZV3)wHX^BtnCRE z!jV@Dswdw;C0(kw%3*Ht+sDg*5vL7@-#xm@$+`72lur#rNi0psxFRV&*}kEeX)x{q z@|KSNe)HMp0O){{XT1z$Ev&4pz91wc$S>a<pxh!iRY z>C~cz{gB>d(V?0b*03!9#mVYY+q6<(1VCCTWG{R0$kJrBkgD{Na8go|u*HBc=#OIi z8L8E}EtS%_V1EZ^XAXXTDw(0$lXJAVTXk@!&*35+7gAkfqN03DONG>Pm72i1xow&% zm-7DFIUx9&&6J?#hikd*b1VVgDGv(GX1eZ@Z|!PLC{W^S-lvNGXUKcVuTc~^c}1NZ z?=~_@x)0-pcjSoB6iC;WTaW(y^XGx9>n*^zY`|5|!;N;EsTx{oY3WinMG=SY8{1m3}d`gTEp* zSbtKfq)BvwAX*wP<&-|!rlAWB;7oG`>!S#_3MlnVF3zezHC+l-!*(R7!D>7TVMmg< z3onEkv*{;?oc~)=*B$Ll2wQ*a!M{?Qf?L^`YZr6d%RkLS6QA-*d8=pOU7WB_BmAAJy;1~;1czu)n&ay;kXHH(;hz73%K`dTgCFPbM z=ybmN?em?Tof6vv_ip!J(d_E%44$y7CI5WWqcjdQ&6NCu2PQvTgF#7C(a;cnzDFsE z!D0nV&HKL~BK(z_dc4YqDmbFp*fIJkBlg@{*9~-c0^b5u5C=Cm>9r(Tw4h)Nba8Tu z%IS%-rT|NLJl?{5-Ul>>x)+!eL^1&D$Hd3q2aE$jAp&Ohu^Vm*X*b|dC_u!al+KNj z)8Z1*z*hkMg0Aa^YHDiFU%dDQfk-=)iQVKaNId|Fjnymdo_;U4CYG_W`2gn?M1m<8 zE^0hR)HYy3*^Oav^SeoJ&DxjOWu!w>FNEwqBA{UnOi}0+BABiBMKZB`g}>US@ z--z1V84E^-EUc|j`i@wQFfA=oy^d4Y*JlRyZW$dNea!qHSzHI~%|)62`nL1(*|;9M z)Tk&!EW=j548GuEU=!>Pu`M9f@Suwj&jD_v)Nw%ttgf9iCR%as*RT5^UQgY6{=>K0 zXxWzp)5Qb+8Z1a4`Q$^v6pr6~oXlpR5WySY( zCBJ!NrQ4XBtLE3i0zl1g+ChaRj1Wsfrf}uz)zj}sy1?3s^wtc!0xA@Hc>_Dq#3rf| zp;0N~E)ff99uxprQb*hBD0oo*?p=aVdcIumqy70a7m8^Bp(dZVkfVU1K!@mYMKKh< zdxwKVCj}*cyb7utGJD-Jq2}_cTD0kV)D*Wg*=s&FAQk0Sc&{LnWmH&d_qQ zsHB}y)W zv5B9Ihc2`;xF?e!{}f0=1QLgGmDxl@Xnm9IYJx%F3)qZ(VmN5wQ$vcKfR&p&G%PHP zq4rC7pPz;!U`MWbf11Q*2U~V-?$6oTCRmVyqTsRNChZY9&@i6BKdAqDGCLcoe2_-UD%XBiNk~Xod@sBHute5fIfo$s8Pvu(O3HTV zCmJu$u-e1rHmWKf(a<+*)dSdSsuycBk5BuEvBny7sTmDVRx2wHa9n)ySRvoJsN=?A$HhXH(IUV)n9Emu%lp>zyXDW z@CEw=sF=M;m2V7+q0-Cy_waQnW;r6`1yH?b$;opN8Xqqq0u^)Slh29wYaX3u@Vw^O zyU}m*AqcFv?4ZeNtc5s!ux?UPQalhzK>`74bq~e{NG@ah{%tT$OixeGMm;e_%IiB` zc>bZ^SZ1oY%ODt+FX5Nvo;+ay$Ts`>_CAt~LB}aeqm4Ng*JQQ)a7#diO^_5Q73tTH zfjk7p2DUW;7kc{z7YHgr@wd1`6-&SDoSdYTR>s>~R>W8=77?gu^h>b(DEGg=?*ke@ z5F-F+aF_)9ML|q$hduB3PDaV};u#jt^-~ZQS0qOt?^HGxKcBq|HZk*_Cfd2s>C{bH zoJKxK((@NCw1QS6mVvTMx@pVMFMT2b(+mLK=#p!a!QOD*v9XEwh}7EpQp8$%l7x`s z2sA?RTMb_W1!q`Ws)=E|2Zqlt#xlSPO)F2^2`Ur<9K<1RE%XyoQBldjG9ly;hGoXd zEqQ5!Gy~KMBFT=q6xif*yv35{wWCA9B1IObSB;=KB=Y{TvAnNn95ix>)NXS=Rn8*F z#}HF8Bj5&l`#efZOAAH{iDHf%`uh6FTCFvoAg!JO0~Gi_2(fOg96x>u4l&Jb{t{rl z1$U&!YBdr(H@CECGNVT@U=3{2&CSe`5?dL$qDVA=e*Y9TMnJ>}0G|ce1>Lm)dIIDp z9b9@(#5FYS;;y)V&caRW^GwYJtYG3euU*5vdUZO()Zzf-ymhV~q*A-w-UYxWOk&tq z0GSa`eS|-jmwN#96D2(=R?bUP=cj=SF*(p9LYp}sAG$$E)Bdw9v>GG~3ee6RC>&-O z(|9Me8f{8S%KG{`D<@|V*!4uFysmtiLN&-l!2iz$(CdHd{k*iY(hE;ty6yBXmxVgt z2*fkTjL(TA5)UIn5QRv`fBS1lS%@S}BwPKN%>c{u_KrU~A zjVaKYBgMlB@EA0th&j$b2cAR$y0#AHJXo5ltwcUN9p5MX=;1>{4xk7fk+FRe^7bt{HddpaL6a4xe9+l%AaDRpzzCx-znCkFpvhsf z)eKWY$SIOiG%$?!m#QhDB<3R+#hPzaPGUPpNl8dtA%;StC}nwe^vE6M6w>#y$_s@p zYG^cVo@}@bL?;Rw8W-S@7K+=M-#fey3YKpRjfj{Z&Q}-Ne0w?ck$Z0e4%eai?h*J? zOyN}yjx&%GOtifX%K>tMvQ;s zZ5-SIa^G*Uo`OgV!t_uW9_4gRr{gp}f|heU+;f1QN)#|T1C&%@sfap5&d>_8t;O%< z+%T0@N)il!;(?H5N6?m%UAz$7pb^AKt{xsV&_PBQA&JaVoUslNV0;L9@+{~)n~91Q zN(1}xlAL1fFGUj=Q^*3MAFq$WNryx98E<)TVgLmn4r7wX>$OzW9v7IHY*Dgz?ljS+ zSAvHjEU;tg5Ptq*H%{ag*vX#NrUbBrxiN{Strx1UTX8 zPVwOEyn!trdQ%HjWoT`!M^b_tBmkWMtw$$Dfs8H3OV}X_j;AeE6EpR@=;JWF@`Qz+ zx;h4;YglLtoCB|z(qHp}xrq1o=#bgVb1b$7&nGDKkMbg2;Q!A;fXT zN6VqK%PbxxRlbi9FQm|61#`J~a4_*~h-mMBhQt}5YYsu_!Ju@488$DD4339(U@D`o zr`H3~LLcbPv6>g$fKF!96}MNbZZGR;$8xH20HT7UfwiAIcP`UqZ5l@2!GOd7Kcs1{ zKhNpAFR>e&n7F|h2%+HaGOv7pVoL1B&-*2%rO^IlFf?g_;1-g+ zx8`6j2V?CoAZNoNA-@Fo@Lyld0+?dL8J_$xHpm^Vlwm=}$nN2dP& kA3y%^|Mk-n-6w=>OSz%yuGMz%gC8h``zn~iyM{0S7c>V(?*IS* literal 0 HcmV?d00001 diff --git a/docs/source/conf.py b/docs/source/conf.py index 8694c88..991ddb4 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -200,6 +200,6 @@ def get_version(): intersphinx_mapping = {'python': ('https://docs.python.org/3.7/', None), 'numpy': ('https://numpy.org/doc/stable/', None), - 'scipy': ('https://docs.scipy.org/doc/scipy/reference/', None), - 'matplotlib': ('https://matplotlib.org/', None), + 'scipy': ('https://docs.scipy.org/doc/scipy/', None), + 'matplotlib': ('https://matplotlib.org/stable/', None), 'skimage': ('https://scikit-image.org/docs/dev/', None)} \ No newline at end of file diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst index 7757628..c340e8c 100644 --- a/docs/source/contributing.rst +++ b/docs/source/contributing.rst @@ -1,46 +1,11 @@ Contributing -=========================== +================= We welcome any improvements made to this package, but please follow the guidelines below when making changes. -Coding style ------------------ +.. toctree:: + :maxdepth: 1 -In this project we (try) to follow the PEP8 standard for formatting python code with a notable exceptions: -- The 79 character line length limit is seen more as a guideline than rule. Code is split across lines where possible and to improve readability. Line length should never excede 119 characters. All documentation should be split to lines of less than 73 characters. - -Repository Structure ---------------------- - -The schematic below shows the intended structure of the branches in this repository, based on `Gitflow `_. The `master` branch will be committed to at regular intervals by an administrator and each release will be given a tag with a version number. This is the first branch that is visible and we (will eventually) have controls in place to ensure this branch always works correctly. The `develop` branch contains new features which may not be significant enough to form part of the 'master' branch just yet. The final type of branch is the feature branch (green), such as `feture_1` and `feature_2`. This is where you come in! If you would like to make a new feature or fix a bug, simply make a branch from develop with a reasonable name and make a start on any changes you would like to make. Don't be afraid of breaking anything at this point, there are controls in place to help make sure buggy code does not make it into `develop` or `master`. - -.. figure:: ./_static/branches.png - :scale: 100 % - :alt: Repository structure - - Repository structure - -Instructions ------------------ - -1. Making an 'issue' first is recommended when adding a new feature or fixing a bug, especially if you're not sure how to go about the change. Advice can then be given on the best way forward. -2. Using your local git client (GitHub Desktop is easy to use), checkout the `develop` branch by selecting it (making sure you 'Pull Origin' after). -3. Create a new branch with an appropriate name, using underscores where appropriate, for example `new_feature` -4. Make any changes necessary in your favourite editor (PyCharm is recommended). -5. Test to make sure the feature works correctly in your Jupyter Notebook. -6. Commit the change to your new branch using the 'Commit to new_feature' button and include a descriptive title and description for the commit, making sure you click 'Push origin' when this is done. -7. Make additional commits if necessary. -8. Raise a pull request. - -Additional notes ------------------ - -- Always make a branch from `develop`, don't try to make a branch from `master` or any feature branch. -- You will not be able to complete a pull request into `develop` without a review by Mike or Rhys. -- Try to avoid adding any dependencies to the code, if you do need to, add a comment to your 'issue' with the details. - - -Documentation ------------------ - -Where possible, update or add documentation at the beginning of the function you are making or changing, adding references if required. This is important so that other people know how to use your code and so that we can validate any methods you use. We are following the `NumPy Docs Style Guide `_, but you can use any of the documentation in the code as an example. Add comments where it is not clear what you have done. + contributing/contributing + contributing/docstrings + contributing/website \ No newline at end of file diff --git a/docs/source/contributing/contributing.rst b/docs/source/contributing/contributing.rst new file mode 100644 index 0000000..04044aa --- /dev/null +++ b/docs/source/contributing/contributing.rst @@ -0,0 +1,29 @@ +Repository Structure +=========================== + +The schematic below shows the intended structure of the branches in this repository, based on `Gitflow `_. The `master` branch will be committed to at regular intervals by an administrator and each release will be given a tag with a version number. This is the first branch that is visible and we (will eventually) have controls in place to ensure this branch always works correctly. The `develop` branch contains new features which may not be significant enough to form part of the 'master' branch just yet. The final type of branch is the feature branch (green), such as `feture_1` and `feature_2`. This is where you come in! If you would like to make a new feature or fix a bug, simply make a branch from develop with a reasonable name and make a start on any changes you would like to make. Don't be afraid of breaking anything at this point, there are controls in place to help make sure buggy code does not make it into `develop` or `master`. + +.. figure:: ../_static/branches.png + :scale: 100 % + :alt: Repository structure + + Repository structure + +Instructions +----------------- + +1. Making an 'issue' first is recommended when adding a new feature or fixing a bug, especially if you're not sure how to go about the change. Advice can then be given on the best way forward. +2. Using your local git client (GitHub Desktop is easy to use), checkout the `develop` branch by selecting it (making sure you 'Pull Origin' after). +3. Create a new branch with an appropriate name, using underscores where appropriate, for example `new_feature` +4. Make any changes necessary in your favourite editor (PyCharm is recommended). +5. Test to make sure the feature works correctly in your Jupyter Notebook. +6. Commit the change to your new branch using the 'Commit to new_feature' button and include a descriptive title and description for the commit, making sure you click 'Push origin' when this is done. +7. Make additional commits if necessary. +8. Raise a pull request. + +Additional notes +----------------- + +- Always make a branch from `develop`, don't try to make a branch from `master` or any feature branch. +- You will not be able to complete a pull request into `develop` without a review by Mike or Rhys. +- Try to avoid adding any dependencies to the code, if you do need to, add a comment to your 'issue' with the details. \ No newline at end of file diff --git a/docs/source/contributing/docstrings.rst b/docs/source/contributing/docstrings.rst new file mode 100644 index 0000000..963e3c8 --- /dev/null +++ b/docs/source/contributing/docstrings.rst @@ -0,0 +1,63 @@ +Docstrings +=========================== + +Where possible, update or add documentation at the beginning of the function you are making or changing, adding references if required. +This is important so that other people know how to use your code and so that we can validate any methods you use. +We are following the `NumPy Docs Style Guide `_, +but you can use any of the documentation in the code as an example. +Add comments where it is not clear what you have done. + +Example +--------- + +.. code-block:: python + + def foo(var1, long_var_name='hi'): + r"""A one-line summary that does not use variable names or the + function name. Several sentences providing an extended description. + Refer to variables using back-ticks, e.g. `var`. + + Parameters + ---------- + var1 : array_like + Array_like means all those objects -- lists, nested lists, etc. -- + that can be converted to an array. We can also refer to + variables like `var1`. The type above can either refer to an actual + Python type (e.g. ``int``), or describe the type of the variable + in more detail, e.g. ``(N,) ndarray`` or ``array_like``. + long_var_name : {'hi', 'ho'}, optional + Choices in brackets, default first when optional. + + Returns + ------- + type + Explanation of anonymous return value of type ``type``. + out : type + Explanation of `out`. + + Raises + ------ + BadException + Because you shouldn't have done that. + + References + ---------- + .. [1] O. McNoleg, "The integration of GIS, remote sensing, + expert systems and adaptive co-kriging for environmental habitat + modelling of the Highland Haggis using object-oriented, fuzzy-logic + and neural-network techniques," Computers & Geosciences, vol. 22, + pp. 585-588, 1996. + + Examples + -------- + These are written in doctest format, and should illustrate how to + use the function. + + >>> a = [1, 2, 3] + >>> print [x + 3 for x in a] + [4, 5, 6] + >>> print "a\n\nb" + a + b + + """ diff --git a/docs/source/contributing/website.rst b/docs/source/contributing/website.rst new file mode 100644 index 0000000..39742d0 --- /dev/null +++ b/docs/source/contributing/website.rst @@ -0,0 +1,28 @@ +Documentation +=========================== + +The narrative documentation on this website (including this page) is built from source files which can be found in the ``docs/source`` directory. +It is automatically built to https://defdap.readthedocs.io/en/latest/ when changes are merged to the main branch. +Documentation for the develop branch is available at https://defdap.readthedocs.io/en/develop/. + +Writing documentation +----------------------- + +DefDAP uses Sphinx to build the documentation, which is written in reStructuredText format. +More details about the format can be found in the Sphinx documentation: + +https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html. + +Building documentation +----------------------- + +To build the documentation yourself, you will first need to install the dependencies for building the documentation. +You can do this using pip: + +``pip install defdap[docs]`` + +Then, you can build the documentation using the following command from the ``docs/source`` directory: + +``make docs`` + +The built html documentation will be available in the ``docs/build/html`` directory, and can be opened in a web browser. diff --git a/docs/source/userguide.rst b/docs/source/userguide.rst index 6bc0a7d..c21bca2 100644 --- a/docs/source/userguide.rst +++ b/docs/source/userguide.rst @@ -1,11 +1,16 @@ User Guide ================= -This is a narrative guide on how to install DefDAP and hwo to use the various classes. +This is a narrative guide on how to install DefDAP and how to use the various classes. .. toctree:: :maxdepth: 1 userguide/installation userguide/hrdic + userguide/ebsd + userguide/conventions + userguide/linking + userguide/experiment + userguide/inspector userguide/howtouse \ No newline at end of file diff --git a/docs/source/userguide/conventions.rst b/docs/source/userguide/conventions.rst new file mode 100644 index 0000000..5fa1404 --- /dev/null +++ b/docs/source/userguide/conventions.rst @@ -0,0 +1,48 @@ +Conventions +=================================================== + +Spatial +---------------------- + +The origin of plots is in the top left, with x increasing to the right and y increasing downwards. + +Orthonormal reference +---------------------- + +Oxford Instruments and EDAX use different conventions when attaching an orthonormal frame to a crystal structure. +This can be set in ``defdap/__init__.py`` by changing the ``crystal_ortho_conv`` argument. The ``hkl`` +convention is x // [10-10] and y // a2 [-12-10], whereas the ``tsl`` convention is x // a1 [2-1-10], y // [01-10] + +Pole figure projection +---------------------- + +There are two common conventions for the pole figure projection, which can be set in ``defdap/__init__.py`` +by changing the ``pole_projection`` argument. The default is the ``stereographic`` (equal-angle) convention, +but the ``lambert`` (equal-area) convention is also available. + +IPF Triangle +-------------- + +The orientation of the hexagonal IPF triangle can be set in ``defdap/__init__.py`` by changing the ``ipf_triangle_convention`` argument. +The ``up`` convention looks lke this: + +.. image:: /_static/IPF_up.png + +The ``down`` convention looks like this: + +.. image:: /_static/IPF_down.png + +Slip systems +---------------------- + +Slip system definition files are in the ``defdap/slip_systems`` folder. +The slip system definition file used for each crystal structure can be chosen in ``defdap/__init__.py``, under the ``slip_system_file`` argument. +By default, the FCC slip systems are defined in ``cubic_fcc.txt``, contatining [111] planes and (011) directions. +By default, the BCC slip systems are defined in ``cubic_bcc.txt``, contatining [110] planes and (111) directions, +[112] planes and (111) directions and [312] planes and (111) directions. +By default, the HCP slip systems are defined in ``hexagonal_withca.txt``, basal , prismatic , pyramidal and pyramidal slip systems. + +Slip trace angles +---------------------- + +These are calculated \ No newline at end of file diff --git a/docs/source/userguide/ebsd.rst b/docs/source/userguide/ebsd.rst new file mode 100644 index 0000000..245a1fc --- /dev/null +++ b/docs/source/userguide/ebsd.rst @@ -0,0 +1,127 @@ +EBSD Map (`defdap.ebsd.Map`) +=================================================== + +The EBSD class in DefDAP provides tools for loading, processing, and analyzing EBSD data. + +Supported Data Formats +---------------------- + +DefDAP supports loading data from various commercial EBSD vendors: + +.. list-table:: + :header-rows: 1 + :widths: 18 12 50 + + * - ``data_type`` + - Extension + - Description + * - ``oxfordbinary`` + - ``.cpr/.crc`` + - Oxford Instruments binary files. + * - ``oxfordtext`` + - ``.ctf`` + - Oxford Instruments text files + * - ``edaxang`` + - ``.ang`` + - EDAX text file + +.. note:: + + Oxford Instruments and EDAX use different conventions when attaching an orthonormal frame to a crystal structure. + More information in :doc:`../userguide/conventions`. + +.. note:: + + If you have issues loading one of these files, please make an 'Issue' on GitHub, including a copy of the file. + If you are trying to load a new file type then please provide a sample file and we will try to add support for it. + +Loading EBSD Data +------------------ + +EBSD data can be loaded (for example, from a Oxford Instruments binary file) as follows: + +.. code-block:: python + + import defdap.ebsd as ebsd + + ebsd_map = ebsd.Map("path/to/ebsd_data.txt", data_type="oxfordbinary") + +Data Structure +-------------- + +The EBSD Map stores several key data structures as a :class:`defdap.utils.Datastore` object under the ``data`` attribute. +You can print a list of all the attributed stored: ``print(ebsd_map.data)``. +These structures must be present in the imported data: + +.. list-table:: + :header-rows: 1 + :widths: 8 24 + + * - Attribute + - Description + * - ``phase`` + - Phase ID map (1-based; 0 for non-indexed points). + * - ``euler_angle`` + - Euler angles stored as (3, y, x) in radians. + +These are optional, but often present. + +.. list-table:: + :header-rows: 1 + :widths: 8 24 + + * - Attribute + - Description + * - ``band_contrast`` + - Band contrast map from the EBSD scan. + * - ``band_slope`` + - Band slope map from the EBSD scan. + * - ``mean_angular_deviation`` + - Mean angular deviation (MAD) map. + +These are generated from the above data: + +.. list-table:: + :header-rows: 1 + :widths: 8 24 + + * - Attribute + - Description + * - ``orientation`` + - Quaternion map (generated from ``euler_angle``). + * - ``grain_boundaries`` + - Grain boundary set. + * - ``phase_boundaries`` + - Phase boundary set. + * - ``grains`` + - Grain ID map (1-based in the map). + * - ``KAM`` + - Kernel average misorientation (radians). + * - ``GND`` + - Geometrically necessary dislocation density map. + * - ``Nye_tensor`` + - 3x3 Nye tensor at each point. + * - ``proxigram`` + - Proxigram values for each pixel. + * - ``point`` + - Point locations used for the proxigram calculation. + * - ``GROD`` + - Grain reference orientation deviation map. + * - ``GROD_axis`` + - Grain reference orientation deviation axis map. + * - ``grain_data_to_map`` + - Derived grain list data mapped back to the pixel grid. + +Plotting and Visualization +--------------------------- + +To plot a maximum shear strain map, with scale bar: + +.. code-block:: python + + ebsd_map.plot_map('band_contrast', vmin=0, vmax=0.1, plot_scale_bar=True) + +Further Reading +--------------- + +For detailed API documentation, see :doc:`../defdap/defdap.ebsd`. \ No newline at end of file diff --git a/docs/source/userguide/experiment.rst b/docs/source/userguide/experiment.rst new file mode 100644 index 0000000..74fe604 --- /dev/null +++ b/docs/source/userguide/experiment.rst @@ -0,0 +1,3 @@ +Experiment (`defdap.experiment.Experiment`) +=================================================== + diff --git a/docs/source/userguide/hrdic.rst b/docs/source/userguide/hrdic.rst index 33e000f..11cd09c 100644 --- a/docs/source/userguide/hrdic.rst +++ b/docs/source/userguide/hrdic.rst @@ -31,26 +31,25 @@ DefDAP supports loading data from various commercial and open-source software pa .. note:: Only files from version 8 of DaVis are currently supported. - In David, ensure the decimal point chartacter in exported files is set to dot, + In DaVis, ensure the decimal point chartacter in exported files is set to dot, by going to Project → Global Options → Export → Decimal Point Character and selecting 'Dot' Loading HRDIC Data ------------------ -HRDIC data can be loaded (for example, from a LaVision DaVis test file) as follows: +HRDIC data can be loaded (for example, from a LaVision DaVis text file) as follows: .. code-block:: python import defdap.hrdic as hrdic - # Load HRDIC map from DaVis format dic_map = hrdic.Map("path/to/dic_data.txt", data_type="davis") Data Structure -------------- The HRDIC Map stores several key data structures as a :class:`defdap.utils.Datastore` object under the ``data`` attribute. -You can print a list of all the attributed stored ``print(dic_map.data)`` and a more detailed summmary is below: +You can print a list of all the attributed stored: ``print(dic_map.data)`` and a more detailed summmary is below: .. list-table:: :header-rows: 1 @@ -59,26 +58,36 @@ You can print a list of all the attributed stored ``print(dic_map.data)`` and a * - Attribute - Description * - ``coordinate`` - - pixel coordinate grid for the DIC map. + - Pixel coordinate grid for the DIC map. * - ``displacement`` - - displacement field arrays (first element is x, second element is y). + - Displacement field arrays (first element is x, second element is y). * - ``e`` - - deformation gradient components (e.g. ``Exx`` is element [0][0] ``Eyy`` is element [1][1]). + - Deformation gradient components (e.g. ``Exx`` is element [0][0] ``Eyy`` is element [1][1]). * - ``f`` - - green strain components (e.g. ``Fxx`` is element [0][0] ``Fyy`` is element [1][1]). + - Green strain components (e.g. ``Fxx`` is element [0][0] ``Fyy`` is element [1][1]). * - ``max_shear`` - - maximum shear strain field. + - Maximum shear strain field. * - ``pattern`` - - image/pattern data associated with DIC, set this with :class:`defdap.hridic.set_pattern`. + - Image/pattern data associated with DIC, set this with :class:`defdap.hridic.set_pattern`. * - ``mask`` - - validity mask for data points. - * - ``proxigram`` - - ??? + - Validity mask for data points, set this with :class:`defdap.hridic.generate_mask`. + +When linked to a :class:`defdap.ebsd.EBSDMap` object, the HRDIC Map also stores the following data structures: +.. list-table:: + :header-rows: 1 + :widths: 8 24 -- **Grains**: ``grains`` - grain/region objects derived from segmentation -- **Phase boundaries**: ``phase_boundaries`` - phase boundary features -- **Grain boundaries**: ``grain_boundaries`` - grain boundary features + * - Attribute + - Description + * - ``proxigram`` + - Distance away from grain boundary for each point in the DIC map. + * - ``grains`` + - Grain map derived from EBSD map. + * - ``phase_boundaries`` + - Phase boundries derived from EBSD map. + * - ``grain_boundaries`` + - Grain boundaries derived from EBSD map. Setting and plotting pattern ----------------------------- @@ -104,30 +113,52 @@ The path can then set with :class:`defdap.hridic.set_pattern`, where the second To plot the pattern image, use the :class:`defdap.hridic.plot_map` method with the argument 'pattern' +.. code-block:: python + dic_map.plot_map("pattern") -Setting scale ------------------- +Setting scale, crop and mask +---------------------------- -HRDIC data is stored in a pixel-based coordinate system. -To set the scale of the map so that a scale bar in microns is plotted when the map is plotted: +HRDIC data is stored in a pixel-based coordinate system, with no knowledge of the physical resolution of the data. +The scale of the map can be set so that a scale bar in microns is +plotted when the map is plotted with the argument ``plot_scale_bar=True``. +If the original image has a horizontal field width of 30 microns and a horizontal resolution of 2048 pixels, the scale can be set as follows: .. code-block:: python - # Set the scale (micron per pixel) - dic_map.set_scale(scale=2.5) + dic_map.set_scale(scale=30/2048) -Setting crop ------------------- +.. note:: + + The sub-window size of the DIC data is automatically taken into account when setting the scale. + For the above example, the scale of the DIC data (16 x 16 pixels) would give (30 / 2048) * 16 = 0.2344 microns per DIC pixel, + so a scale bar of 10 microns would be plotted as 10 / 0.2344 = 42.7 pixels long. There are normally some anomalous points near the edges of a DIC map, -so it is often desirable to crop the map to a region of interest, using this command: +so it is often desirable to crop the map to a region of interest, which can be done using this command: .. code-block:: python # Crop to region of interest dic_map.crop(left=100, right=100, top=100, bottom=100) +Finally, a mask can be generated to identify valid and invalid points in the DIC map, using the :class:`defdap.hridic.generate_mask` method. +The boolean array passed as ``mask`` should have the same shape as the DIC map, +where ``True`` values indicate invalid points and ``False`` values indicate valid points. +These are some examples of how to generate a mask based on the DIC data: + +.. code-block:: python + + #To remove data points in dic_map where max_shear is above 0.8, use: + mask = dic_map.data.max_shear > 0.8 + + #To remove data points in dic_map where e11 is above 1 or less than -1, use: + mask = (dic_map.data.e[0, 0] > 1) | (dic_map.data.e[0, 0] < -1) + + #To disable masking: + mask = None + Plotting and Visualization --------------------------- @@ -135,7 +166,6 @@ To plot a maximum shear strain map, with scale bar: .. code-block:: python - # Plot strain field dic_map.plot_map('max_shear', vmin=0, vmax=0.1, plot_scale_bar=True) Further Reading diff --git a/docs/source/userguide/inspector.rst b/docs/source/userguide/inspector.rst new file mode 100644 index 0000000..2e616ec --- /dev/null +++ b/docs/source/userguide/inspector.rst @@ -0,0 +1,24 @@ +Grain inspector (`defdap.inspector.GrainInspector`) +=================================================== + +The inspector class in DefDAP provides GUI tools for interrogating HRDIC maps on a grain-by-grain basis. + +Launching the Grain Inspector +----------------------------- + +To launch the grain inspector, use the ``grain_inspector`` method of the HRDIC Map class: + +.. code-block:: python + + dic_map.grain_inspector() + +Interface Overview +------------------ + +Labelled image of interface + + +Data Structure +-------------- + +The grain inspector stores data for each grain in \ No newline at end of file diff --git a/docs/source/userguide/linking.rst b/docs/source/userguide/linking.rst new file mode 100644 index 0000000..d7e36cb --- /dev/null +++ b/docs/source/userguide/linking.rst @@ -0,0 +1,3 @@ +Linking HRDIC to EBSD +=================================================== + From e611173e4ba7a78f87aec4e3c3769469fd71c725 Mon Sep 17 00:00:00 2001 From: Rhys Thomas Date: Fri, 20 Mar 2026 10:31:36 +0000 Subject: [PATCH 09/18] chore: update conventions doc --- docs/source/_static/IPF_up_down.png | Bin 0 -> 22387 bytes docs/source/_static/favicon.png | Bin 0 -> 697 bytes docs/source/conf.py | 7 ++++--- docs/source/userguide/conventions.rst | 8 ++------ docs/source/userguide/hrdic.rst | 5 ++++- 5 files changed, 10 insertions(+), 10 deletions(-) create mode 100644 docs/source/_static/IPF_up_down.png create mode 100644 docs/source/_static/favicon.png diff --git a/docs/source/_static/IPF_up_down.png b/docs/source/_static/IPF_up_down.png new file mode 100644 index 0000000000000000000000000000000000000000..d2eff5c80f76c40e7103d107a4388b690cd903f1 GIT binary patch literal 22387 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 literal 0 HcmV?d00001 diff --git a/docs/source/_static/favicon.png b/docs/source/_static/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..3edd1b14f5344b515361e2c76a2775a763c32885 GIT binary patch literal 697 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uuz(rC1}QWNE&Rm5z@+Ty;uvBf zxHM?5_p3yax<6ibRCon1Ic5e(=Z2_ov$wl_=D%<;gP-%SFiT;fhX{LNkW`MSy%@Xd zL0!|XCZ2;_!ZzP)`@{9`?Jm9l@bBFFmFFwZ70)XcQQ`adSfhT@@1H++dL}nq%wXAm zyLa(oWtL{gJNNG8G5s^Mk>h7-Wcc;>-z(2;1`<3e(qI1k@tNw?x+O~YYn81OU%N=J zo2Cd?;M%aoMLW+FPV{hD_OWr#b%xD1d*gDFOii!VO- z{d@L)uGXfXyZz3I0Z zPN-Vj*rfbQPfyow=vdx`o9&y$v~i+9?cxxvhkyRaWNno?{Zz Date: Fri, 20 Mar 2026 10:40:29 +0000 Subject: [PATCH 10/18] chore: update docstrings Add missing docstings, update arguments, fix typos --- defdap/base.py | 222 ++++++++++++++++++++++++++-------------- defdap/crystal_utils.py | 112 +++++++++++++++++--- defdap/ebsd.py | 149 ++++++++++++++++++++++++++- defdap/experiment.py | 93 ++++++++++++++++- defdap/file_readers.py | 101 ++++++++++-------- defdap/file_writers.py | 14 ++- defdap/hrdic.py | 81 ++++++++++++--- defdap/inspector.py | 146 +++++++++++++------------- defdap/plotting.py | 98 +++++++++++------- defdap/quat.py | 158 ++++++++++++++-------------- defdap/utils.py | 84 ++++++++++++--- 11 files changed, 905 insertions(+), 353 deletions(-) diff --git a/defdap/base.py b/defdap/base.py index f4f2401..0f2ee0e 100755 --- a/defdap/base.py +++ b/defdap/base.py @@ -45,14 +45,22 @@ class Map(ABC): """ def __init__(self, file_name, data_type=None, experiment=None, increment=None, frame=None, map_name=None): - """ + """Initialize a Map object. Parameters ---------- file_name : str - Path to EBSD file, including name, excluding extension. - data_type : str, {'OxfordBinary', 'OxfordText'} - Format of EBSD data file. + Path to data file. + data_type : str, optional + Format of data file. + experiment : defdap.experiment.Experiment, optional + Experiment object to associate with this map. + increment : defdap.experiment.Increment, optional + Increment object to associate with this map. + frame : defdap.experiment.Frame, optional + Frame object for coordinate transformations. + map_name : str, optional + Name for this map in the experiment structure. """ @@ -273,7 +281,10 @@ def calc_line_profile(self, plot, start_end, **kwargs): @report_progress("constructing neighbour network") def build_neighbour_network(self): - """Construct a list of neighbours + """Construct a network graph of neighbouring grains. + + Creates a NetworkX graph where nodes are grains and edges connect + neighbouring grains that share a boundary. """ ## TODO: fix HRDIC NN @@ -439,11 +450,17 @@ def calc_proxigram(self, num_trials=500): return trial_distances.min(axis=0) def _validate_map(self, map_name): - """Check the name exists and is a map data. + """Check that the data name exists and that it contains map data. Parameters ---------- map_name : str + Name of the map data to validate. + + Raises + ------ + ValueError + If the data name does not exist or is not map data. """ if map_name not in self.data: @@ -453,19 +470,26 @@ def _validate_map(self, map_name): raise ValueError(f'`{map_name}` is not a valid map.') def _validate_component(self, map_name, comp): - """ + """Validate component specification for map data. Parameters ---------- map_name : str - comp : int or tuple of int or str + Name of the map data. + comp : int or tuple of int or str, optional Component of the map data. This is either the tensor component (tuple of ints) or the name of a calculation to be applied e.g. 'norm', 'all_euler' or 'IPF_x'. Returns ------- - tuple of int or str + tuple of int or str or None + Validated component specification. + + Raises + ------ + ValueError + If component specification is invalid. """ order = self.data[map_name, 'order'] @@ -590,14 +614,14 @@ def plot_map(self, map_name, component=None, **kwargs): return MapPlot.create(self, map_data, **plot_params) def calc_grain_average(self, map_data, grain_ids=-1): - """Calculate grain average of any DIC map data. + """Calculate grain average of map data. Parameters ---------- map_data : numpy.ndarray Array of map data to grain average. This must be cropped! - grain_ids : list, optional - grain_ids to perform operation on, set to -1 for all grains. + grain_ids : list of int, optional + Grain IDs to perform operation on. Set to -1 for all grains. Returns ------- @@ -610,14 +634,27 @@ def calc_grain_average(self, map_data, grain_ids=-1): grain_average_data = np.zeros(len(grain_ids)) - for i, grainId in enumerate(grain_ids): - grain = self[grainId] - grainData = grain.grain_data(map_data) - grain_average_data[i] = grainData.mean() + for i, grain_id in enumerate(grain_ids): + grain = self[grain_id] + grain_data = grain.grain_data(map_data) + grain_average_data[i] = grain_data.mean() return grain_average_data def grain_data_to_map(self, name): + """Convert grain list data to map data. + + Parameters + ---------- + name : str + Map name of the grain data to convert. + + Returns + ------- + numpy.ndarray + Map data created from grain data. + + """ map_data = np.zeros(self[0].data[name].shape[:-1] + self.shape) for grain in self: for i, point in enumerate(grain.data.point): @@ -664,8 +701,8 @@ def grain_data_to_map_data(self, grain_data, grain_ids=-1, bg=0): "single value or RGB values per grain.") grain_map = np.full(mapShape, bg, dtype=grain_data.dtype) - for grainId, grain_value in zip(grain_ids, grain_data): - for point in self[grainId].data.point: + for grain_id, grain_value in zip(grain_ids, grain_data): + for point in self[grain_id].data.point: grain_map[point[1], point[0]] = grain_value return grain_map @@ -673,18 +710,18 @@ def grain_data_to_map_data(self, grain_data, grain_ids=-1, bg=0): def plot_grain_data_map( self, map_data=None, grain_data=None, grain_ids=-1, bg=0, **kwargs ): - """Plot a grain map with grains coloured by given data. The data - can be provided as a list of values per grain or as a map which - a grain average will be applied. + """Plot a grain map with grains colored by given data. The data + can be provided as a list of values per grain or as a map + from which grain averages will be calculated. Parameters ---------- map_data : numpy.ndarray, optional - Array of map data. This must be cropped! Either mapData or + Array of map data. This must be cropped! Either map_data or grain_data must be supplied. - grain_data : list or np.array, optional - Grain values. This an be a single value per grain or RGB - values. You must supply either mapData or grain_data. + grain_data : list or numpy.ndarray, optional + Grain values. This can be a single value per grain or RGB + values. You must supply either map_data or grain_data. grain_ids: list of int or int, optional IDs of grains to plot for. Use -1 for all grains in the map. bg: int or real, optional @@ -721,24 +758,29 @@ def plot_grain_data_ipf( **kwargs ): """ - Plot IPF of grain reference (average) orientations with - points coloured by grain average values from map data. + Plot IPF of grain verage orientations with points coloured + by grain average values from map data. Parameters ---------- direction : numpy.ndarray Vector of reference direction for the IPF. - map_data : numpy.ndarray - Array of map data. This must be cropped! Either mapData or + map_data : numpy.ndarray, optional + Array of map data. This must be cropped! Either map_data or grain_data must be supplied. - grain_data : list or np.array, optional - Grain values. This an be a single value per grain or RGB - values. You must supply either mapData or grain_data. - grain_ids: list of int or int, optional + grain_data : list or numpy.ndarray, optional + Grain values. This can be a single value per grain or RGB + values. You must supply either map_data or grain_data. + grain_ids : list of int, optional IDs of grains to plot for. Use -1 for all grains in the map. kwargs : dict, optional Keyword arguments passed to :func:`defdap.quat.Quat.plot_ipf` + Returns + ------- + defdap.plotting.Plot + Plot object containing the IPF. + """ # Set default plot parameters then update with any input plot_params = {} @@ -781,6 +823,18 @@ class Grain(ABC): """ def __init__(self, grain_id, owner_map, group_id): + """Initialize a Grain object. + + Parameters + ---------- + grain_id : int + Unique identifier for this grain. + owner_map : defdap.base.Map + The map that contains this grain. + group_id : uuid.UUID + Group identifier for the datastore. + + """ self.data = Datastore(group_id=group_id) self.data.add_derivative( owner_map.data, self.grain_data, @@ -885,16 +939,17 @@ def plot_outline(self, ax=None, plot_scale_bar=False, **kwargs): Parameters ---------- - ax : matplotlib.axes.Axes - axis to plot on, if not provided the current active axis is used. - plot_scale_bar : bool - plots the scale bar on the grain if true. - kwargs : dict - keyword arguments passed to :func:`defdap.plotting.GrainPlot.add_map` + ax : matplotlib.axes.Axes, optional + Axis to plot on. If not provided, the current active axis is used. + plot_scale_bar : bool, optional + If True, plots the scale bar on the grain. + kwargs : dict, optional + Keyword arguments passed to :func:`defdap.plotting.GrainPlot.add_map` Returns ------- defdap.plotting.GrainPlot + Plot object containing the grain outline. """ plot = plotting.GrainPlot(self, ax=ax) @@ -926,15 +981,15 @@ def grain_map_data(self, map_data=None, grain_data=None, bg=np.nan): Parameters ---------- - map_data : numpy.ndarray - Array of map data. This must be cropped! Either this or - 'grain_data' must be supplied and 'grain_data' takes precedence. - grain_data : numpy.ndarray + map_data : numpy.ndarray, optional + Array of map data. Either this or grain_data must be supplied. + grain_data takes precedence. + grain_data : numpy.ndarray, optional Array of data at each point in the grain. Either this or - 'mapData' must be supplied and 'grain_data' takes precedence. + map_data must be supplied. grain_data takes precedence. bg : various, optional Value to fill the background with. Must be same dtype as - input array. + input array. Default is :obj:`numpy.nan`. Returns ------- @@ -944,7 +999,7 @@ def grain_map_data(self, map_data=None, grain_data=None, bg=np.nan): """ if grain_data is None: if map_data is None: - raise ValueError("Either 'mapData' or 'grain_data' must " + raise ValueError("Either 'map_data' or 'grain_data' must " "be supplied.") else: grain_data = self.grain_data(map_data) @@ -966,29 +1021,29 @@ def grain_map_data(self, map_data=None, grain_data=None, bg=np.nan): def grain_map_data_coarse(self, map_data=None, grain_data=None, kernel_size=2, bg=np.nan): """ - Create a coarsened data map of this grain only from the given map - data. Data is coarsened using a kernel at each pixel in the - grain using only data in this grain. + Create a coarsened data map of this grain only from given map + or grain data. Pixel values are averaged within a kernel around each + pixel in the grain. Parameters ---------- - map_data : numpy.ndarray - Array of map data. This must be cropped! Either this or - 'grain_data' must be supplied and 'grain_data' takes precedence. - grain_data : numpy.ndarray - List of data at each point in the grain. Either this or - 'mapData' must be supplied and 'grain_data' takes precedence. + map_data : numpy.ndarray, optional + Array of map data. Either this or grain_data must be supplied. + grain_data takes precedence. + grain_data : numpy.ndarray, optional + Array of data at each point in the grain. Either this or + map_data must be supplied. grain_data takes precedence. kernel_size : int, optional - Size of kernel as the number of pixels to dilate by i.e 1 - gives a 3x3 kernel. + Size of kernel as the number of pixels to dilate by. For example, + 1 gives a 3x3 kernel. Default is 2 (5x5 kernel). bg : various, optional Value to fill the background with. Must be same dtype as - input array. + input array. Default is :obj:`numpy.nan`. Returns ------- numpy.ndarray - Map of this grains coarsened data. + Coarsened data map for this grain. """ grain_map_data = self.grain_map_data(map_data=map_data, grain_data=grain_data) @@ -1032,20 +1087,24 @@ def grain_map_data_coarse(self, map_data=None, grain_data=None, return grain_map_data_coarse def plot_grain_data(self, map_data=None, grain_data=None, **kwargs): - """ - Plot a map of this grain from the given map data. + """Plot a map of this grain from the given map or grain data. Parameters ---------- - map_data : numpy.ndarray - Array of map data. This must be cropped! Either this or - 'grain_data' must be supplied and 'grain_data' takes precedence. - grain_data : numpy.ndarray - List of data at each point in the grain. Either this or - 'mapData' must be supplied and 'grain_data' takes precedence. + map_data : numpy.ndarray, optional + Array of map data. Either this or grain_data must be supplied. + grain_data takes precedence. + grain_data : numpy.ndarray, optional + Array of data at each point in the grain. Either this or + map_data must be supplied. grain_data takes precedence. kwargs : dict, optional Keyword arguments passed to :func:`defdap.plotting.GrainPlot.create` + Returns + ------- + defdap.plotting.GrainPlot + Plot object containing the grain data map. + """ # Set default plot parameters then update with any input plot_params = {} @@ -1058,11 +1117,17 @@ def plot_grain_data(self, map_data=None, grain_data=None, **kwargs): return plot def _validate_list(self, list_name): - """Check the name exists and is a list data. + """Check that data name exists and is valid list data. Parameters ---------- list_name : str + Name of the list data to validate. + + Raises + ------ + ValueError + If the data name does not exist or is not list data. """ if list_name not in self.data: @@ -1072,19 +1137,26 @@ def _validate_list(self, list_name): raise ValueError(f'`{list_name}` is not a valid data.') def _validate_component(self, map_name, comp): - """ + """Validate and normalize component specification for grain data. Parameters ---------- map_name : str - comp : int or tuple of int or str - Component of the map data. This is either the + Name of the data. + comp : int or tuple of int or str, optional + Component of the data. This is either the tensor component (tuple of ints) or the name of a calculation to be applied e.g. 'norm', 'all_euler' or 'IPF_x'. Returns ------- - tuple of int or str + tuple of int or str or None + Validated component specification. + + Raises + ------ + ValueError + If component specification is invalid. """ order = self.data[map_name, 'order'] @@ -1162,12 +1234,12 @@ def plot_map(self, map_name, component=None, **kwargs): ---------- map_name : str Map data name to plot i.e. e, max_shear, euler_angle, orientation. - component : int or tuple of int or str + component : int or tuple of int or str, optional Component of the map data to plot. This is either the tensor component (int or tuple of ints) or the name of a calculation to be applied e.g. 'norm', 'all_euler' or 'IPF_x'. - kwargs - All arguments are passed to :func:`defdap.plotting.MapPlot.create`. + kwargs : dict, optional + Keyword arguments passed to :func:`defdap.plotting.MapPlot.create`. Returns ------- diff --git a/defdap/crystal_utils.py b/defdap/crystal_utils.py index 18018b5..2af7b31 100644 --- a/defdap/crystal_utils.py +++ b/defdap/crystal_utils.py @@ -33,8 +33,28 @@ def create_l_matrix(a, b, c, alpha, beta, gamma, convention=None): - """ Construct L matrix based on Page 22 of - Randle and Engle - Introduction to texture analysis""" + """ Construct L matrix. + + Parameters + ---------- + a, b, c : float + Lattice parameters in angstroms. + alpha, beta, gamma : float + Interaxial lattice angles in radians. + convention : str {'hkl', 'oi', 'tsl'}, optional + Orthonormalisation convention. If omitted, uses + ``defaults['crystal_ortho_conv']``. + + Returns + ------- + numpy.ndarray + A ``(3, 3)`` L matrix for transforming crystal directions. + + References + ---------- + Page 22 of Randle and Engle - Introduction to Texture Analysis + + """ l_matrix = np.zeros((3, 3)) cos_alpha = np.cos(alpha) @@ -85,9 +105,23 @@ def create_l_matrix(a, b, c, alpha, beta, gamma, convention=None): def create_q_matrix(l_matrix): - """ Construct matrix of reciprocal lattice vectors to transform - plane normals See C. T. Young and J. L. Lytton, J. Appl. Phys., - vol. 43, no. 4, pp. 1408–1417, 1972.""" + """Construct matrix of reciprocal lattice vectors to transform plane normals + + Parameters + ---------- + l_matrix : numpy.ndarray + Direct-lattice transform matrix of shape ``(3, 3)``. + + Returns + ------- + numpy.ndarray + A ``(3, 3)`` Q matrix whose columns are reciprocal lattice vectors. + + References + ---------- + C. T. Young and J. L. Lytton, J. Appl. Phys., vol. 43, no. 4, pp. 1408–1417, 1972. + + """ a = l_matrix[:, 0] b = l_matrix[:, 1] c = l_matrix[:, 2] @@ -103,6 +137,21 @@ def create_q_matrix(l_matrix): def check_len(val, length): + """Validate that a vector-like object has the expected length. + + Parameters + ---------- + val : collection + Value to validate. + length : int + Required number of elements. + + Raises + ------ + ValueError + If ``val`` does not contain exactly ``length`` elements. + + """ if len(val) != length: raise ValueError(f"Vector must have {length} values.") @@ -117,9 +166,9 @@ def convert_idc(in_type, *, dir=None, plane=None): Type of indices provided. If 'm' converts from Miller to Miller-Bravais, opposite for 'mb'. dir : tuple of int or equivalent, optional - Direction to convert. This OR `plane` must me provided. + Direction to convert. This OR ``plane`` must be provided. plane : tuple of int or equivalent, optional - Plane to convert. This OR `direction` must me provided. + Plane to convert. This OR ``dir`` must be provided. Returns ------- @@ -180,6 +229,30 @@ def equavlent_indicies( c_over_a=None, in_type=None ): + """Generate crystallographically equivalent planes or directions. + + Parameters + ---------- + crystal_symm : str + Crystal symmetry name, e.g. ``'hexagonal'``. + symmetries : iterable + Symmetry operators. + dir : tuple of int, optional + Direction indices. Provide either ``dir`` or ``plane``. + plane : tuple of int, optional + Plane indices. Provide either ``plane`` or ``dir``. + c_over_a : float, optional + Hexagonal ``c/a`` ratio. + in_type : str {'m', 'mb'}, optional + Input index basis. Defaults to ``'mb'`` for hexagonal crystals, + otherwise ``'m'``. + + Returns + ------- + list[tuple[int, ...]] + Equivalent indices in the requested basis. + + """ if dir is None and plane is None: raise ValueError("One of either `direction` or `plane` must be " "provided.") @@ -252,16 +325,19 @@ def project_to_orth(c_over_a, *, dir=None, plane=None, in_type='mb'): Parameters ---------- - in_type : str {'m', 'mb'} - Type of indices provided + c_over_a : float + Hexagonal lattice ratio ``c/a``. dir : tuple of int or equivalent, optional - Direction to convert. This OR `plane` must me provided. + Direction to convert. This OR ``plane`` must be provided. plane : tuple of int or equivalent, optional - Plane to convert. This OR `direction` must me provided. + Plane to convert. This OR ``dir`` must be provided. + in_type : str {'m', 'mb'} + Type of indices provided. Returns ------- - + numpy.ndarray + Projected direction or plane normal. """ if dir is None and plane is None: @@ -270,7 +346,6 @@ def project_to_orth(c_over_a, *, dir=None, plane=None, in_type='mb'): if dir is not None and plane is not None: raise ValueError("One of either `direction` or `plane` must be " "provided, not both.") - if in_type == 'mb': if dir is None: check_len(plane, 4) @@ -322,7 +397,7 @@ def pos_idc(vec): def reduce_idc(vec): """ - Reduce indices to lowest integers + Reduce indices to lowest integers. Parameters ---------- @@ -371,7 +446,7 @@ def safe_int_cast(vec, tol=1e-3): def idc_to_string(idc, brackets=None, str_type='unicode'): """ - String representation of a set of indicies. + String representation of a set of indices. Parameters ---------- @@ -399,11 +474,18 @@ def str_idx(idx, str_type='unicode'): ---------- idx : int str_type : str {'unicode', 'tex'} + Output format. ``'unicode'`` uses combining overbars for negative + values, while ``'tex'`` returns TeX math markup. Returns ------- str + Raises + ------ + ValueError + If ``idx`` is not an integer. + """ if not isinstance(idx, (int, np.integer)): raise ValueError("Index must be an integer.") diff --git a/defdap/ebsd.py b/defdap/ebsd.py index cbd3a6f..f1863d9 100755 --- a/defdap/ebsd.py +++ b/defdap/ebsd.py @@ -230,6 +230,14 @@ def c_over_a(self): @property def num_phases(self): + """Number of phases in the EBSD map. + + Returns + ------- + int or None + Number of phases, or ``None`` if no phases are defined. + + """ return len(self.phases) or None @property @@ -246,6 +254,7 @@ def primary_phase(self): @property def scale(self): + """Spatial scale of the map in microns per pixel.""" return self.step_size @report_progress("rotating EBSD data") @@ -270,6 +279,23 @@ def rotate_data(self): yield 1. def calc_euler_colour(self, map_data, phases=None, bg_colour=None): + """Calculate RGB colours using Euler colouring. + + Parameters + ---------- + map_data : numpy.ndarray + Euler-angle map data with shape ``(3, y, x)``. + phases : list of int, optional + Phase IDs to include. If omitted, include all phases. + bg_colour : numpy.ndarray, optional + Background RGB colour used where phases are excluded. + + Returns + ------- + numpy.ndarray + RGB map array with shape ``(y, x, 3)``. + + """ if phases is None: phases = self.phases phase_ids = range(len(phases)) @@ -298,6 +324,25 @@ def calc_euler_colour(self, map_data, phases=None, bg_colour=None): def calc_ipf_colour(self, map_data, direction, phases=None, bg_colour=None): + """Calculate RGB colours from IPF colouring. + + Parameters + ---------- + map_data : numpy.ndarray + Orientation data as quaternion objects. + direction : numpy.ndarray + Sample reference direction for IPF colouring. + phases : list of int, optional + Phase IDs to include. If omitted, include all phases. + bg_colour : numpy.ndarray, optional + Background RGB colour used where phases are excluded. + + Returns + ------- + numpy.ndarray + RGB map array with shape ``(y, x, 3)``. + + """ if phases is None: phases = self.phases phase_ids = range(len(phases)) @@ -586,6 +631,19 @@ def calc_quat_array(self): return quats def filter_data(self, misori_tol=5): + """Apply a Kuwahara-style quaternion filter. + + Parameters + ---------- + misori_tol : float, optional + Misorientation tolerance in degrees. + + Returns + ------- + numpy.ndarray + Last processed quadrant quaternion subset. + + """ # Kuwahara filter print("8 quadrants") misori_tol *= np.pi / 180 @@ -757,6 +815,7 @@ def find_boundaries(self, misori_tol=10): @report_progress("constructing neighbour network") def build_neighbour_network(self): + """Construct the grain-neighbour network from boundary pixels.""" # create network nn = nx.Graph() nn.add_nodes_from(self.grains) @@ -1228,7 +1287,7 @@ def __init__(self, grain_id, ebsdMap, group_id): @property def crystal_sym(self): - """Temporary""" + """Crystal symmetry name of the grain phase.""" return self.phase.crystal_structure.name def calc_average_ori(self): @@ -1291,6 +1350,14 @@ def build_mis_ori_list(self, calc_axis=False): self.mis_ori_axis_list.append(row) def calc_grod(self): + """Calculate GROD magnitude and axis for all grain points. + + Returns + ------- + tuple[numpy.ndarray, numpy.ndarray] + GROD magnitudes and GROD axis vectors. + + """ quat_comps = Quat.calc_sym_eqvs(self.data.orientation, self.crystal_sym) if self.ref_ori is None: @@ -1316,7 +1383,22 @@ def calc_grod(self): return misori, misori_axis - def calc_ipf_colour(self, grain_data, direction, bg_colour=None): + def calc_ipf_colour(self, grain_data, direction): + """Calculate grain colours from IPF colouring. + + Parameters + ---------- + grain_data : numpy.ndarray + Grain orientation data as quaternions. + direction : numpy.ndarray + Sample reference direction for IPF colouring. + + Returns + ------- + numpy.ndarray + RGB colour array for the grain. + + """ grain_colours = Quat.calc_ipf_colours( grain_data, direction, self.phase.crystal_structure.name @@ -1324,7 +1406,20 @@ def calc_ipf_colour(self, grain_data, direction, bg_colour=None): return grain_colours - def calc_euler_colour(self, grain_data, bg_colour=None): + def calc_euler_colour(self, grain_data): + """Calculate grain colours from normalised Euler angles. + + Parameters + ---------- + grain_data : numpy.ndarray + Euler-angle data with shape ``(3, n_points)``. + + Returns + ------- + numpy.ndarray + RGB colour array for the grain. + + """ if self.phase.crystal_structure.name == 'cubic': norm = np.array([2 * np.pi, np.pi / 2, np.pi / 2]) @@ -1577,17 +1672,20 @@ def calc_slip_traces(self, slip_systems=None): class BoundarySet(object): + """Container for phase and grain boundary point sets.""" # boundaries : numpy.ndarray # Map of boundaries. -1 for a boundary, 0 otherwise. # phaseBoundaries : numpy.ndarray # Map of phase boundaries. -1 for boundary, 0 otherwise. def __init__(self, ebsd_map, points_x, points_y): + """Initialise a boundary set from horizontal and vertical points.""" self.ebsd_map = ebsd_map self.points_x = set(points_x) self.points_y = set(points_y) @classmethod def from_image(cls, ebsd_map, image_x, image_y): + """Create a boundary set from boolean boundary images.""" return cls( ebsd_map, zip(*image_x.transpose().nonzero()), @@ -1596,6 +1694,7 @@ def from_image(cls, ebsd_map, image_x, image_y): @classmethod def from_boundary_segments(cls, b_segs): + """Create a boundary set from boundary segments.""" points_x = [] points_y = [] for b_seg in b_segs: @@ -1606,27 +1705,33 @@ def from_boundary_segments(cls, b_segs): @property def points(self): + """Combined boundary points from horizontal and vertical sets.""" return self.points_x.union(self.points_y) def _image(self, points): + """Convert a point collection to a boolean map image.""" image = np.zeros(self.ebsd_map.shape, dtype=bool) image[tuple(zip(*points))[::-1]] = True return image @property def image_x(self): + """Boolean image of horizontal boundary points.""" return self._image(self.points_x) @property def image_y(self): + """Boolean image of vertical boundary points.""" return self._image(self.points_y) @property def image(self): + """Boolean image of all boundary points.""" return self._image(self.points) @property def lines(self): + """Line segments representing all boundary points.""" _, _, lines = self.boundary_points_to_lines( boundary_points_x=self.points_x, boundary_points_y=self.points_y @@ -1636,6 +1741,21 @@ def lines(self): @staticmethod def boundary_points_to_lines(*, boundary_points_x=None, boundary_points_y=None): + """Convert boundary points to line segments for plotting. + + Parameters + ---------- + boundary_points_x : iterable of tuple, optional + Horizontal boundary points. + boundary_points_y : iterable of tuple, optional + Vertical boundary points. + + Returns + ------- + list or tuple + Line-segment collections for provided boundary directions. + + """ boundary_data = {} if boundary_points_x is not None: boundary_data['x'] = boundary_points_x @@ -1666,7 +1786,10 @@ def boundary_points_to_lines(*, boundary_points_x=None, class BoundarySegment(object): + """Boundary segment between two neighbouring grains.""" + def __init__(self, ebsdMap, grain1, grain2): + """Initialise a boundary segment for a grain pair.""" self.ebsdMap = ebsdMap self.grain1 = grain1 @@ -1694,6 +1817,18 @@ def __len__(self): return len(self.boundary_points_x) + len(self.boundary_points_y) def addBoundaryPoint(self, point, kind, owner_grain): + """Add a boundary point and its owner grain. + + Parameters + ---------- + point : tuple[int, int] + Boundary point coordinates. + kind : int + Boundary type: ``0`` for horizontal, ``1`` for vertical. + owner_grain + Grain that owns the point side. + + """ if kind == 0: self.boundary_points_x.append(point) self.boundary_point_owners_x.append(owner_grain is self.grain1) @@ -1750,6 +1885,14 @@ def boundary_lines(self): return lines def misorientation(self): + """Calculate misorientation angle and axis between neighbouring grains. + + Returns + ------- + tuple[float, numpy.ndarray] + Misorientation angle (radians) and unit rotation axis. + + """ mis_ori, minSymm = self.grain1.ref_ori.mis_ori( self.grain2.ref_ori, self.ebsdMap.crystal_sym, return_quat=2 ) diff --git a/defdap/experiment.py b/defdap/experiment.py index 5642aa0..9f5254d 100644 --- a/defdap/experiment.py +++ b/defdap/experiment.py @@ -19,19 +19,49 @@ class Experiment(object): + """Container for map increments and frame transformations.""" + def __init__(self): + """Initialise an empty experiment.""" self.frame_relations = {} self.increments = [] def __getitem__(self, key): + """Return an increment by index.""" return self.increments[key] def add_increment(self, **kwargs): + """Create and append a new increment. + + Parameters + ---------- + **kwargs + Metadata stored on the increment. + + Returns + ------- + Increment + Newly created increment. + + """ inc = Increment(self, **kwargs) self.increments.append(inc) return inc def iter_over_maps(self, map_name): + """Iterate over increments containing a named map. + + Parameters + ---------- + map_name : str + Map name to look up. + + Yields + ------ + tuple[int, object] + Increment index and map object. + + """ for i, inc in enumerate(self.increments): map_obj = inc.maps.get(map_name) if map_obj is None: @@ -39,9 +69,30 @@ def iter_over_maps(self, map_name): yield i, map_obj def link_frames(self, frame_1, frame_2, transform_props): + """Store transformation properties between two frames.""" self.frame_relations[(frame_1, frame_2)] = transform_props def get_frame_transform(self, frame_1, frame_2): + """Estimate the transform mapping ``frame_1`` to ``frame_2``. + + Parameters + ---------- + frame_1 : Frame + Source frame. + frame_2 : Frame + Target frame. + + Returns + ------- + skimage.transform._geometric.GeometricTransform + Estimated transform object. + + Raises + ------ + ValueError + If frames are not linked or relations are inconsistent. + + """ transform_lookup = { 'piecewise_affine': tf.PiecewiseAffineTransform, 'projective': tf.ProjectiveTransform, @@ -76,7 +127,7 @@ def get_frame_transform(self, frame_1, frame_2): return transform def warp_image(self, map_data, frame_1, frame_2, crop=True, **kwargs): - """Warps a map to the DIC frame. + """Warp image data from ``frame_1`` into ``frame_2``. Parameters ---------- @@ -88,9 +139,9 @@ def warp_image(self, map_data, frame_1, frame_2, crop=True, **kwargs): All other arguments passed to :func:`skimage.transform.warp`. Returns - ---------- + ------- numpy.ndarray - Map (i.e. EBSD map data) warped to the DIC frame. + Warped map data. """ transform = self.get_frame_transform(frame_2, frame_1) @@ -130,6 +181,25 @@ def warp_lines(self, lines, frame_1, frame_2): return lines def warp_points(self, points_img, frame_1, frame_2, **kwargs): + """Warp point-image data between frames and return point coordinates. + + Parameters + ---------- + points_img : numpy.ndarray + Binary/float image containing points to warp. + frame_1 : Frame + Source frame. + frame_2 : Frame + Target frame. + **kwargs + Additional keyword arguments passed to ``warp_image``. + + Returns + ------- + iterator + Iterator of ``(x, y)`` point coordinates in the target frame. + + """ input_shape = np.array(points_img.shape) points_img = self.warp_image(points_img, frame_1, frame_2, crop=False, **kwargs) @@ -154,8 +224,19 @@ def warp_points(self, points_img, frame_1, frame_2, **kwargs): class Increment(object): - # def __init__(self, experiment, **kwargs): + """A single experiment increment containing one or more maps.""" + def __init__(self, experiment, **kwargs): + """Initialise an increment. + + Parameters + ---------- + experiment : Experiment + Parent experiment. + **kwargs + Increment metadata. + + """ self.maps = {} # ex: (name, map, frame) @@ -166,11 +247,15 @@ def __init__(self, experiment, **kwargs): self.metadata = kwargs def add_map(self, name, map_obj): + """Add a named map object to this increment.""" self.maps[name] = map_obj class Frame(object): + """Frame containing homologous points for map registration.""" + def __init__(self): + """Initialise an empty frame.""" # self.maps = [] self.homog_points = [] diff --git a/defdap/file_readers.py b/defdap/file_readers.py index 123fd9b..4a57859 100644 --- a/defdap/file_readers.py +++ b/defdap/file_readers.py @@ -28,9 +28,7 @@ class EBSDDataLoader(ABC): - """Class containing methods for loading and checking EBSD data - - """ + """Base class for loading and validating EBSD datasets.""" def __init__(self) -> None: # required metadata self.loaded_metadata = { @@ -87,6 +85,7 @@ def check_metadata(self) -> None: assert type(phase) is Phase def check_data(self) -> None: + """Validate the shape of required loaded EBSD data arrays.""" shape = self.loaded_metadata['shape'] assert self.loaded_data.phase.shape == shape @@ -100,13 +99,12 @@ def load(self, file_name: pathlib.Path) -> None: class OxfordTextLoader(EBSDDataLoader): def load(self, file_name: pathlib.Path) -> None: - """ Read an Oxford Instruments .ctf file, which is a HKL single - orientation file. + """Read an Oxford Instruments ``.ctf`` orientation file. Parameters ---------- - file_name - Path to file + file_name : pathlib.Path + Path to file. """ # open data file and read in metadata @@ -236,12 +234,12 @@ def parse_phase() -> Phase: class EdaxAngLoader(EBSDDataLoader): def load(self, file_name: pathlib.Path) -> None: - """ Read an EDAX .ang file. + """Read an EDAX ``.ang`` file. Parameters ---------- - file_name - Path to file + file_name : pathlib.Path + Path to file. """ # open data file and read in metadata @@ -347,6 +345,24 @@ def load(self, file_name: pathlib.Path) -> None: @staticmethod def parse_phase(lines) -> Phase: + """Parse phase metadata lines from an EDAX ``.ang`` header. + + Parameters + ---------- + lines : list of str + Header lines describing a single phase. + + Returns + ------- + Phase + Parsed phase definition. + + Raises + ------ + ValueError + If an unsupported crystal symmetry is encountered. + + """ for line in lines: line = line.split() @@ -382,8 +398,8 @@ def load(self, file_name: pathlib.Path) -> None: Parameters ---------- - file_name - Path to file + file_name : pathlib.Path + Path to file. """ self.load_oxford_cpr(file_name) @@ -396,8 +412,8 @@ def load_oxford_cpr(self, file_name: pathlib.Path) -> None: Parameters ---------- - file_name - Path to file + file_name : pathlib.Path + Path to file. """ comment_char = ';' @@ -505,12 +521,12 @@ def parse_line(line: str, group_dict: Dict) -> None: self.data_format = np.dtype(data_format) def load_oxford_crc(self, file_name: pathlib.Path) -> None: - """Read binary EBSD data from an Oxford Instruments .crc file + """Read binary EBSD data from an Oxford Instruments ``.crc`` file. Parameters ---------- - file_name - Path to file + file_name : pathlib.Path + Path to file. """ shape = self.loaded_metadata['shape'] @@ -577,7 +593,7 @@ def load(self, data_dict: Dict[str, Any]) -> None: Parameters ---------- - data_dict + data_dict : dict Dictionary with keys: 'step_size' 'phases' @@ -603,9 +619,7 @@ def load(self, data_dict: Dict[str, Any]) -> None: class DICDataLoader(ABC): - """Class containing methods for loading and checking HRDIC data - - """ + """Base class for loading and validating DIC datasets.""" def __init__(self, file_type : str = '') -> None: self.file_type = file_type self.loaded_metadata = { @@ -656,8 +670,7 @@ def checkMetadata(self) -> None: return def check_data(self) -> None: - """ Calculate size of map from loaded data and check it matches - values from metadata. + """Validate DIC coordinate spacing and map shape against metadata. """ # check binning @@ -687,12 +700,12 @@ def load(self, file_name: pathlib.Path) -> None: class DavisLoader(DICDataLoader): def load(self, file_name: pathlib.Path) -> None: - """ Load from Davis .txt file. + """Load a DaVis ``.txt`` displacement file. Parameters ---------- - file_name - Path to file + file_name : pathlib.Path + Path to file. """ if not file_name.is_file(): @@ -724,12 +737,12 @@ def load(self, file_name: pathlib.Path) -> None: @staticmethod def load_davis_image_data(file_name: pathlib.Path) -> np.ndarray: - """ A .txt file from DaVis containing a 2D image + """Load a DaVis ``.txt`` file containing a 2D image. Parameters ---------- - file_name - Path to file + file_name : pathlib.Path + Path to file. Returns ------- @@ -748,12 +761,12 @@ def load_davis_image_data(file_name: pathlib.Path) -> np.ndarray: class OpenPivTextLoader(DICDataLoader): def load(self, file_name: pathlib.Path) -> None: - """ Load from Open PIV .txt file. + """Load an OpenPIV .txt file. Parameters ---------- - file_name - Path to file + file_name : pathlib.Path + Path to file. """ if not file_name.is_file(): @@ -798,12 +811,12 @@ def load(self, file_name: pathlib.Path) -> None: class OpenPivBinaryLoader(DICDataLoader): def load(self, file_name: pathlib.Path) -> None: - """ Load from Open PIV .npz file. + """Load an OpenPIV binary ``.npz`` file. Parameters ---------- - file_name - Path to file + file_name : pathlib.Path + Path to file. """ if not file_name.is_file(): @@ -834,12 +847,12 @@ def load(self, file_name: pathlib.Path) -> None: class PyValeLoader(DICDataLoader): def load(self, file_name: pathlib.Path) -> None: - """ Load from PyVale csv or binary file. + """Load a PyVale CSV or binary file. Parameters ---------- - file_name - Path to file + file_name : pathlib.Path + Path to file. """ if not file_name.is_file(): @@ -921,16 +934,16 @@ def read_until_string( Parameters ---------- - file + file : TextIO An open python text file object. - term_string + term_string : str String to terminate reading. - comment_char + comment_char : str, optional Character at start of a comment line to ignore. - line_process + line_process : callable, optional Function to apply to each line when loaded. - exact - A line must exactly match `termString` to stop. + exact : bool, optional + If ``True``, a line must exactly match ``term_string`` to stop. Returns ------- diff --git a/defdap/file_writers.py b/defdap/file_writers.py index ea1d440..49b0bf8 100644 --- a/defdap/file_writers.py +++ b/defdap/file_writers.py @@ -22,6 +22,8 @@ class EBSDDataWriter(object): + """Base class for writing EBSD data to supported file formats.""" + def __init__(self) -> None: self.metadata = { 'shape': (0, 0), @@ -49,16 +51,20 @@ def get_writer(datatype: str) -> "Type[EBSDDataWriter]": class OxfordTextWriter(EBSDDataWriter): def write(self, file_name: str, file_dir: str = "") -> None: - """ Write an Oxford Instruments .ctf file, which is a HKL single - orientation file. + """Write an Oxford Instruments ``.ctf`` file. Parameters ---------- - file_name + file_name : str File name. - file_dir + file_dir : str, optional Path to file. + Raises + ------ + FileExistsError + If the destination file already exists. + """ # check output file diff --git a/defdap/hrdic.py b/defdap/hrdic.py index 064bcea..ca49e10 100755 --- a/defdap/hrdic.py +++ b/defdap/hrdic.py @@ -178,10 +178,12 @@ def __init__(self, *args, **kwargs): @property def original_shape(self): + """Original map shape before cropping as ``(y, x)``.""" return self.ydim, self.xdim @property def crystal_sym(self): + """Crystal symmetry of the linked EBSD map.""" return self.ebsd_map.crystal_sym @report_progress("loading HRDIC data") @@ -217,7 +219,7 @@ def load_data(self, file_name, data_type=None): f"sub-window size: {self.binning} x {self.binning} pixels)") def load_corr_val_data(self, file_name, data_type=None): - """Load correlation value for DIC data + """Load correlation value map for the DIC data. Parameters ---------- @@ -243,7 +245,7 @@ def load_corr_val_data(self, file_name, data_type=None): "Dimensions of imported data and dic data do not match" def retrieve_name(self): - """Gets the first name assigned to the a map, as a string + """Get the first variable name bound to this map instance. """ for fi in reversed(inspect.stack()): @@ -274,7 +276,7 @@ def scale(self): return self.bse_scale * self.binning def print_stats_table(self, percentiles, components): - """Print out a statistics table for a DIC map + """Print a statistics table for selected DIC map components. Parameters ---------- @@ -363,9 +365,15 @@ def crop(self, map_data, binning=None): Parameters ---------- map_data : numpy.ndarray - Bap data to crop. - binning : int - True if mapData is binned i.e. binned BSE pattern. + Map data to crop. + binning : int, optional + Scale factor applied to crop distances (for binned data). + + Returns + ------- + numpy.ndarray + Cropped map data. + """ binning = 1 if binning is None else binning @@ -407,12 +415,12 @@ def check_ebsd_linked(self): """Check if an EBSD map has been linked. Returns - ---------- + ------- bool Returns True if EBSD map linked. Raises - ---------- + ------ Exception If EBSD map not linked. @@ -422,7 +430,7 @@ def check_ebsd_linked(self): return True def warp_to_dic_frame(self, map_data, **kwargs): - """Warps a map to the DIC frame. + """Warp map data into the DIC frame. Parameters ---------- @@ -432,7 +440,7 @@ def warp_to_dic_frame(self, map_data, **kwargs): All other arguments passed to :func:`defdap.experiment.Experiment.warp_map`. Returns - ---------- + ------- numpy.ndarray Map (i.e. EBSD map data) warped to the DIC frame. @@ -501,7 +509,18 @@ def calc_mask(self, mask=None, dilation=0): return mask def mask(self, map_data): - """ Values set to False in mask will be set to nan in map. + """Apply the current mask to map data. + + Parameters + ---------- + map_data : numpy.ndarray + Data to mask. + + Returns + ------- + numpy.ndarray or numpy.ma.MaskedArray + Input data if no mask is set, otherwise masked data. + """ if self.data.mask is None: return map_data @@ -518,7 +537,8 @@ def set_pattern(self, img_path, window_size): Path to image. window_size : int Size of pixel in pattern image relative to pixel size of DIC data - i.e 1 means they are the same size and 2 means the pixels in + + i.e 1 means they are the same size and 2 means the pixels in the pattern are half the size of the dic data. """ @@ -527,6 +547,22 @@ def set_pattern(self, img_path, window_size): self.data['pattern', 'binning'] = window_size def load_pattern(self): + """Load and validate the linked pattern image. Set a pattern image with + :func:`defdap.hrdic.Map.set_pattern`. + + Returns + ------- + numpy.ndarray + Pattern image array. + + Raises + ------ + FileNotFoundError + If no pattern path has been configured. + ValueError + If image dimensions do not match expected binned size. + + """ print('Loading img') path = self.data.get_metadata('pattern', 'path') binning = self.data.get_metadata('pattern', 'binning', 1) @@ -809,7 +845,7 @@ def calc_slip_traces(self, slip_systems=None): """Calculates list of slip trace angles based on EBSD grain orientation. Parameters - ------- + ---------- slip_systems : defdap.crystal.SlipSystem, optional """ @@ -828,7 +864,7 @@ def calc_slip_bands(self, grain_map_data, thres=None, min_dist=None): Minimum angle between bands. Returns - ---------- + ------- list(float) Detected slip band angles @@ -877,13 +913,28 @@ def calc_slip_bands(self, grain_map_data, thres=None, min_dist=None): class BoundarySet(object): + """Boundary points and line segments represented in DIC coordinates.""" + def __init__(self, dic_map, points, lines): + """Initialise a boundary set. + + Parameters + ---------- + dic_map : defdap.hrdic.Map + Parent DIC map. + points : iterable + Boundary point coordinates. + lines : iterable + Boundary line segments. + + """ self.dic_map = dic_map self.points = set(points) self.lines = lines @classmethod def from_ebsd_boundaries(cls, dic_map, ebsd_boundaries): + """Create DIC frame boundaries by warping EBSD boundaries.""" if len(ebsd_boundaries.points) == 0: return cls(dic_map, [], []) @@ -898,10 +949,12 @@ def from_ebsd_boundaries(cls, dic_map, ebsd_boundaries): return cls(dic_map, points, lines) def _image(self, points): + """Convert boundary points to a boolean image.""" image = np.zeros(self.dic_map.shape, dtype=bool) image[tuple(zip(*points))[::-1]] = True return image @property def image(self): + """Boolean image of all boundary points.""" return self._image(self.points) diff --git a/defdap/inspector.py b/defdap/inspector.py index 17e70ba..e47408e 100644 --- a/defdap/inspector.py +++ b/defdap/inspector.py @@ -26,29 +26,27 @@ class GrainInspector: - """ - Class containing the interactive grain inspector tool for slip trace analysis - and relative displacement ratio analysis. - - """ + """Interactive tool for slip-trace and relative displacement analysis.""" def __init__(self, selected_dic_map: 'hrdic.Map', vmax: float, correction_angle: float = 0, rdr_line_length: int = 3): - """ + """Initialise the grain inspector. Parameters ---------- - selected_dic_map + selected_dic_map : hrdic.Map DIC map to run grain inspector on. - vmax + vmax : float Maximum effective shear strain in colour scale. - correction_angle + correction_angle : float, optional Angle (in degrees) to subtract from drawn line angle. - rdr_line_length - Length on lines perpendicular to slip trace (can be any odd number above default 3). + rdr_line_length : int, optional + Length of lines perpendicular to the slip trace used for RDR + calculation. Can be any odd number greater than or equal to 3. + """ # Initialise some values self.grain_id = 0 @@ -117,12 +115,14 @@ def __init__(self, def goto_grain(self, event: int, plot): - """ Go to a specified grain ID. + """Go to a specified grain ID. Parameters ---------- - event + event : int Grain ID to go to. + plot + Plot callback argument. """ # Go to grain ID specified in event @@ -135,12 +135,14 @@ def goto_grain(self, def save_line(self, event: np.ndarray, plot): - """ Save the start point, end point and angle of drawn line into the grain. + """Save a drawn line to the selected grain. Parameters ---------- - event - Start x, start y, end x, end y point of line passed from drawn line. + event : numpy.ndarray + Start and end coordinates passed from the drawn line callback. + plot + Plot callback argument. """ @@ -167,15 +169,12 @@ def save_line(self, def group_lines(self, grain: 'hrdic.Grain' = None): - """ - Group the lines drawn in the current grain item using a mean shift algorithm, - save the average angle and then detect the active slip planes. - + """Group drawn lines by angle and detect candidate slip planes. groups_list is a list of line groups: [id, angle, [slip plane id], [angular deviation] Parameters ---------- - grain + grain : hrdic.Grain, optional Grain for which to group the slip lines. """ @@ -221,9 +220,7 @@ def group_lines(self, def clear_all_lines(self, event, plot): - """ Clear all lines in a given grain. - - """ + """Clear all saved lines and groups for the selected grain.""" self.selected_dic_grain.points_list = [] self.selected_dic_grain.groups_list = [] @@ -232,12 +229,14 @@ def clear_all_lines(self, def remove_line(self, event: int, plot): - """ Remove single line [runs after submitting a text box]. + """Remove a single saved line. Parameters ---------- - event + event : int Line ID to remove. + plot + Plot callback argument. """ # Remove single line @@ -246,9 +245,7 @@ def remove_line(self, self.redraw() def redraw(self): - """Draw items which need to be redrawn when changing grain ID. - - """ + """Redraw the grain inspector for the currently selected grain.""" # Plot max shear for grain self.max_shear_axis.clear() @@ -279,10 +276,7 @@ def redraw(self): self.redraw_line() def redraw_line(self): - """ - Draw items which need to be redrawn when adding a line. - - """ + """Redraw line, group, and slip-trace overlays for the grain.""" # Write lines text and draw lines title_text = 'List of lines' lines_text = 'ID x0 y0 x1 y1 Angle Group\n' \ @@ -336,12 +330,14 @@ def redraw_line(self): def run_rdr_group(self, event: int, plot): - """ Run RDR on a specified group, upon submitting a text box. + """Run RDR for a specified line group. Parameters ---------- - event + event : int Group ID specified from text box. + plot + Plot callback argument. """ # Run RDR for group of lines @@ -352,9 +348,7 @@ def run_rdr_group(self, def batch_run_sta(self, event, plot): - """ Run slip trace analysis on all grains which hve slip trace lines drawn. - - """ + """Run slip-trace analysis for all grains with saved lines.""" # Print header print("Grain\tEul1\tEul2\tEul3\tMaxSF\tGroup\tAngle\tSystem\tDev\tRDR") @@ -375,16 +369,16 @@ def calc_rdr(self, grain, group: int, show_plot: bool = True): - """ Calculates the relative displacement ratio for a given grain and group. + """Calculate the relative displacement ratio for a grain group. Parameters ---------- grain DIC grain to run RDR on. - group - group ID to run RDR on. - show_plot - if True, show plot window. + group : int + Group ID to run RDR on. + show_plot : bool, optional + If ``True``, show the RDR plot window. """ @@ -443,25 +437,24 @@ def plot_rdr(self, x_list: List[List[int]], y_list: List[List[int]], lin_reg_result: List): - """ - Plot rdr figure, including location of perpendicular lines and scatter plot of ucentered vs vcentered. + """Plot the RDR calculation summary for a group of lines in a grain. Parameters ---------- grain DIC grain to plot. - group + group : int Group ID to plot. - u_list - List of ucentered values. - v_list - List of vcentered values. - x_list - List of all x values. - y_list - List of all y values. + u_list : list of float + List of centred ``u`` values. + v_list : list of float + List of centred ``v`` values. + x_list : list of list of int + Sampled x coordinates. + y_list : list of list of int + Sampled y coordinates. lin_reg_result - Results from linear regression of ucentered vs vcentered + Linear regression result for centred ``u`` against centred ``v``. {slope, intercept, rvalue, pvalue, stderr}. """ @@ -590,22 +583,33 @@ def plot_rdr(self, def update_filename(self, event: str, plot): - """ Update class variable filename, based on text input from textbox handler. + """Update the output filename from textbox input. - event: - Text in textbox. + Parameters + ---------- + event : str + Text entered in the textbox. + plot + Plot callback argument. """ self.filename = event - def save_file(self, - event, - plot): - """ Save a file which contains definitions of slip lines drawn in grains - [(x0, y0, x1, y1), angle, groupID] - and groups of lines, defined by an average angle and identified sip plane - [groupID, angle, [slip plane id(s)], [angular deviation(s)]] + def save_file(self, event, plot): + """Save drawn line and group definitions to a text file. + + Lines drawn are saved in the following format: + [(x0, y0, x1, y1), angle, groupID]. + Groups of lines are saved in the following format: + [groupID, angle, [slip plane id(s)], [angular deviation(s)]]. + + Parameters + ---------- + event + Plot callback event argument. + plot + Plot callback argument. """ @@ -630,10 +634,14 @@ def save_file(self, def load_file(self, event, plot): - """ Load a file which contains definitions of slip lines drawn in grains - [(x0, y0, x1, y1), angle, groupID] - and groups of lines, defined by an average angle and identified sip plane - [groupID, angle, [slip plane id(s)], [angular deviation(s)]] + """Load drawn line and group definitions from a text file. + + Parameters + ---------- + event + Plot callback event argument. + plot + Plot callback argument. """ diff --git a/defdap/plotting.py b/defdap/plotting.py index d490bfd..cd5536a 100644 --- a/defdap/plotting.py +++ b/defdap/plotting.py @@ -36,9 +36,7 @@ class Plot(object): - """ Class used for creating and manipulating plots. - - """ + """Base class for creating and manipulating plots.""" def __init__(self, ax=None, ax_params={}, fig=None, make_interactive=False, title=None, **kwargs): @@ -87,23 +85,34 @@ def check_interactive(self): raise Exception("Plot must be interactive") def add_event_handler(self, eventName, eventHandler): + """Register an interactive matplotlib event handler. + + Parameters + ---------- + eventName : str + Matplotlib event name. + eventHandler : callable + Callback receiving ``(event, plot)``. + + """ self.check_interactive() self.fig.canvas.mpl_connect(eventName, lambda e: eventHandler(e, self)) def add_axes(self, loc, proj='2d'): - """Add axis to current plot + """Add an axis to the current plot. Parameters ---------- - loc + loc : tuple Location of axis. - proj : str, {2d, 3d} + proj : str, {'2d', '3d'} 2D or 3D projection. Returns ------- - matplotlib.Axes.axes + matplotlib.axes.Axes + Created axes object. """ if proj == '2d': @@ -151,7 +160,7 @@ def add_text_box(self, label, submit_handler=None, change_handler=None, loc=(0.8 Returns ------- - matplotlotlib.widgets.TextBox + matplotlib.widgets.TextBox """ self.check_interactive() @@ -192,13 +201,13 @@ def add_arrow(self, start_end, persistent=False, clear_previous=True, label=None Parameters ---------- - start_end: 4-tuple + start_end : tuple[float, float, float, float] Starting (x, y), Ending (x, y). - persistent : + persistent : bool, optional If persistent, do not clear arrow with clearPrev. - clear_previous : + clear_previous : bool, optional Clear all non-persistent arrows. - label + label : str, optional Label to place near arrow. """ @@ -306,7 +315,7 @@ def clear(self): self.draw() def draw(self): - """Draw plot + """Draw the plot. """ self.fig.canvas.draw() @@ -346,6 +355,7 @@ def __init__(self, calling_map, fig=None, ax=None, ax_params={}, self.set_empty_state() def set_empty_state(self): + """Reset map-plot layer and annotation state.""" self.img_layers = [] self.highlights_layer_id = None self.points_layer_ids = [] @@ -425,11 +435,6 @@ def add_grain_boundaries(self, kind="pixel", boundaries=None, colour=None, of coordinates representing the start and end of each boundary segment. If not provided the boundaries are loaded from the calling map. - - boundaries : various, defdap.ebsd.BoundarySet - Boundaries to plot. If not provided the boundaries are loaded from - the calling map. - colour : various One of: - Colour of all boundaries as a string (only option pixel kind) @@ -444,8 +449,8 @@ def add_grain_boundaries(self, kind="pixel", boundaries=None, colour=None, Returns ------- - Various : - matplotlib.image.AxesImage if type is pixel + matplotlib.artist.Artist + Added image or line collection artist. """ if colour is None: @@ -737,6 +742,7 @@ def __init__(self, calling_grain, fig=None, ax=None, ax_params={}, self.set_empty_state() def set_empty_state(self): + """Reset grain-plot layer state.""" self.img_layers = [] self.ax.set_xticks([]) @@ -969,9 +975,7 @@ def create( class PolePlot(Plot): - """ Class for creating an inverse pole figure plot. - - """ + """Plot class for inverse pole figure visualisation.""" def __init__(self, plot_type, crystal_sym, projection=None, fig=None, ax=None, ax_params={}, make_interactive=False, @@ -1251,6 +1255,26 @@ def add_legend( @staticmethod def _validateProjection(projection_in, validate_default=False): + """Validate and resolve a projection specification. + + Parameters + ---------- + projection_in : str or callable or None + Projection name/function. + validate_default : bool, optional + If ``True``, validate only explicit defaults. + + Returns + ------- + callable + Projection function. + + Raises + ------ + ValueError + If no valid default projection is available. + + """ if validate_default: default_projection = None else: @@ -1351,21 +1375,19 @@ def lambert_project(*args): class HistPlot(Plot): - """ Class for creating a histogram. - - """ + """Plot class for histogram visualisation.""" def __init__(self, plot_type="scatter", axes_type="linear", density=True, fig=None, ax=None, ax_params={}, make_interactive=False, **kwargs): - """Initialise a histogram plot + """Initialise a histogram plot. Parameters ---------- - plot_type: str, {'scatter', 'bar', 'step'} - Type of plot to use + plot_type : str, {'scatter', 'bar', 'step'} + Type of plot to use. axes_type : str, {'linear', 'logx', 'logy', 'loglog', 'None'}, optional If 'log' is specified, logarithmic scale is used. - density : + density : bool, optional If true, histogram is normalised such that the integral sums to 1. fig : matplotlib.figure.Figure Matplotlib figure to plot on. @@ -1411,7 +1433,7 @@ def __init__(self, plot_type="scatter", axes_type="linear", density=True, fig=No def add_hist(self, hist_data, bins=100, range=None, line='o', label=None, **kwargs): - """Add a histogram to the current plot + """Add a histogram to the current plot. Parameters ---------- @@ -1482,14 +1504,14 @@ def create( ax_params : Passed to defdap.plotting.Plot as ax_params. plot : defdap.plotting.HistPlot - Plot where histgram is created. If none, a new plot is created. + Plot where histogram is created. If ``None``, a new plot is created. make_interactive : bool, optional If true, make plot interactive. - plot_type: str, {'scatter', 'bar', 'barfilled', 'step'} - Type of plot to use + plot_type : str, {'scatter', 'bar', 'step'} + Type of plot to use. axes_type : str, {'linear', 'logx', 'logy', 'loglog', 'None'}, optional If 'log' is specified, logarithmic scale is used. - density : + density : bool, optional If true, histogram is normalised such that the integral sums to 1. bins : int Number of bins to use for histogram. @@ -1518,9 +1540,7 @@ def create( class CrystalPlot(Plot): - """ Class for creating a 3D plot for plotting unit cells. - - """ + """Plot class for 3D unit-cell visualisation.""" def __init__(self, fig=None, ax=None, ax_params={}, make_interactive=False, **kwargs): @@ -1558,7 +1578,7 @@ def __init__(self, fig=None, ax=None, ax_params={}, ) def add_verts(self, verts, **kwargs): - """Plots planes, defined by the vertices provided. + """Plot planes defined by the provided vertices. Parameters ---------- diff --git a/defdap/quat.py b/defdap/quat.py index 1af9a6c..c5a9e19 100755 --- a/defdap/quat.py +++ b/defdap/quat.py @@ -41,9 +41,9 @@ def __init__(self, *args, allow_southern: Optional[bool] = False) -> None: Parameters ---------- *args - Variable length argument list. - allow_southern - if False, move quat to northern hemisphere. + Either four quaternion coefficients or one length-4 iterable. + allow_southern : bool, optional + If ``False``, move the quaternion to the northern hemisphere. """ # construct with array of quat coefficients @@ -71,11 +71,11 @@ def from_euler_angles(cls, ph1: float, phi: float, ph2: float) -> 'Quat': Parameters ---------- - ph1 + ph1 : float First Euler angle, rotation around Z in radians. - phi + phi : float Second Euler angle, rotation around new X in radians. - ph2 + ph2 : float Third Euler angle, rotation around new Z in radians. Returns @@ -102,9 +102,9 @@ def from_axis_angle(cls, axis: np.ndarray, angle: float) -> 'Quat': Parameters ---------- - axis + axis : numpy.ndarray Axis that the rotation is applied around. - angle + angle : float Magnitude of rotation in radians. Returns @@ -259,11 +259,11 @@ def __mul__(self, right: 'Quat', allow_southern: bool = False) -> 'Quat': raise TypeError("{:} - {:}".format(type(self), type(right))) def dot(self, right: 'Quat') -> float: - """ Calculate dot product between two quaternions. + """Calculate the dot product between two quaternions. Parameters ---------- - right + right : Quat Right hand quaternion. Returns @@ -337,7 +337,7 @@ def transform_vector( vector: Union[Tuple, List, np.ndarray] ) -> np.ndarray: """ - Transforms vector by the quaternion. For passive EBSD quaterions + Transforms a vector by the quaternion. For passive EBSD quaterions this is a transformation from sample space to crystal space. Perform on conjugate of quaternion for crystal to sample. For a quaternion representing a passive rotation from CS1 to CS2 and a @@ -373,27 +373,25 @@ def mis_ori( sym_group: str, return_quat: Optional[int] = 0 ) -> Tuple[float, 'Quat']: - """ - Calculate misorientation angle between 2 orientations taking - into account the symmetries of the crystal structure. - Angle is 2*arccos(output). + """Calculate minimum misorientation between two quaternions, + accounting for crystal symmetry. The misorientation angle is + ``2 * arccos(m)``, where ``m`` is the minimum misorientation + value. Parameters ---------- - right + right : Quat Orientation to find misorientation to. - sym_group + sym_group : str Crystal type (cubic, hexagonal). - return_quat + return_quat : int, optional What to return: 0 for minimum misorientation, 1 for symmetric equivalent with minimum misorientation, 2 for both. Returns ------- - float - Minimum misorientation. - defdap.quat.Quat - Symmetric equivalent orientation with minimum misorientation. + float or Quat or tuple[float, Quat] + Return type depends on ``return_quat``. """ if isinstance(right, type(self)): @@ -453,19 +451,19 @@ def plot_ipf( marker_size: Optional[float] = 40, **kwargs ) -> 'plotting.PolePlot': - """ - Plot IPF of orientation, with relation to specified sample direction. + """Plot the orientation on an inverse pole figure, + with relation to the specified sample direction. Parameters ---------- - direction + direction : numpy.ndarray Sample reference direction for IPF. - sym_group + sym_group : str Crystal type (cubic, hexagonal). - projection - Projection to use. Either string (stereographic or lambert) - or a function. - plot + projection : str or callable, optional + Projection to use. Either a string (``stereographic`` or + ``lambert``) or a function. + plot : defdap.plotting.Plot, optional Defdap plot to plot on. fig Figure to plot on, if not provided the current @@ -473,21 +471,26 @@ def plot_ipf( ax Axis to plot on, if not provided the current active axis is used. - make_interactive - If true, make the plot interactive. - plot_colour_bar : bool - If true, plot a colour bar next to the map. - clabel : str + plot_colour_bar : bool, optional + If ``True``, plot a colour bar next to the map. + clabel : str, optional Label for the colour bar. - marker_colour: str or list of str + make_interactive : bool, optional + If ``True``, make the plot interactive. + marker_colour : str or list of str, optional Colour of markers (only used for half and half colouring, otherwise use argument c). - marker_size + marker_size : float, optional Size of markers (only used for half and half colouring, otherwise use argument s). kwargs All other arguments are passed to :func:`defdap.plotting.PolePlot.add_points`. + Returns + ------- + defdap.plotting.PolePlot + Pole-figure plot object. + """ plot_params = {'marker': '+'} plot_params.update(kwargs) @@ -526,25 +529,30 @@ def plot_unit_cell( make_interactive: Optional[bool] = False, **kwargs ) -> 'plotting.CrystalPlot': - """Plots a unit cell. + """Plot a unit cell for the current orientation. Parameters ---------- crystal_structure Crystal structure. - OI - True if using oxford instruments system. - plot + OI : bool, optional + If ``True``, use the Oxford Instruments convention. + plot : defdap.plotting.CrystalPlot, optional Plot object to plot to. - fig + fig : matplotlib.figure.Figure, optional Figure to plot on, if not provided the current active axis is used. - ax + ax : matplotlib.axes.Axes, optional Axis to plot on, if not provided the current active axis is used. - make_interactive - True to make the plot interactive. + make_interactive : bool, optional + If ``True``, make the plot interactive. kwargs All other arguments are passed to :func:`defdap.plotting.CrystalPlot.add_verts`. + Returns + ------- + defdap.plotting.CrystalPlot + Crystal plot object. + """ # Set default plot parameters then update with any input plot_params = {} @@ -597,11 +605,11 @@ def plot_unit_cell( @staticmethod def create_many_quats(eulerArray: np.ndarray) -> np.ndarray: - """Create a an array of quats from an array of Euler angles. + """Create an array of quaternions from Euler angles. Parameters ---------- - eulerArray + eulerArray : numpy.ndarray Array of Bunge Euler angles of shape 3 x n x ... x m. Returns @@ -631,18 +639,19 @@ def create_many_quats(eulerArray: np.ndarray) -> np.ndarray: @staticmethod def multiply_many_quats(quats: List['Quat'], right: 'Quat') -> List['Quat']: - """ Multiply all quats in a list of quats, by a single quat. + """Multiply a list of quaternions by a single quaternion. Parameters ---------- - quats + quats : list[Quat] List of quats to be operated on. - right + right : Quat Single quaternion to multiply with the list of quats. Returns ------- - list(defdap.quat.Quat) + list[Quat] + Resulting multiplied quaternions. """ quat_array = np.array([q.quat_coef for q in quats]) @@ -659,7 +668,7 @@ def multiply_many_quats(quats: List['Quat'], right: 'Quat') -> List['Quat']: @staticmethod def extract_quat_comps(quats: np.ndarray) -> np.ndarray: - """Return a NumPy array of the provided quaternion components + """Return quaternion components as a NumPy array. Input quaternions may be given as a list of Quat objects or any iterable whose items have 4 components which map to the quaternion. @@ -667,12 +676,12 @@ def extract_quat_comps(quats: np.ndarray) -> np.ndarray: Parameters ---------- quats : numpy.ndarray(defdap.quat.Quat) - A list of Quat objects to return the components of + Quaternions to extract components from. Returns ------- numpy.ndarray - Array of quaternion components, shape (4, ..) + Array of quaternion components, shape ``(4, ...)``. """ quats = np.array(quats) @@ -694,15 +703,16 @@ def calc_sym_eqvs( ---------- quats : numpy.ndarray(defdap.quat.Quat) Array of quat objects. - sym_group + sym_group : str Crystal type (cubic, hexagonal). - dtype - Datatype used for calculation, defaults to `float`. + dtype : type, optional + Datatype used for calculation, defaults to ``float``. Returns ------- - quat_comps: numpy.ndarray, shape: (numSym x 4 x numQuats) - Array containing all symmetrically equivalent quaternion components of input quaternions. + numpy.ndarray + Symmetrically equivalent quaternion components with shape + ``(num_sym, 4, num_quats)``. """ syms = Quat.sym_eqv(sym_group) @@ -775,23 +785,23 @@ def calc_average_ori( def calcMisOri( quat_comps: np.ndarray, ref_ori: 'Quat' - ) -> Tuple[np.ndarray, 'Quat']: + ) -> Tuple[np.ndarray, np.ndarray]: """Calculate the misorientation between the quaternions and a reference quaternion. Parameters ---------- quat_comps Array containing all symmetrically equivalent quaternion components of given quaternions - (shape: numSym x 4 x numQuats), can be calculated from quats with :func:`Quat.calc_sym_eqvs` . + (shape: numSym x 4 x numQuats), can be calculated from quats with :func:`Quat.calc_sym_eqvs`. ref_ori Reference orientation. Returns ------- - min_mis_oris : numpy.ndarray, len numQuats + min_mis_oris : numpy.ndarray Minimum misorientation between quats and reference orientation. - min_quat_comps : defdap.quat.Quat - Quaternion components describing minimum misorientation between quats and reference orientation. + min_quat_comps : numpy.ndarray + Quaternion components describing minimum misorientation. """ mis_oris = np.empty((quat_comps.shape[0], quat_comps.shape[2])) @@ -826,7 +836,7 @@ def polar_angles(x: np.ndarray, y: np.ndarray, z: np.ndarray): Returns ------- - float, float + tuple[numpy.ndarray, numpy.ndarray] inclination angle and azimuthal angle (around z axis from x in anticlockwise as per ISO). @@ -856,11 +866,11 @@ def calc_ipf_colours( ---------- quats : numpy.ndarray(defdap.quat.Quat) Array of quat objects. - direction + direction : numpy.ndarray Direction in sample space. - sym_group + sym_group : str Crystal type (cubic, hexagonal). - dtype + dtype : type, optional Data type to use for calculation. Returns @@ -969,17 +979,17 @@ def calc_fund_dirs( Parameters ---------- - quats: array_like(defdap.quat.Quat) + quats : array_like[Quat] Array of quat objects. - direction + direction : numpy.ndarray Direction in sample space. - sym_group + sym_group : str Crystal type (cubic, hexagonal). - dtype + dtype : type, optional Data type to use for calculation. - triangle: str, optional + triangle : str, optional Triangle convention to use for hexagonal symmetry (up, down). If None, - defaults to the value in `defaults['ipf_triangle_convention']`. + defaults to the value in ``defaults['ipf_triangle_convention']``. Returns ------- diff --git a/defdap/utils.py b/defdap/utils.py index 4022d69..b67190c 100644 --- a/defdap/utils.py +++ b/defdap/utils.py @@ -23,13 +23,18 @@ def report_progress(message: str = ""): Parameters ---------- - message - Message to display (prefixed by 'Starting ', progress percentage - and then 'Finished ' + message : str, optional + Message to display (prefixed by ``Starting``, progress percentage, + and then ``Finished``). + + Returns + ------- + callable + Decorator wrapping a generator function that yields progress values. References ---------- - Inspiration from : + Inspiration from: https://gist.github.com/Garfounkel/20aa1f06234e1eedd419efe93137c004 """ @@ -106,6 +111,14 @@ class Datastore(object): @staticmethod def generate_id(): + """Generate a unique identifier for datastore grouping. + + Returns + ------- + uuid.UUID + Generated group identifier. + + """ return uuid4() def __init__(self, group_id=None, crop_func=None, mask_func=None): @@ -138,7 +151,7 @@ def __contains__(self, key): return key in self.keys() def __getitem__(self, key): - """Get data or metadata + """Get data or metadata. Parameters ---------- @@ -207,15 +220,11 @@ def __setitem__(self, key, val): self._store[key][attr] = val def __getattr__(self, key): - """Get data - - """ + """Get data for attributes via datastore lookup.""" return self[key] def __setattr__(self, key, val): - """Set data of item that already exists. - - """ + """Set known attributes or route unknown ones to datastore items.""" if key in self.__slots__: super().__setattr__(key, val) else: @@ -240,6 +249,19 @@ def keys(self): return keys def lookup_derivative_keys(self, derivative): + """Return source keys whose metadata matches a derivative definition. + + Parameters + ---------- + derivative : dict + Derivative definition created by ``add_derivative``. + + Returns + ------- + list[str] + Matching source keys. + + """ root_call = False if Datastore._been_to is None: root_call = True @@ -263,6 +285,26 @@ def lookup_derivative_keys(self, derivative): return matched_keys def _get_derived_item(self, key, attr): + """Retrieve derived data or metadata for a key. + + Parameters + ---------- + key : str + Name of the requested derived item. + attr : str + Attribute to access, typically ``'data'`` or metadata name. + + Returns + ------- + any + Requested derived value. + + Raises + ------ + KeyError + If no matching derivative exists for ``key``. + + """ for derivative in self._derivatives: if key in self.lookup_derivative_keys(derivative): break @@ -347,6 +389,23 @@ def add_generator(self, keys, func, metadatas=None, **kwargs): def add_derivative(self, datastore, derive_func, in_props=None, out_props=None, pass_ref=False): + """Register or update a derived-data relationship. + + Parameters + ---------- + datastore : Datastore + Source datastore for derived values. + derive_func : callable + Function used to derive output values. + in_props : dict, optional + Metadata filters required on source items. + out_props : dict, optional + Metadata values exposed on derived items. + pass_ref : bool, optional + If ``True``, pass source key reference into ``derive_func`` + instead of source data. + + """ if in_props is None: in_props = {} if out_props is None: @@ -412,7 +471,8 @@ def update(self, other, priority=None): Parameters ---------- other : defdap.utils.Datastore - priority : str + Datastore providing additional data items. + priority : str, optional Which datastore to keep an item from if the same name exists in both. Default is to prioritise `other`. From b06629d3e6ec7dd1d19387c39aedaac39cc28dd2 Mon Sep 17 00:00:00 2001 From: Rhys Thomas Date: Thu, 16 Apr 2026 12:12:21 +0100 Subject: [PATCH 11/18] Update conventions.rst --- docs/source/userguide/conventions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/userguide/conventions.rst b/docs/source/userguide/conventions.rst index 57790f6..66e01f2 100644 --- a/docs/source/userguide/conventions.rst +++ b/docs/source/userguide/conventions.rst @@ -41,4 +41,4 @@ By default, the HCP slip systems are defined in ``hexagonal_withca.txt``, basal Slip trace angles ---------------------- -These are calculated \ No newline at end of file +These are calculated with the convention that 0 degrees corresponds to a slip trace pointing upwards, and angles increase anticlockwise. From 3aeb4b851bceea6d3db952cedb661c3b7230c29d Mon Sep 17 00:00:00 2001 From: Rhys Thomas Date: Thu, 16 Apr 2026 12:25:31 +0100 Subject: [PATCH 12/18] Fix incorrect sphinx version number --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 391dbba..5e645b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,13 +55,13 @@ testing = [ "pytest_cases", ] docs = [ - "sphinx==9.1.0", + "sphinx==8.1.3", # Max version avaialble from readthedocs - Rhys running 8.2.3 locally "pydata_sphinx_theme", "sphinx_autodoc_typehints==3.6.2", "nbsphinx==0.9.8", "ipykernel", "pandoc", - "ipympl", + "ipympl" ] [tool.setuptools] From a8e8bf29c5430dc744c6ddfa5720f7b9a0771aeb Mon Sep 17 00:00:00 2001 From: Rhys Thomas Date: Thu, 16 Apr 2026 12:27:18 +0100 Subject: [PATCH 13/18] Update pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5e645b1..4e1c225 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,7 @@ testing = [ docs = [ "sphinx==8.1.3", # Max version avaialble from readthedocs - Rhys running 8.2.3 locally "pydata_sphinx_theme", - "sphinx_autodoc_typehints==3.6.2", + "sphinx_autodoc_typehints==3.0.1", # Max version avaialble from readthedocs - Rhys running 3.6.2 locally "nbsphinx==0.9.8", "ipykernel", "pandoc", From d96a629bb69577cf8cf9e9428f25a52fe2412d29 Mon Sep 17 00:00:00 2001 From: Rhys Thomas Date: Thu, 16 Apr 2026 12:31:09 +0100 Subject: [PATCH 14/18] Update pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4e1c225..c57a2f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ testing = [ ] docs = [ "sphinx==8.1.3", # Max version avaialble from readthedocs - Rhys running 8.2.3 locally - "pydata_sphinx_theme", + "pydata_sphinx_theme==0.16.1", "sphinx_autodoc_typehints==3.0.1", # Max version avaialble from readthedocs - Rhys running 3.6.2 locally "nbsphinx==0.9.8", "ipykernel", From 5f1d4eed97e96b9b9a0945b09f61d462ce1b8a3d Mon Sep 17 00:00:00 2001 From: Rhys Thomas Date: Thu, 16 Apr 2026 12:32:48 +0100 Subject: [PATCH 15/18] Update pyproject.toml --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c57a2f1..9f316ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,8 @@ docs = [ "nbsphinx==0.9.8", "ipykernel", "pandoc", - "ipympl" + "ipympl", + "sphinx_design"==0.6.1 ] [tool.setuptools] From e7d72d7f3d711851bb043df67875ed8c295dbf1e Mon Sep 17 00:00:00 2001 From: Rhys Thomas Date: Thu, 16 Apr 2026 12:33:36 +0100 Subject: [PATCH 16/18] Update pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9f316ed..8f9add4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,7 +62,7 @@ docs = [ "ipykernel", "pandoc", "ipympl", - "sphinx_design"==0.6.1 + "sphinx_design==0.6.1" ] [tool.setuptools] From 6b91ce740c162d4e612e8bf9c40a5cf8863cd50e Mon Sep 17 00:00:00 2001 From: Rhys Thomas Date: Thu, 16 Apr 2026 13:06:19 +0100 Subject: [PATCH 17/18] Revert to old apidocs method New method needs 'sphinx.ext.apidoc', which needs sphinx 8.2.3, which needs python 3.11 --- docs/source/conf.py | 52 +++++++++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 83c33aa..ea032e1 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -81,7 +81,7 @@ def get_version(): # ones. extensions = [ 'sphinx.ext.autodoc', - 'sphinx.ext.apidoc', + #'sphinx.ext.apidoc', # Need sphinx.ext.apidoc for this, which needs sphinx 8.2.3, which needs python 3.11 'sphinx.ext.coverage', 'sphinx.ext.mathjax', 'sphinx.ext.viewcode', @@ -179,20 +179,46 @@ def get_version(): 'exclude_patterns': ['*base*'], } +# -- Generate API docs during sphinx-build (for readthedocs) ------------------ + +# Determine if on RTD +ON_RTD = (os.environ.get('READTHEDOCS') == 'True') + +def run_apidoc(_): + + from sphinx.ext import apidoc + + api_args = [ + '--force', + '--separate', # Put each module on seperate page + '--no-toc', # No table of contents + '../../defdap', # Module path + '-o', # Directory to output.. + '../source/defdap' # here + ] + + # Invoke apidoc + apidoc.main(api_args) + +def setup(app): + if ON_RTD: + app.connect('builder-inited', run_apidoc) + # ----------------------------------------------------------------------------- # Apidoc -# ----------------------------------------------------------------------------- - -apidoc_modules = [ - { - 'path': '../../defdap', - 'destination': 'defdap', - 'exclude_patterns': ['*base*'], - 'separate_modules': True, - 'module_first': False, - 'automodule_options': {'members', 'show-inheritance', 'undoc-members'} - } -] +# Need sphinx.ext.apidoc for this, which needs sphinx 8.2.3, which needs python 3.11 +# ----------------------------------------------------------------------------- + +# apidoc_modules = [ +# { +# 'path': '../../defdap', +# 'destination': 'defdap', +# 'exclude_patterns': ['*base*'], +# 'separate_modules': True, +# 'module_first': False, +# 'automodule_options': {'members', 'show-inheritance', 'undoc-members'} +# } +# ] # ----------------------------------------------------------------------------- From 3b124e707d1a36b1c93a168e4702028feae29aad Mon Sep 17 00:00:00 2001 From: Rhys Thomas Date: Thu, 16 Apr 2026 13:20:10 +0100 Subject: [PATCH 18/18] Update papers.rst --- docs/source/papers.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/source/papers.rst b/docs/source/papers.rst index c454ccd..2820782 100644 --- a/docs/source/papers.rst +++ b/docs/source/papers.rst @@ -6,6 +6,10 @@ Here is a list of journal papers which have used the DefDAP Python library. 2026 ------ +* `D.Lunt, A.D.Smith, J.M.Donoghue, I.Alakiozidis, R.Thomas, E.J.Pickering, P.Frankel, M.Carrington, B.Poole, C.Hardie, C.Hamelin, A.Harte. A protocol for in-situ high resolution strain mapping at elevated temperature. Ultramicroscopy. Materials Characterization. Volume 233, March 2026, 116119. `_ + +* `C.Liu, T.Sun, D.Hu, R.Thomas, P.Frankel, J.Fonseca, M.Preuss. Mechanistic insight into cooperative slip system activation under cyclic loading in a near-alpha titanium alloy. Acta Materialia. Volume 308, 15 April 2026, 122031. `_ + * `A.D.Smith, D.Lunt, M.Taylor, A.Davis, R.Thomas, F.Martinez, A.Candeias, A.Gholinia, M.Preuss, J.M.Donoghue. A new approach to SEM in-situ thermomechanical experiments through automation. Ultramicroscopy. Vol. 280, Feb 2026, pp. 114244. `_ 2025