├── .gitignore ├── LICENSE ├── README.rst ├── docs ├── Makefile ├── make.bat ├── requirements.txt └── source │ ├── DIC_2D_doc.rst │ ├── _static │ ├── Foto1.png │ ├── Foto1_post.png │ ├── local_and_global_DIC.png │ ├── logo.png │ └── speckle_pattern.png │ ├── conf.py │ ├── index.rst │ ├── installation_doc.rst │ ├── introduction_doc.rst │ ├── math4dic_doc.rst │ └── utils_doc.rst ├── examples └── Example_1.ipynb ├── logo.png ├── python_powered.png ├── setup.cfg ├── setup.py └── src └── DICpy ├── DIC_2D ├── __init__.py ├── _coarse_fine.py ├── _gradient_one.py ├── _gradient_zero.py ├── _image_registration.py ├── _images.py ├── _lucas_kanade.py ├── _oversampling.py ├── _post_processing.py ├── _regular_grid.py └── _synthetic.py ├── __init__.py ├── math4dic.py └── utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Earthquake Engineering and Structural Dynamics Laboratory 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ******************************************************* 2 | DICpy: digital image correlation with python 3 | ******************************************************* 4 | 5 | .. |logo| 6 | 7 | .. ![plot](./logo.png) 8 | 9 | .. [![Pypi](https://badge.fury.io/py/DICpy.svg)](https://badge.fury.io/py/DICpy.svg) 10 | .. [![Downloads](https://pypip.in/download/DICpy/badge.svg)](https://pypi.python.org/DICpy/) 11 | 12 | |version| |downloads_pypi| 13 | 14 | .. |version| image:: https://badge.fury.io/py/DICpy.svg 15 | :target: https://badge.fury.io/py/DICpy 16 | 17 | .. |downloads_pypi| image:: https://img.shields.io/pypi/dw/DICpy.svg 18 | :target: https://img.shields.io/pypi/dw/DICpy 19 | 20 | ==== 21 | 22 | :Authors: Ketson R. M. dos Santos 23 | :Contact: ketson.santos@epfl.ch 24 | :Version: 0.3.1 25 | 26 | 27 | Description 28 | =========== 29 | 30 | DICpy is a python toolbox for digital image correlation analysis. 31 | 32 | Documentation 33 | ================ 34 | 35 | Website: 36 | https://dicpy.readthedocs.io/ 37 | 38 | Dependencies 39 | =========== 40 | 41 | * :: 42 | 43 | Python >= 3.6 44 | Git >= 2.13.1 45 | 46 | License 47 | =========== 48 | DICpy is distributed under the MIT license 49 | 50 | Copyright (c) 2020 Earthquake Engineering and Structural Dynamics laboratory 51 | 52 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 53 | 54 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 55 | 56 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 57 | 58 | 59 | Installation 60 | =========== 61 | 62 | From PyPI 63 | 64 | * :: 65 | 66 | pip install DICpy 67 | 68 | In order to uninstall it 69 | 70 | * :: 71 | 72 | pip uninstall DICpy 73 | 74 | .. Using Conda 75 | 76 | * :: 77 | 78 | conda install --channel ``...`` ... 79 | 80 | Clone your fork of the PyCrack repo from your GitHub account to your local disk (to get the latest version): 81 | 82 | * :: 83 | 84 | git clone https://github.com/eesd-epfl/DICpy.git 85 | cd DICpy/ 86 | python setup.py install (user installation) 87 | python setup.py develop (developer installation) 88 | 89 | #Referencing DICpy 90 | #================= 91 | 92 | #If you are using this software in a work that will be published, please cite this paper: 93 | 94 | #... 95 | 96 | 97 | Help and Support 98 | =========== 99 | 100 | .. |logo| image:: logo.png 101 | :scale: 10 % 102 | :target: https://github.com/eesd-epfl/DICpy 103 | 104 | .. image:: python_powered.png 105 | :target: https://www.python.org 106 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | DICpy 2 | -------------------------------------------------------------------------------- /docs/source/DIC_2D_doc.rst: -------------------------------------------------------------------------------- 1 | DICpy.DIC_2D 2 | ==================== 3 | 4 | .. automodule:: DICpy.DIC_2D 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | The module ``DIC_2D`` of ``DICpy`` contain classes for performing 2D digital image correlation with Python. The module follows the objected-oriented paradigm and the DIC is performed following a sequence of object instantiation. Next, a detailed description of each class is presented. 10 | 11 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 12 | 13 | DICpy.DIC_2D.Images 14 | -------------------------- 15 | 16 | .. autoclass:: DICpy.DIC_2D.Images 17 | :members: 18 | :undoc-members: 19 | :show-inheritance: 20 | 21 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 22 | 23 | DICpy.DIC_2D.RegularGrid 24 | ------------------------------ 25 | 26 | .. autoclass:: DICpy.DIC_2D.RegularGrid 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 32 | 33 | DICpy.DIC_2D.ImageRegistration 34 | ------------------------------------ 35 | 36 | .. autoclass:: DICpy.DIC_2D.ImageRegistration 37 | :members: 38 | :undoc-members: 39 | :show-inheritance: 40 | 41 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 42 | 43 | DICpy.DIC_2D.Oversampling 44 | ------------------------------- 45 | 46 | .. autoclass:: DICpy.DIC_2D.Oversampling 47 | :members: 48 | :undoc-members: 49 | :show-inheritance: 50 | 51 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 52 | 53 | DICpy.DIC_2D.GradientZero 54 | ------------------------------- 55 | 56 | .. autoclass:: DICpy.DIC_2D.GradientZero 57 | :members: 58 | :undoc-members: 59 | :show-inheritance: 60 | 61 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 62 | 63 | DICpy.DIC_2D.GradientOne 64 | ------------------------------ 65 | 66 | .. autoclass:: DICpy.DIC_2D.GradientOne 67 | :members: 68 | :undoc-members: 69 | :show-inheritance: 70 | 71 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 72 | 73 | DICpy.DIC_2D.CoarseFine 74 | ----------------------------- 75 | 76 | .. autoclass:: DICpy.DIC_2D.CoarseFine 77 | :members: 78 | :undoc-members: 79 | :show-inheritance: 80 | 81 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 82 | 83 | DICpy.DIC_2D.LucasKanade 84 | ------------------------------ 85 | 86 | .. autoclass:: DICpy.DIC_2D.LucasKanade 87 | :members: 88 | :undoc-members: 89 | :show-inheritance: 90 | 91 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 92 | 93 | DICpy.dic2d.PostProcessing 94 | --------------------------------- 95 | 96 | .. autoclass:: DICpy.DIC_2D.PostProcessing 97 | :members: 98 | :undoc-members: 99 | :show-inheritance: 100 | 101 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 102 | 103 | DICpy.DIC_2D.Synthetic 104 | ----------------------------- 105 | 106 | .. autoclass:: DICpy.DIC_2D.Synthetic 107 | :members: 108 | :undoc-members: 109 | :show-inheritance: 110 | 111 | .. toctree:: 112 | :maxdepth: 2 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /docs/source/_static/Foto1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eesd-epfl/DICpy/feedc5fe630f347d1ef9d6a2069c239ac5388aea/docs/source/_static/Foto1.png -------------------------------------------------------------------------------- /docs/source/_static/Foto1_post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eesd-epfl/DICpy/feedc5fe630f347d1ef9d6a2069c239ac5388aea/docs/source/_static/Foto1_post.png -------------------------------------------------------------------------------- /docs/source/_static/local_and_global_DIC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eesd-epfl/DICpy/feedc5fe630f347d1ef9d6a2069c239ac5388aea/docs/source/_static/local_and_global_DIC.png -------------------------------------------------------------------------------- /docs/source/_static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eesd-epfl/DICpy/feedc5fe630f347d1ef9d6a2069c239ac5388aea/docs/source/_static/logo.png -------------------------------------------------------------------------------- /docs/source/_static/speckle_pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eesd-epfl/DICpy/feedc5fe630f347d1ef9d6a2069c239ac5388aea/docs/source/_static/speckle_pattern.png -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | # import os 14 | # import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = 'DICpy' 21 | copyright = '2021, EESD-EPFL' 22 | author = 'EESD-EPFL' 23 | 24 | # The full version, including alpha/beta/rc tags 25 | release = '0.3.0' 26 | 27 | 28 | # -- General configuration --------------------------------------------------- 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = [ 34 | 'sphinx.ext.autodoc', 35 | 'sphinx.ext.napoleon', 36 | 'sphinx.ext.doctest', 37 | 'sphinx.ext.intersphinx', 38 | 'sphinx.ext.coverage', 39 | 'sphinx.ext.mathjax', 40 | 'sphinx.ext.viewcode', 41 | 'sphinx.ext.githubpages', 42 | 'sphinx.ext.autosummary', 43 | 'sphinx.ext.autosectionlabel', 44 | ] 45 | 46 | # Add any paths that contain templates here, relative to this directory. 47 | templates_path = ['_templates'] 48 | 49 | # List of patterns, relative to source directory, that match files and 50 | # directories to ignore when looking for source files. 51 | # This pattern also affects html_static_path and html_extra_path. 52 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 53 | 54 | 55 | # -- Options for HTML output ------------------------------------------------- 56 | 57 | # The theme to use for HTML and HTML Help pages. See the documentation for 58 | # a list of builtin themes. 59 | # 60 | language = None 61 | pygments_style = None 62 | 63 | html_theme = 'alabaster' 64 | 65 | html_theme_options = { 66 | "github_user": "eesd-epfl", 67 | "github_repo": "DICpy", 68 | "github_banner": True, 69 | "logo": "logo.png", 70 | "logo_name": False, 71 | "logo_text_align": "left", 72 | "description": ("Digital Image Correlation with Python "), 73 | } 74 | 75 | # Add any paths that contain custom static files (such as style sheets) here, 76 | # relative to this directory. They are copied after the builtin static files, 77 | # so a file named "default.css" will overwrite the builtin "default.css". 78 | html_static_path = ['_static'] 79 | 80 | html_sidebars = { 81 | '**': [ 82 | 'about.html', 83 | 'navigation.html', 84 | 'relations.html', 85 | 'searchbox.html', 86 | ] 87 | } 88 | 89 | # The suffix(es) of source filenames. 90 | # You can specify multiple suffix as a list of string: 91 | # 92 | # source_suffix = ['.rst', '.md'] 93 | source_suffix = '.rst' 94 | 95 | # The master toctree document. 96 | master_doc = 'index' 97 | 98 | # -- Options for HTMLHelp output --------------------------------------------- 99 | 100 | # Output file base name for HTML help builder. 101 | htmlhelp_basename = 'dicpydoc' 102 | 103 | #autodoc_default_flags = ['members', 'private-members'] 104 | 105 | # -- Options for LaTeX output ------------------------------------------------ 106 | 107 | latex_elements = { 108 | # The paper size ('letterpaper' or 'a4paper'). 109 | # 110 | # 'papersize': 'letter-paper', 111 | 112 | # The font size ('10pt', '11pt' or '12pt'). 113 | # 114 | # 'pointsize': '10pt', 115 | 116 | # Additional stuff for the LaTeX preamble. 117 | # 118 | # 'preamble': '', 119 | 120 | # Latex figure (float) alignment 121 | # 122 | # 'figure_align': 'htbp', 123 | } 124 | 125 | # Grouping the document tree into LaTeX files. List of tuples 126 | # (source start file, target name, title, 127 | # author, documentclass [howto, manual, or own class]). 128 | latex_documents = [ 129 | (master_doc, 'dicpy.tex', 'DICpy Documentation', 130 | 'Ketson R. M. dos Santos', 'manual'), 131 | ] 132 | 133 | 134 | # -- Options for manual page output ------------------------------------------ 135 | 136 | # One entry per manual page. List of tuples 137 | # (source start file, name, description, authors, manual section). 138 | man_pages = [ 139 | (master_doc, 'dicpy', 'DICpy Documentation', 140 | [author], 1) 141 | ] 142 | 143 | 144 | # -- Options for Texinfo output ---------------------------------------------- 145 | 146 | # Grouping the document tree into Texinfo files. List of tuples 147 | # (source start file, target name, title, author, 148 | # dir menu entry, description, category) 149 | texinfo_documents = [ 150 | (master_doc, 'dicpy', 'DICpy Documentation', 151 | author, 'dicpy', 'One line description of project.', 152 | 'Miscellaneous'), 153 | ] 154 | 155 | 156 | # -- Options for Epub output ------------------------------------------------- 157 | 158 | # Bibliographic Dublin Core info. 159 | epub_title = project 160 | 161 | # The unique identifier of the text. This can be a ISBN number 162 | # or the project homepage. 163 | # 164 | # epub_identifier = '' 165 | 166 | # A unique identification for the text. 167 | # 168 | # epub_uid = '' 169 | 170 | # A list of files that should not be packed into the epub file. 171 | epub_exclude_files = ['search.html'] 172 | 173 | 174 | # Example configuration for intersphinx: refer to the Python standard library. 175 | intersphinx_mapping = { 176 | 'python': ('https://docs.python.org/3', None), 177 | 'numpy': ('https://docs.scipy.org/doc/numpy/', None), 178 | } -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. DICpy documentation master file, created by 2 | sphinx-quickstart on Thu Aug 26 15:15:26 2021. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to DICpy's documentation! 7 | ================================= 8 | 9 | DICpy is a open-source Python toolbox for perfoming digital image correlation (DIC). 10 | The code is a collection of modules implementing several algorithms used in DIC, including 11 | subpixel resolution, optical flow, and more. 12 | 13 | .. _toc: 14 | 15 | Table of contents 16 | ----------------- 17 | 18 | .. toctree:: 19 | :maxdepth: 2 20 | 21 | introduction_doc 22 | DIC_2D_doc 23 | math4dic_doc 24 | utils_doc 25 | installation_doc 26 | 27 | * :ref:`genindex` 28 | * :ref:`modindex` 29 | * :ref:`search` 30 | -------------------------------------------------------------------------------- /docs/source/installation_doc.rst: -------------------------------------------------------------------------------- 1 | .. _installation_doc: 2 | 3 | Installation 4 | ============== 5 | 6 | Dependencies:: 7 | 8 | macOS, Linux, Windows 9 | Python >= 3.6 10 | 11 | 12 | Installation on a macOS can be made with the following commands. 13 | 14 | Using Python package index (PyPI):: 15 | 16 | pip install DICpy 17 | 18 | 19 | From GitHub: Clone your fork of the UQpy repo from your GitHub account to your local disk (to get the latest version:: 20 | 21 | git clone https://github.com/eesd-epfl/DICpy.git 22 | cd DICpy 23 | python setup.py install 24 | 25 | Help & Support 26 | --------------------------- 27 | 28 | * Raise an issue on GitHub: ``https://github.com/eesd-epfl/DICpy`` 29 | * Contact: ``ketson.santos@epfl.ch`` 30 | 31 | 32 | .. _issues: https://github.com/eesd-epfl/DICpy/issues 33 | -------------------------------------------------------------------------------- /docs/source/introduction_doc.rst: -------------------------------------------------------------------------------- 1 | .. _instroduction_doc: 2 | 3 | Digital Image Correlation 4 | ========================= 5 | 6 | Introduction 7 | ------------- 8 | 9 | Digital Image Correlation (DIC) is a class of non-contacting methods for extracting strain fields, deformation, and motion of objects by using digital image registration. It is relevant for solving problem in several fields such as in civil engineering [2]_, biomechanics [3]_, and materials science [4]_. In DIC, matching algorithms are used to mesure deformations/motion in solids and fluids; however, in most applications there is a need for measurements in small scales (in the order of mm) and therefore, higher image resolutions are required. To further increase the precision of the measurements, sub-pixel registration algorithms are employed for minimal systematic errors [5]_. In general, DIC can be classified as 2D and 3D [1]_. 2D DIC is focused on the estimation of strain fields in materials by comparing pictures at consecutive load steps. Therefore, matching algorithms are used for maximizing the correlation of patches encoding the local deformation in two dimensions only. On the other hand, 3D DIC consider stereographic images acquired with multiple cameras for estimating the strain fields in three dimensions. Furthermore, a similar approach can be applied for the analysis of volumetric images via digital volume correlation (DVC) [6]_. 10 | 11 | 2D DIC 12 | _______ 13 | 14 | The principle of DIC in 2D is based on the comparison of images at two consecutive instants. In particular, one can capture the structural deformation at different load steps to estimate the evolution of the strain field. The correlation is a measure of similarity between signals such as digital images. One of the largely adopted measures of correlation between images is the normalized cross correlation (:math:`C_{NCC}`) [1], which can de defined as 15 | 16 | .. math:: C_{NCC} = \frac{(\sum{F - \bar{F})} \sum{(G - \bar{G})}}{\sqrt{\sum{(F - \bar{F})^2} \sum{(G - \bar{G})^2}}} 17 | 18 | where :math:`F` and :math:`G` are the images from which the correlation is computed and the bar over both represent the average of the pixels in the gray scale. Clearly, :math:`C_{NCC}` is maximum (equal to one) when the images :math:`F` and :math:`G` are the same. This quantity is relevant for DIC, however, one can think about some of the possible challenges one can face when searching for the best match for a template in another image. For example, if the values of the magnitude of pixels are the same in a wide area of the searching image, once can end up with a ill-posed problem because the solution cannot be unique. Therefore, there is a need to circumvent this limitation. In the DIC context, a conventional approach consists in add speckles in the object under analysis. These speckles can be applied physically for instance by spraying drops of paint in the object's surface. The objective of creating a speckle pattern in the object surface is that the uniqueness for matching algorithm can be imposed with high probability due to the random characteristics of the speckle pattern, as observed in the following figure. 19 | 20 | .. image:: _static/speckle_pattern.png 21 | :scale: 30 % 22 | :align: center 23 | 24 | During an experimental campaign, images are acquired at each load step. These images are then analyzed using an appropriate DIC method for estimating the strain field in the sequence of images. Two classes of methods are the global and local DIC. The global DIC is based on the finite element methodology because it guarantees the global kinematic compatibility and add regularization penalties for reducing noise. On the other hand, the local DIC is based on the template matching methodology, where patches of the reference images are searched in the deformed image. See the difference between these two methods in the following figure. 25 | 26 | .. image:: _static/local_and_global_DIC.png 27 | :scale: 30 % 28 | :align: center 29 | 30 | Next, the local DIC is discussed with more details, including the necessity of using sub-pixel algorithms. 31 | 32 | 33 | DICpy: Digital Image Correlation with Python 34 | ============================================ 35 | 36 | ``DICpy`` (Digital Image Correlation with Python) is a python toolbox containing several algorithms for performing DIC. The algorithms are easily accessible and are useful in several applications. Users can define which one is more suitable for their own applications; moreover, they can fork the GitHub repository to implement their own modifications. However, it is important mentioning that ``DICpy`` is under development, and possible inconsistencies/bugs/errors should be taken into consideration. ``DICpy`` currently contains the module ``DIC_2D`` including the following Python classes: 37 | 38 | 39 | * ``Images`` 40 | 41 | The class ``Images`` is used to read the images to be used in the DIC analysis. 42 | 43 | * ``RegularGrid`` 44 | 45 | The class ``RegularGrid`` is used to define the region of interest (ROI) and the grid determining the regions where the image matching is performed. The users have the option to use the mouse by clicking on the image to get the coordinates of points in opposite positions of a rectangle defining ROI. Alternatively, the coordinates can be passed by the user as arguments of the method ``define_mesh``. 46 | 47 | * ``Synthetic`` 48 | 49 | The class ``Synthetic`` can be used to generate synthetic images to test the algorithms implemented in ``DICpy``. 50 | 51 | * ``ImageRegistration`` 52 | 53 | The class ``ImageRegistration`` implements the image matching process with integer resolution. 54 | 55 | * ``Oversampling`` 56 | 57 | The class ``Oversampling`` is used to perform registration with sub-pixel resolution using the oversampling approach to increase the image resolution. 58 | 59 | * ``GradientZero`` 60 | 61 | The class ``GradientZero`` is used to perform registration with sub-pixel resolution using gradient methods. The class implements the method presented in [1] for shape functions with order zero encoding pure translations. 62 | 63 | * ``GradientOne`` 64 | 65 | The class ``GradientOne`` is used to perform registration with sub-pixel resolution using gradient methods. The class implements the method presented in [1] for shape functions with order one encoding shear with affine transformations. 66 | 67 | * ``CoarseFine`` 68 | 69 | The class ``CoarseFine`` is used to perform registration with sub-pixel resolution using gradient methods. The class implements the method presented in [1] for shape functions with zeroth order. 70 | 71 | * ``LucasKanade`` 72 | 73 | The class ``LucasKanade`` implements the Lucas-Kanade method for tracking image features using optical flow. The method uses the method calcOpticalFlowPyrLK() from OpenCV-Python. 74 | 75 | Moreover, the following modules contains the implementation of auxiliary methods. 76 | 77 | * ``utils`` 78 | * ``math4dic`` 79 | 80 | Local DIC with DICpy 81 | ____________________ 82 | 83 | Local DIC is a method based on template match for estimating the strain fields in materials under deformation. Template matches algorithms can solve this problem by finding the location in the searching image that maximize the correlation, as the normalized cross correlation presented previously, between the template and the searching image. Moreover, algorithms based on the optical flow can be used as well. 84 | 85 | Let's consider the following image of a reinforced concrete wall tested at the Earthquake Engineering and Structural Dynamics Lab at EPFL. 86 | 87 | .. image:: _static/Foto1.png 88 | :scale: 50 % 89 | :align: center 90 | 91 | 92 | Two images can be read from a given directory using the class ``Images``, as presented next. 93 | 94 | * Instantiate an object of the class Images 95 | 96 | .. code-block:: python 97 | 98 | imobj = Images() 99 | 100 | * Calibrate pixel_dim for conversion pixel/length. 101 | 102 | .. code-block:: python 103 | 104 | imobj.calibration(pixel_dim=pixel_length) 105 | 106 | * Read the speckle images. 107 | 108 | .. code-block:: python 109 | 110 | imobj.read_speckle_images(path=path_to_images, extension="png") 111 | 112 | Next, instantiate an object of ``RegularGrid`` with an object of ``Images``. 113 | 114 | .. code-block:: python 115 | 116 | meshobj = RegularGrid(images_obj=imobj) 117 | 118 | And construct the mesh by giving the extreme points of a rectangular area, and the number of divisions (`nx` and `ny`) per dimension, such as 119 | 120 | .. code-block:: python 121 | 122 | meshobj.define_mesh(point_a=(1500,3000), point_b=(6000, 400), nx=30, ny=20, show_grid=False) 123 | 124 | Next, run DIC using one of the methods implemented in ``DICpy``. In this example, the Lucas-Kanade algorithm is used using the following commands. 125 | 126 | .. code-block:: python 127 | 128 | anaobj = LucasKanade(mesh_obj=meshobj) 129 | 130 | and 131 | 132 | .. code-block:: python 133 | 134 | anaobj.run() 135 | 136 | Use the class ``PostProcessing`` for plotting the strain fields and displacements. In this example, the horizontal displacement is provided. Moreover, the smoothed graphic is presented because `smooth` is set as True. 137 | 138 | .. code-block:: python 139 | 140 | ppobj = PostProcessing(analysis_obj=anaobj) 141 | 142 | .. code-block:: python 143 | 144 | ppobj.visualization(results='u', step=1, smooth=True) 145 | 146 | Therefore, the following graphic is generated. 147 | 148 | .. image:: _static/Foto1_post.png 149 | :scale: 50 % 150 | :align: center 151 | 152 | .. [1] H. Schreier, J-J Orteu, M. A. Sutton, Image Correlation for Shape, Motion and Deformation Measurements: Basic Concepts,Theory and Applications, ISBN: 978-0-387-78747-3, ed. 1, Springer, Boston, MA. 153 | 154 | .. [2] A. Rezaie, R. Achanta, M. Godio, K. Beyer, Comparison of crack segmentation using digital image correlation measurements and deep learning, Construction and Building Materials, 261, 2020, pp. 120474. 155 | 156 | .. [3] Y. Katz, Z. Yosibash, New insights on the proximal femur biomechanics using Digital Image Correlation, Journal of Biomechanics, 101, 2020, pp. 109599. 157 | 158 | .. [4] S. R. Heinz, J. S. Wiggins, Uniaxial compression analysis of glassy polymer networks using digital image correlation, Polymer Testing, 29(8), 2010, pp. 925-932, 159 | 160 | .. [5] B. Pan, B. Wang. Digital Image Correlation with Enhanced Accuracy and Efficiency: A Comparison of Two Subpixel Registration Algorithms. Experimental Mechanics, 56, 2016, pp. 1395–1409. 161 | 162 | .. [6] B. K. Bay, T. S. Smith, D. P. Fyhrie et al. Digital volume correlation: Three-dimensional strain mapping using X-ray tomography. Experimental Mechanics 39, 1999, 217–226. 163 | 164 | .. toctree:: 165 | :maxdepth: 2 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | -------------------------------------------------------------------------------- /docs/source/math4dic_doc.rst: -------------------------------------------------------------------------------- 1 | .. _math4dic_doc: 2 | 3 | DICpy.math4dic 4 | ============== 5 | 6 | .. automodule:: DICpy.math4dic 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /docs/source/utils_doc.rst: -------------------------------------------------------------------------------- 1 | .. _utils_doc: 2 | 3 | DICpy.utils 4 | ============= 5 | 6 | .. automodule:: DICpy.utils 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eesd-epfl/DICpy/feedc5fe630f347d1ef9d6a2069c239ac5388aea/logo.png -------------------------------------------------------------------------------- /python_powered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eesd-epfl/DICpy/feedc5fe630f347d1ef9d6a2069c239ac5388aea/python_powered.png -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.rst 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup, find_packages 4 | 5 | setup( 6 | name='DICpy', 7 | version="0.3.1", 8 | url='https://github.com/eesd-epfl/DICpy', 9 | description="Digital Image Correlation with Python.", 10 | author="Ketson R. M. dos Santos", 11 | author_email="ketson.santos@epfl.ch", 12 | license='MIT', 13 | platforms=["OSX", "Windows", "Linux"], 14 | packages=find_packages("src"), 15 | package_dir={"": "src"}, 16 | package_data={"": ["*.pdf"]}, 17 | install_requires=[ 18 | "numpy", "scipy", "matplotlib", "scikit-learn", "scikit-image", "opencv-python" 19 | ], 20 | classifiers=[ 21 | 'Programming Language :: Python :: 3', 22 | 'Intended Audience :: Science/Research', 23 | 'Topic :: Scientific/Engineering :: Image Processing', 24 | 'License :: OSI Approved :: MIT License', 25 | 'Natural Language :: English', 26 | ], 27 | ) 28 | -------------------------------------------------------------------------------- /src/DICpy/DIC_2D/__init__.py: -------------------------------------------------------------------------------- 1 | from DICpy.DIC_2D._regular_grid import RegularGrid 2 | from DICpy.DIC_2D._post_processing import PostProcessing 3 | from DICpy.DIC_2D._images import Images 4 | from DICpy.DIC_2D._lucas_kanade import LucasKanade 5 | from DICpy.DIC_2D._image_registration import ImageRegistration 6 | from DICpy.DIC_2D._coarse_fine import CoarseFine 7 | from DICpy.DIC_2D._gradient_zero import GradientZero 8 | from DICpy.DIC_2D._gradient_one import GradientOne 9 | from DICpy.DIC_2D._oversampling import Oversampling 10 | from DICpy.DIC_2D._synthetic import Synthetic 11 | 12 | 13 | from DICpy.DIC_2D._regular_grid import * 14 | from DICpy.DIC_2D._post_processing import * 15 | from DICpy.DIC_2D._images import * 16 | from DICpy.DIC_2D._lucas_kanade import * 17 | from DICpy.DIC_2D._image_registration import * 18 | from DICpy.DIC_2D._coarse_fine import * 19 | from DICpy.DIC_2D._gradient_zero import * 20 | from DICpy.DIC_2D._gradient_one import * 21 | from DICpy.DIC_2D._oversampling import * 22 | from DICpy.DIC_2D._synthetic import * 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/DICpy/DIC_2D/_coarse_fine.py: -------------------------------------------------------------------------------- 1 | from DICpy.utils import * 2 | import numpy as np 3 | from DICpy.math4dic import interpolate_template, norm_xcorr 4 | from DICpy.DIC_2D._image_registration import ImageRegistration 5 | from scipy.interpolate import RectBivariateSpline 6 | 7 | 8 | class CoarseFine(ImageRegistration): 9 | """ 10 | DIC with subpixel resolution using an coarse-fine search approach as presented in the following paper: 11 | 12 | "An advanced coarse-fine search approach for digital image correlation applications." 13 | (By: Samo Simoncic, Melita Kompolsek, Primoz Podrzaj) 14 | 15 | Further, this class is a child class of ``ImageRegistration``, and it inherit the method `run` and override 16 | `_registration`. 17 | 18 | **Input:** 19 | * **mesh_obj** (`object`) 20 | Object of ``RegularGrid``. 21 | 22 | **Attributes:** 23 | 24 | * **pixel_dim** (`float`) 25 | Constant to convert pixel into a physical quantity (e.g., mm). 26 | 27 | * **mesh_obj** (`object`) 28 | Object of the ``RegularGrid``. 29 | 30 | * **u** (`ndarray`) 31 | Displacements in the x (columns) dimension at the center of each cell. 32 | 33 | * **v** (`ndarray`) 34 | Displacements in the y (rows) dimension at the center of each cell. 35 | 36 | * **niter** (`int`) 37 | Number of iterations in the coarse-fine algorithm. 38 | 39 | **Methods:** 40 | """ 41 | 42 | def __init__(self, mesh_obj=None, niter=1): 43 | 44 | self.pixel_dim = mesh_obj.images_obj.pixel_dim 45 | self.mesh_obj = mesh_obj 46 | self.u = None 47 | self.v = None 48 | self.niter = niter 49 | 50 | super().__init__(mesh_obj=mesh_obj) 51 | 52 | def _registration(self, img_0, img_1, psearch, ptem, lengths, windows, gaps): 53 | """ 54 | Private method for estimating the displacements using the template matching technique with subpixel resolution. 55 | This method overrides the one in the parent class. 56 | 57 | **Input:** 58 | * **img_0** (`ndarray`) 59 | Image in time t. 60 | 61 | * **img_1** (`ndarray`) 62 | Image in time t + dt. 63 | 64 | * **psearch** (`tuple`) 65 | Upper left corner of the searching area. 66 | 67 | * **ptem** (`tuple`) 68 | Upper left corner of the template. 69 | 70 | * **lengths** (`tuple`) 71 | Lengths in x and y of the searching area (length_x, length_y). 72 | 73 | * **windows** (`tuple`) 74 | Lengths in x and y of the template (window_x, window_y). 75 | 76 | * **gaps** (`tuple`) 77 | Gaps between template and searching area (gap_x, gap_y). 78 | 79 | **Output/Returns:** 80 | * **px** (`float`) 81 | Displacement in x (columns). 82 | 83 | * **px** (`float`) 84 | Displacement in y (rows). 85 | """ 86 | 87 | l_x = lengths[0] 88 | l_y = lengths[1] 89 | window_x = windows[0] 90 | window_y = windows[1] 91 | 92 | # Template. 93 | img_template = get_template_left(im_source=img_0, point=ptem, sidex=window_x, sidey=window_y) 94 | 95 | # Searching area. 96 | img_search = get_template_left(im_source=img_1, point=psearch, sidex=l_x, sidey=l_y) 97 | 98 | # Get positions with subpixel resolution. 99 | px, py = self._template_match_sk(img_search, img_template, mlx=1, mly=1) 100 | px = int(px) 101 | py = int(py) 102 | pval = (py, px) 103 | 104 | img_u = get_template_left(im_source=img_0, point=ptem, sidex=window_x, sidey=window_y) 105 | 106 | delta = self._coarse_fine(img_u=img_u, img_search=img_search, n=self.niter, pval=pval, 107 | window_x=window_x, window_y=window_y) 108 | 109 | px = px + delta[0] 110 | py = py + delta[1] 111 | 112 | return px, py 113 | 114 | @staticmethod 115 | def _coarse_fine(img_u=None, img_search=None, n=None, pval=None, window_x=None, window_y=None): 116 | """ 117 | Private method implementing the coarse-fine search. 118 | 119 | **Input:** 120 | * **img_u** (`ndarray`) 121 | Reference image. 122 | 123 | * **img_search** (`ndarray`) 124 | Image of the searching area. 125 | 126 | * **n** (`int`) 127 | Number of iterations. 128 | 129 | * **pval** (`tuple`) 130 | Corner: integer location of the template in the deformed image. 131 | 132 | * **window_x** (`int`) 133 | Length of the template in the x dimension. 134 | 135 | * **window_y** (`int`) 136 | Length of the template in the y dimension. 137 | 138 | **Output/Returns:** 139 | * **delta** (`tuple`) 140 | Sub-pixel increment. 141 | """ 142 | 143 | # Identify the coordinates of the template in the deformed image. 144 | px = pval[1] 145 | py = pval[0] 146 | x = np.arange(px, px + window_x) 147 | y = np.arange(py, py + window_y) 148 | img_0 = img_u 149 | 150 | # Get the interpolator. 151 | dim = np.shape(img_search) 152 | x0 = np.arange(0, dim[1]) 153 | y0 = np.arange(0, dim[0]) 154 | interp_img = RectBivariateSpline(y0, x0, img_search) 155 | 156 | # Positions for rows and columns in the sub-grid. 157 | r0 = -0.5 158 | r1 = 0.5 159 | c0 = -0.5 160 | c1 = 0.5 161 | 162 | # Start the iterative process. 163 | yc0 = 0 164 | xc0 = 0 165 | delta = (0, 0) 166 | for k in range(n): 167 | 168 | # Determine the sub-grid for each iteration. 169 | row = np.linspace(r0, r1, 3) 170 | col = np.linspace(c0, c1, 3) 171 | 172 | corr = [] 173 | posi = [] 174 | posif = [] 175 | center = [] 176 | 177 | # Find the normalized cross correlation for each cell in the sub-grid. 178 | for i in range(len(row) - 1): 179 | for j in range(len(col) - 1): 180 | 181 | # Coordinate of the center of the cells in the sub-grid. 182 | yc = (row[i] + row[i + 1]) / 2 + yc0 * 0 183 | xc = (col[j] + col[j + 1]) / 2 + xc0 * 0 184 | center.append((xc, yc)) 185 | 186 | # Get the interpolated template. 187 | img_1 = interpolate_template(f=interp_img, x=x, y=y, dx=xc, dy=yc, dim=dim) 188 | 189 | # Compute the normalized cross correlation between the reference and the interpolated template. 190 | c = norm_xcorr(img_0, img_1) 191 | 192 | corr.append(c) 193 | posi.append((row[i], col[j])) 194 | posif.append((row[i + 1], col[j + 1])) 195 | 196 | # Get the position in the sub-grid with maximum cross correlation and define a smaller sub-grid. 197 | c_max = corr.index(max(corr)) 198 | p0 = posi[c_max] 199 | p1 = posif[c_max] 200 | delta = center[c_max] 201 | yc0 = delta[1] 202 | xc0 = delta[0] 203 | 204 | # Update the sub-grid and repeat the process within this sub-grid. 205 | r0 = p0[0] 206 | r1 = p1[0] 207 | c0 = p0[1] 208 | c1 = p1[1] 209 | 210 | return delta -------------------------------------------------------------------------------- /src/DICpy/DIC_2D/_gradient_one.py: -------------------------------------------------------------------------------- 1 | from DICpy.utils import * 2 | import numpy as np 3 | import copy 4 | from DICpy.math4dic import gradient, interpolate_template 5 | from DICpy.DIC_2D._image_registration import ImageRegistration 6 | import scipy.spatial.distance as sd 7 | from scipy.interpolate import RectBivariateSpline 8 | 9 | 10 | class GradientOne(ImageRegistration): 11 | """ 12 | DIC with subpixel resolution using gradients. This class implement a method using zeroth order shape functions, and 13 | It is a child class of `ImageRegistration`. 14 | 15 | **Input:** 16 | * **mesh_obj** (`object`) 17 | Object of ``RegularGrid``. 18 | 19 | **Attributes:** 20 | 21 | * **pixel_dim** (`float`) 22 | Constant to convert pixel into a physical quantity (e.g., mm). 23 | 24 | * **mesh_obj** (`object`) 25 | Object of the ``RegularGrid``. 26 | 27 | * **u** (`ndarray`) 28 | Displacements in the x (columns) dimension at the center of each cell. 29 | 30 | * **v** (`ndarray`) 31 | Displacements in the y (rows) dimension at the center of each cell. 32 | 33 | * **max_iter** (`ndarray`) 34 | Maximum number of iterations in the optimization process. 35 | 36 | * **tol** (`ndarray`) 37 | Error tolerance in the optimization process. 38 | 39 | **Methods:** 40 | """ 41 | 42 | def __init__(self, mesh_obj=None, max_iter=20, tol=1e-3): 43 | 44 | self.pixel_dim = mesh_obj.images_obj.pixel_dim 45 | self.mesh_obj = mesh_obj 46 | self.u = None 47 | self.v = None 48 | self.max_iter = max_iter 49 | self.tol = tol 50 | 51 | super().__init__(mesh_obj=mesh_obj) 52 | 53 | def _registration(self, img_0, img_1, psearch, ptem, lengths, windows, gaps): 54 | """ 55 | Private method for estimating the displacements using the template matching technique with subpixel resolution. 56 | This method overrides the one in the parent class. 57 | 58 | **Input:** 59 | * **img_0** (`ndarray`) 60 | Image in time t. 61 | 62 | * **img_1** (`ndarray`) 63 | Image in time t + dt. 64 | 65 | * **psearch** (`tuple`) 66 | Upper left corner of the searching area. 67 | 68 | * **ptem** (`tuple`) 69 | Upper left corner of the template. 70 | 71 | * **lengths** (`tuple`) 72 | Lengths in x and y of the searching area (length_x, length_y). 73 | 74 | * **windows** (`tuple`) 75 | Lengths in x and y of the template (window_x, window_y). 76 | 77 | * **gaps** (`tuple`) 78 | Gaps between template and searching area (gap_x, gap_y). 79 | 80 | **Output/Returns:** 81 | * **px** (`float`) 82 | Displacement in x (columns). 83 | 84 | * **px** (`float`) 85 | Displacement in y (rows). 86 | """ 87 | 88 | l_x = lengths[0] 89 | l_y = lengths[1] 90 | window_x = windows[0] 91 | window_y = windows[1] 92 | gap_x = gaps[0] 93 | gap_y = gaps[1] 94 | 95 | # Image of the template. 96 | img_template = get_template_left(im_source=img_0, point=ptem, sidex=window_x, sidey=window_y) 97 | 98 | # Image of the searching area. 99 | img_search = get_template_left(im_source=img_1, point=psearch, sidex=l_x, sidey=l_y) 100 | 101 | # Subpixel resolution. 102 | # Integer location. 103 | px, py = self._template_match_sk(img_search, img_template, mlx=1, mly=1) 104 | px = int(px) 105 | py = int(py) 106 | 107 | # Crop the searching areas for both images (sequential images). 108 | ff = get_template_left(im_source=img_0, point=psearch, sidex=l_x, sidey=l_y) 109 | gg = get_template_left(im_source=img_1, point=psearch, sidex=l_x, sidey=l_y) 110 | 111 | # subpixel estimation. 112 | delta = self._grad_subpixel(f=ff, g=gg, gap_x=gap_x, gap_y=gap_y, p_corner=(py, px)) 113 | 114 | px = px + delta[0] 115 | py = py + delta[1] 116 | 117 | return px, py 118 | 119 | def _grad_subpixel(self, f=None, g=None, gap_x=None, gap_y=None, p_corner=None): 120 | """ 121 | This is a gradient method considering a shape function for affine transformation 122 | (including distortion in the deformed image): x* = a x + b. 123 | 124 | **Input:** 125 | * **f** (`ndarray`) 126 | Reference image. 127 | 128 | * **g** (`ndarray`) 129 | Deformed image. 130 | 131 | * **gap_x** (`int`) 132 | Gap between searching area and the template in the x direction. 133 | 134 | * **gap_y** (`int`) 135 | Gap between searching area and the template in the y direction. 136 | 137 | * **p_corner** (`tuple`) 138 | Point containing the upper left corner of the searching area. 139 | 140 | **Output/Returns:** 141 | * **delta** (`tuple`) 142 | Sub-pixel displacement. 143 | """ 144 | 145 | ly, lx = np.shape(f) 146 | xtem_0 = gap_x 147 | ytem_0 = gap_y 148 | xtem_1 = lx - gap_x 149 | ytem_1 = ly - gap_y 150 | 151 | # Local: in the searching area. 152 | ptem = (ytem_0, xtem_0) 153 | 154 | window_x = abs(xtem_1 - xtem_0) + 1 155 | window_y = abs(ytem_1 - ytem_0) + 1 156 | 157 | # using Sobel. 158 | gx, gy = gradient(g, k=7) 159 | 160 | # Interpolation. 161 | dim = np.shape(g) 162 | x0 = np.arange(0, dim[1]) 163 | y0 = np.arange(0, dim[0]) 164 | interp_g = RectBivariateSpline(y0, x0, g) 165 | interp_gx = RectBivariateSpline(y0, x0, gx) 166 | interp_gy = RectBivariateSpline(y0, x0, gy) 167 | 168 | # Crop the searching areas in f and g, for the correlated points. 169 | gx_crop = get_template_left(im_source=gx, point=p_corner, sidex=window_x, sidey=window_y) 170 | gy_crop = get_template_left(im_source=gy, point=p_corner, sidex=window_x, sidey=window_y) 171 | f_crop = get_template_left(im_source=f, point=ptem, sidex=window_x, sidey=window_y) 172 | g_crop = get_template_left(im_source=g, point=p_corner, sidex=window_x, sidey=window_y) 173 | 174 | # write p_corner as an array. 175 | p_corner = np.array(p_corner, dtype=float) 176 | pc = copy.copy(p_corner) 177 | 178 | # Initiate the update of the parameters of the shape function. 179 | delta = np.zeros(np.shape(p_corner)) 180 | err = 1000 181 | tol = self.tol 182 | max_iter = self.max_iter # maximum number of iterations. 183 | niter = 0 184 | while err > tol and niter <= max_iter: 185 | 186 | xx = np.linspace(p_corner[1], p_corner[1] + window_x, window_x) 187 | yy = np.linspace(p_corner[0], p_corner[0] + window_y, window_y) 188 | XX, YY = np.meshgrid(xx, yy) 189 | 190 | fg_crop = f_crop - g_crop 191 | 192 | # Get the matrices and solve the corresponding linear system. 193 | a11 = np.sum(gx_crop * gx_crop) 194 | a12 = np.sum(gx_crop * gy_crop) 195 | a13 = np.sum(gx_crop * gx_crop * XX) 196 | a14 = np.sum(gx_crop * gx_crop * YY) 197 | a15 = np.sum(gx_crop * gy_crop * XX) 198 | a16 = np.sum(gx_crop * gy_crop * YY) 199 | 200 | a22 = np.sum(gy_crop * gy_crop) 201 | a23 = np.sum(gx_crop * gy_crop * XX) 202 | a24 = np.sum(gx_crop * gy_crop * YY) 203 | a25 = np.sum(gy_crop * gy_crop * XX) 204 | a26 = np.sum(gy_crop * gy_crop * YY) 205 | 206 | a33 = np.sum(gx_crop * gx_crop * XX * XX) 207 | a34 = np.sum(gx_crop * gx_crop * XX * YY) 208 | a35 = np.sum(gx_crop * gy_crop * XX * XX) 209 | a36 = np.sum(gx_crop * gx_crop * XX * YY) 210 | 211 | a44 = np.sum(gx_crop * gx_crop * YY * YY) 212 | a45 = np.sum(gx_crop * gy_crop * XX * YY) 213 | a46 = np.sum(gx_crop * gy_crop * YY * YY) 214 | 215 | a55 = np.sum(gy_crop * gy_crop * XX * XX) 216 | a56 = np.sum(gy_crop * gy_crop * XX * YY) 217 | 218 | a66 = np.sum(gy_crop * gy_crop * YY * YY) 219 | 220 | A_vec = [a12, a13, a14, a15, a16, a23, a24, a25, a26, a34, a35, a36, a45, a46, a56] 221 | A = sd.squareform(np.array(A_vec)) 222 | A[0, 0] = a11 223 | A[1, 1] = a22 224 | A[2, 2] = a33 225 | A[3, 3] = a44 226 | A[4, 4] = a55 227 | A[5, 5] = a66 228 | 229 | A_inv = np.linalg.inv(A) 230 | 231 | C = np.zeros(6) 232 | C[0] = np.sum(fg_crop * gx_crop) 233 | C[1] = np.sum(fg_crop * gy_crop) 234 | C[1] = np.sum(fg_crop * gx_crop * XX) 235 | C[1] = np.sum(fg_crop * gx_crop * YY) 236 | C[1] = np.sum(fg_crop * gy_crop * XX) 237 | C[1] = np.sum(fg_crop * gy_crop * YY) 238 | 239 | # Get the step increment. 240 | d_delta = A_inv @ C 241 | 242 | # Get the norm of the step increment to check the convergence. 243 | err = np.linalg.norm(d_delta) 244 | 245 | # Step update. 246 | delta[0] = delta[0] + d_delta[0] 247 | delta[1] = delta[1] + d_delta[1] 248 | 249 | # Update the position of the upper left corner. 250 | p_corner[0] = pc[0] + delta[0] 251 | p_corner[1] = pc[1] + delta[1] 252 | 253 | # Interpolate. 254 | x = np.linspace(p_corner[1], p_corner[1] + window_x, window_x) 255 | y = np.linspace(p_corner[0], p_corner[0] + window_y, window_y) 256 | g_crop = interpolate_template(f=interp_g, x=x, y=y, dim=dim) 257 | gx_crop = interpolate_template(f=interp_gx, x=x, y=y, dim=dim) 258 | gy_crop = interpolate_template(f=interp_gy, x=x, y=y, dim=dim) 259 | 260 | niter += 1 261 | 262 | return delta 263 | 264 | -------------------------------------------------------------------------------- /src/DICpy/DIC_2D/_gradient_zero.py: -------------------------------------------------------------------------------- 1 | from DICpy.utils import * 2 | import numpy as np 3 | import copy 4 | from DICpy.math4dic import gradient, interpolate_template 5 | from DICpy.DIC_2D._image_registration import ImageRegistration 6 | from scipy.interpolate import RectBivariateSpline 7 | 8 | 9 | class GradientZero(ImageRegistration): 10 | """ 11 | DIC with subpixel resolution using gradients. This class implement a method using zeroth order shape functions, and 12 | It is a child class of `ImageRegistration`. 13 | 14 | **Input:** 15 | * **mesh_obj** (`object`) 16 | Object of ``RegularGrid``. 17 | 18 | **Attributes:** 19 | 20 | * **pixel_dim** (`float`) 21 | Constant to convert pixel into a physical quantity (e.g., mm). 22 | 23 | * **mesh_obj** (`object`) 24 | Object of the ``RegularGrid``. 25 | 26 | * **u** (`ndarray`) 27 | Displacements in the x (columns) dimension at the center of each cell. 28 | 29 | * **v** (`ndarray`) 30 | Displacements in the y (rows) dimension at the center of each cell. 31 | 32 | * **max_iter** (`ndarray`) 33 | Maximum number of iterations in the optimization process. 34 | 35 | * **tol** (`ndarray`) 36 | Error tolerance in the optimization process. 37 | 38 | **Methods:** 39 | """ 40 | 41 | def __init__(self, mesh_obj=None, max_iter=20, tol=1e-3): 42 | 43 | self.pixel_dim = mesh_obj.images_obj.pixel_dim 44 | self.mesh_obj = mesh_obj 45 | self.u = None 46 | self.v = None 47 | self.max_iter = max_iter 48 | self.tol = tol 49 | 50 | super().__init__(mesh_obj=mesh_obj) 51 | 52 | def _registration(self, img_0, img_1, psearch, ptem, lengths, windows, gaps): 53 | """ 54 | Private method for estimating the displacements using the template matching technique with subpixel resolution. 55 | This method overrides the one in the parent class. 56 | 57 | **Input:** 58 | * **img_0** (`ndarray`) 59 | Image in time t. 60 | 61 | * **img_1** (`ndarray`) 62 | Image in time t + dt. 63 | 64 | * **psearch** (`tuple`) 65 | Upper left corner of the searching area. 66 | 67 | * **ptem** (`tuple`) 68 | Upper left corner of the template. 69 | 70 | * **lengths** (`tuple`) 71 | Lengths in x and y of the searching area (length_x, length_y). 72 | 73 | * **windows** (`tuple`) 74 | Lengths in x and y of the template (window_x, window_y). 75 | 76 | * **gaps** (`tuple`) 77 | Gaps between template and searching area (gap_x, gap_y). 78 | 79 | **Output/Returns:** 80 | * **px** (`float`) 81 | Displacement in x (columns). 82 | 83 | * **px** (`float`) 84 | Displacement in y (rows). 85 | """ 86 | 87 | l_x = lengths[0] 88 | l_y = lengths[1] 89 | window_x = windows[0] 90 | window_y = windows[1] 91 | gap_x = gaps[0] 92 | gap_y = gaps[1] 93 | 94 | # Image of the template. 95 | img_template = get_template_left(im_source=img_0, point=ptem, sidex=window_x, sidey=window_y) 96 | 97 | # Image of the searching area. 98 | img_search = get_template_left(im_source=img_1, point=psearch, sidex=l_x, sidey=l_y) 99 | 100 | # Subpixel resolution. 101 | # Integer location. 102 | px, py = self._template_match_sk(img_search, img_template, mlx=1, mly=1) 103 | px = int(px) 104 | py = int(py) 105 | 106 | # Crop the searching areas for both images (sequential images). 107 | ff = get_template_left(im_source=img_0, point=psearch, sidex=l_x, sidey=l_y) 108 | gg = get_template_left(im_source=img_1, point=psearch, sidex=l_x, sidey=l_y) 109 | 110 | # subpixel estimation. 111 | delta = self._grad_subpixel(f=ff, g=gg, gap_x=gap_x, gap_y=gap_y, p_corner=(py, px)) 112 | 113 | px = px + delta[0] 114 | py = py + delta[1] 115 | 116 | return px, py 117 | 118 | def _grad_subpixel(self, f=None, g=None, gap_x=None, gap_y=None, p_corner=None): 119 | """ 120 | This is a gradient method considering a shape function for pure translation only: x* = x + p. 121 | 122 | **Input:** 123 | * **f** (`ndarray`) 124 | Reference image. 125 | 126 | * **g** (`ndarray`) 127 | Deformed image. 128 | 129 | * **gap_x** (`int`) 130 | Gap between searching area and the template in the x direction. 131 | 132 | * **gap_y** (`int`) 133 | Gap between searching area and the template in the y direction. 134 | 135 | * **p_corner** (`tuple`) 136 | Point containing the upper left corner of the searching area. 137 | 138 | **Output/Returns:** 139 | * **delta** (`tuple`) 140 | Sub-pixel displacement. 141 | """ 142 | 143 | ly, lx = np.shape(f) 144 | xtem_0 = gap_x 145 | ytem_0 = gap_y 146 | xtem_1 = lx - gap_x 147 | ytem_1 = ly - gap_y 148 | 149 | # Local: in the searching area. 150 | ptem = (ytem_0, xtem_0) 151 | 152 | window_x = abs(xtem_1 - xtem_0) + 1 153 | window_y = abs(ytem_1 - ytem_0) + 1 154 | 155 | # using Sobel. 156 | gx, gy = gradient(g, k=7) 157 | 158 | # Interpolants. 159 | dim = np.shape(g) 160 | x0 = np.arange(0, dim[1]) 161 | y0 = np.arange(0, dim[0]) 162 | interp_g = RectBivariateSpline(y0, x0, g) 163 | interp_gx = RectBivariateSpline(y0, x0, gx) 164 | interp_gy = RectBivariateSpline(y0, x0, gy) 165 | 166 | # Crop the searching areas in f and g, for the correlated points. 167 | gx_crop = get_template_left(im_source=gx, point=p_corner, sidex=window_x, sidey=window_y) 168 | gy_crop = get_template_left(im_source=gy, point=p_corner, sidex=window_x, sidey=window_y) 169 | f_crop = get_template_left(im_source=f, point=ptem, sidex=window_x, sidey=window_y) 170 | g_crop = get_template_left(im_source=g, point=p_corner, sidex=window_x, sidey=window_y) 171 | 172 | # write p_corner as an array. 173 | p_corner = np.array(p_corner, dtype=float) 174 | pc = copy.copy(p_corner) 175 | 176 | # Initiate the update of the parameters of the shape function. 177 | delta = np.zeros(np.shape(p_corner)) 178 | err = 1000 179 | tol = self.tol 180 | max_iter = self.max_iter # maximum number of iterations. 181 | niter = 0 182 | while err > tol and niter <= max_iter: 183 | fg_crop = f_crop - g_crop 184 | 185 | # Get the matrices and solve the corresponding linear system. 186 | a11 = np.sum(gx_crop ** 2) 187 | a22 = np.sum(gy_crop ** 2) 188 | a12 = np.sum(gx_crop * gy_crop) 189 | 190 | c1 = np.sum(fg_crop * gx_crop) 191 | c2 = np.sum(fg_crop * gy_crop) 192 | 193 | A_inv = np.linalg.inv(np.array([[a11, a12], [a12, a22]])) 194 | C = np.array([c1, c2]) 195 | 196 | # Get the step increment. 197 | d_delta = A_inv @ C 198 | 199 | # Get the norm of the step increment to check the convergence. 200 | err = np.linalg.norm(d_delta) 201 | 202 | # Step update. 203 | delta[0] = delta[0] + d_delta[0] 204 | delta[1] = delta[1] + d_delta[1] 205 | 206 | # update the position of the upper left corner. 207 | p_corner[0] = pc[0] + delta[0] 208 | p_corner[1] = pc[1] + delta[1] 209 | 210 | # Interpolate. 211 | x = np.linspace(p_corner[1], p_corner[1] + window_x, window_x) 212 | y = np.linspace(p_corner[0], p_corner[0] + window_y, window_y) 213 | g_crop = interpolate_template(f=interp_g, x=x, y=y, dim=dim) 214 | gx_crop = interpolate_template(f=interp_gx, x=x, y=y, dim=dim) 215 | gy_crop = interpolate_template(f=interp_gy, x=x, y=y, dim=dim) 216 | 217 | niter += 1 218 | 219 | return delta -------------------------------------------------------------------------------- /src/DICpy/DIC_2D/_image_registration.py: -------------------------------------------------------------------------------- 1 | from DICpy.utils import * 2 | import numpy as np 3 | import cv2 4 | from skimage.feature import match_template 5 | 6 | 7 | class ImageRegistration: 8 | """ 9 | This class has the methods to perform the DIC analysis with integer resolution. 10 | This is a parent class for children classes implementing methods with subpixel resolution. 11 | 12 | **Input:** 13 | * **mesh_obj** (`object`) 14 | Object of ``RegularGrid``. 15 | 16 | **Attributes:** 17 | 18 | * **pixel_dim** (`float`) 19 | Constant to convert pixel into a physical quantity (e.g., mm). 20 | 21 | * **mesh_obj** (`object`) 22 | Object of the ``RegularGrid``. 23 | 24 | * **u** (`ndarray`) 25 | Displacements in the x (columns) dimension at the center of each cell. 26 | 27 | * **v** (`ndarray`) 28 | Displacements in the y (rows) dimension at the center of each cell. 29 | 30 | **Methods:** 31 | """ 32 | 33 | def __init__(self, mesh_obj=None): 34 | 35 | self.pixel_dim = mesh_obj.images_obj.pixel_dim 36 | self.mesh_obj = mesh_obj 37 | self.u = None 38 | self.v = None 39 | 40 | def run(self): 41 | """ 42 | Method for performing the DIC analysis. 43 | """ 44 | 45 | images = self.mesh_obj.images_obj.images 46 | num_img = self.mesh_obj.images_obj.num_img 47 | xp = self.mesh_obj.xp 48 | yp = self.mesh_obj.yp 49 | 50 | u = [] 51 | v = [] 52 | 53 | usum = np.zeros((self.mesh_obj.ny - 1, self.mesh_obj.nx - 1)) 54 | vsum = np.zeros((self.mesh_obj.ny - 1, self.mesh_obj.nx - 1)) 55 | 56 | # Loop over the images. 57 | for k in range(num_img - 1): 58 | 59 | img_0 = images[k] 60 | img_1 = images[k + 1] 61 | 62 | # Loop over the elements. 63 | c = 0 64 | centers = [] 65 | # print(self.mesh_obj.ny - 1, self.mesh_obj.nx - 1) 66 | for i in range(self.mesh_obj.ny - 1): 67 | for j in range(self.mesh_obj.nx - 1): 68 | # print(i, j) 69 | l_x = abs(xp[i + 1, j + 1] - xp[i, j]) # searching area. 70 | l_y = abs(yp[i + 1, j + 1] - yp[i, j]) # searching area. 71 | xc = (xp[i + 1, j + 1] + xp[i, j]) / 2 # center searching area. 72 | yc = (yp[i + 1, j + 1] + yp[i, j]) / 2 # center searching area. 73 | centers.append((xc, yc)) 74 | 75 | gap_x = int(max(np.ceil(l_x / 3), 3)) # default gap: 1/3. 76 | gap_y = int(max(np.ceil(l_y / 3), 3)) # default gap: 1/3 77 | 78 | xtem_0 = xp[i, j] + gap_x # x coordinate of the top right (template). 79 | ytem_0 = yp[i, j] + gap_y # y coordinate of the top right (template). 80 | xtem_1 = xp[i + 1, j + 1] - gap_x # x coordinate of the bottom left (template). 81 | ytem_1 = yp[i + 1, j + 1] - gap_y # x coordinate of the bottom left (template). 82 | 83 | window_x = abs(xtem_1 - xtem_0) + 1 # size x (columns) template. 84 | window_y = abs(ytem_1 - ytem_0) + 1 # size y (rows) template. 85 | 86 | ptem = (ytem_0, xtem_0) # top right coordinate of the template for the matching processing. 87 | psearch = (yp[i, j], xp[i, j]) # top right coordinate of the searching area for matching. 88 | 89 | lengths = (l_x, l_y) 90 | windows = (window_x, window_y) 91 | gaps = (gap_x, gap_y) 92 | px, py = self._registration(img_0, img_1, psearch, ptem, lengths, windows, gaps) 93 | 94 | u0 = px - gap_x 95 | v0 = py - gap_y 96 | 97 | usum[i, j] = usum[i, j] + (u0 * self.pixel_dim) 98 | vsum[i, j] = vsum[i, j] + (v0 * self.pixel_dim) 99 | 100 | u.append(usum) 101 | v.append(vsum) 102 | 103 | self.u = np.array(u) 104 | self.v = np.array(v) 105 | 106 | def _registration(self, img_0, img_1, psearch, ptem, lengths, windows, gaps): 107 | """ 108 | Private method for estimating the displacements using the template matching technique with integer resolution. 109 | 110 | **Input:** 111 | * **img_0** (`ndarray`) 112 | Image in time t. 113 | 114 | * **img_1** (`ndarray`) 115 | Image in time t + dt. 116 | 117 | * **psearch** (`tuple`) 118 | Upper left corner of the searching area. 119 | 120 | * **ptem** (`tuple`) 121 | Upper left corner of the template. 122 | 123 | * **lengths** (`tuple`) 124 | Lengths in x and y of the searching area (length_x, length_y). 125 | 126 | * **windows** (`tuple`) 127 | Lengths in x and y of the template (window_x, window_y). 128 | 129 | * **gaps** (`tuple`) 130 | Gaps between template and searching area (gap_x, gap_y). 131 | 132 | **Output/Returns:** 133 | * **px** (`float`) 134 | Displacement in x (columns). 135 | 136 | * **px** (`float`) 137 | Displacement in y (rows). 138 | """ 139 | 140 | l_x = lengths[0] 141 | l_y = lengths[1] 142 | window_x = windows[0] 143 | window_y = windows[1] 144 | 145 | # Image of the template. 146 | img_template = get_template_left(im_source=img_0, point=ptem, sidex=window_x, sidey=window_y) 147 | 148 | # Image of the searching area. 149 | img_search = get_template_left(im_source=img_1, point=psearch, sidex=l_x, sidey=l_y) 150 | 151 | # No subpixel resolution required. 152 | px, py = self._template_match_sk(img_search, img_template, mlx=1, mly=1) 153 | px = int(px) 154 | py = int(py) 155 | 156 | return px, py 157 | 158 | @staticmethod 159 | def _template_match_sk(img_source, img_template, mlx=1, mly=1): 160 | """ 161 | Method for template matching using the normalized cross correlation. 162 | 163 | **Input:** 164 | * **img_source** (`ndarray`) 165 | Image where the search will be performed. 166 | 167 | * **img_template** (`ndarray`) 168 | Template image. 169 | 170 | * **mlx** (`int`) 171 | Parameter used for oversampling in the x direction (columns). 172 | 173 | * **mly** (`int`) 174 | Parameter used for oversampling in the y direction (rows). 175 | 176 | **Output/Returns:** 177 | * **px** (`int`) 178 | Location in the x direction. 179 | 180 | * **py** (`int`) 181 | Location in the y direction. 182 | """ 183 | 184 | # Image oversampling 185 | img_source = cv2.resize(img_source, None, fx=mlx, fy=mly, interpolation=cv2.INTER_CUBIC) 186 | img_template = cv2.resize(img_template, None, fx=mlx, fy=mly, interpolation=cv2.INTER_CUBIC) 187 | 188 | result = match_template(img_source, img_template) 189 | ij = np.unravel_index(np.argmax(result), result.shape) 190 | px, py = ij[::-1] 191 | 192 | px = px / mlx 193 | py = py / mly 194 | 195 | return px, py 196 | -------------------------------------------------------------------------------- /src/DICpy/DIC_2D/_images.py: -------------------------------------------------------------------------------- 1 | from DICpy.utils import _close 2 | import numpy as np 3 | import matplotlib.patches as patches 4 | import matplotlib.pyplot as plt 5 | import os 6 | import skimage.io as sio 7 | from matplotlib.widgets import Button 8 | import cv2 9 | 10 | 11 | class Images: 12 | """ 13 | This class contains the methods for reading and processing images used in digital image correlation (DIC). 14 | **Input:** 15 | **Attributes:** 16 | 17 | * **path_speckle** (`str`) 18 | Path to the speckle images. 19 | 20 | * **path_calibration** (`str`) 21 | Path to the calibration image. 22 | 23 | * **ref_image** (`ndarray`) 24 | Reference image. 25 | 26 | * **calibration_image** (`ndarray`) 27 | Calibration image. 28 | 29 | * **images** (`ndarray`) 30 | Gray images. 31 | 32 | * **images_normalized** (`ndarray`) 33 | Gray images normalized by 255. 34 | 35 | * **lx** (`int`) 36 | Number of columns of each image. 37 | 38 | * **ly** (`int`) 39 | Number of rows of each image. 40 | 41 | * **num_img** (`int`) 42 | Number of images. 43 | 44 | * **pixel_dim** (`float`) 45 | Size of each pixel in length dimension. 46 | 47 | **Methods:** 48 | """ 49 | 50 | def __init__(self): 51 | 52 | self.path_speckle = None 53 | self.path_calibration = None 54 | self.ref_image = None 55 | self.calibration_image = None 56 | self.images = None 57 | self.images_normalized = None 58 | self.lx = None 59 | self.ly = None 60 | self.num_img = None 61 | self.pixel_dim = 1 62 | 63 | def read_speckle_images(self, path=None, extension=None, file_names=None, ref_id=0, verbose=False): 64 | """ 65 | Read the speckle images. 66 | 67 | **Input:** 68 | * **path** (`str`) 69 | Path for the images used in the DIC analysis. 70 | 71 | * **extension** (`str`) 72 | The extension of the files located in `path`. 73 | 74 | * **file_names** (`str`) 75 | Define the files to be read in the sequence of your preference. 76 | 77 | * **ref_id** (`int`) 78 | Define a file to be used as reference, the default is zero, which means that the first image in the stack will 79 | be used as reference. 80 | 81 | * **verbose** (`bool`) 82 | Boolean varible to print some information on screen. 83 | 84 | **Output/Returns:** 85 | """ 86 | 87 | if not isinstance(ref_id,int): 88 | raise TypeError('DICpy: ref_id must be an integer.') 89 | 90 | if ref_id < 0: 91 | raise ValueError('DICpy: ref_id is an integer larger than or equal to 0.') 92 | 93 | if path is None: 94 | path = os.getcwd() 95 | 96 | if extension is None: 97 | raise TypeError('DICpy: extension cannot be NoneType when using path.') 98 | 99 | self.path_speckle = path 100 | 101 | if file_names is None: 102 | file_names = [f for f in os.listdir(path) if f.endswith('.' + extension)] 103 | file_names = np.sort(file_names) 104 | 105 | if verbose: 106 | print('DICpy: reading speckle images.') 107 | 108 | images = [] 109 | images_normalized = [] 110 | for f in file_names: 111 | 112 | if verbose: 113 | print(f) 114 | 115 | im = cv2.imread(os.path.join(path, f), 0) 116 | 117 | images.append(im) 118 | images_normalized.append(im/255) 119 | 120 | self.images = images 121 | self.images_normalized = images_normalized 122 | (lx, ly) = np.shape(im) 123 | self.lx = lx 124 | self.ly = ly 125 | self.num_img = len(images) 126 | self.ref_image = images[ref_id] 127 | 128 | @staticmethod 129 | def _read_calibration_images(path=None, file_name=None, verbose=True): 130 | """ 131 | Private method for reading the calibration image. 132 | 133 | **Input:** 134 | * **path** (`str`) 135 | Path for the calibration image used in the DIC analysis. 136 | 137 | * **file_names** (`str`) 138 | Name of the calibration image. 139 | 140 | * **verbose** (`bool`) 141 | Boolean varible to print some information on screen. 142 | 143 | **Output/Returns:** 144 | * **calibration_image** (`ndarray`) 145 | Calibration image. 146 | """ 147 | 148 | if path is None: 149 | path = os.getcwd() 150 | 151 | if file_name is None: 152 | raise TypeError('DICpy: file_name cannot be NoneType.') 153 | 154 | if verbose: 155 | print('DICpy: reading the calibration image.') 156 | 157 | im = sio.imread(os.path.join(path, file_name), as_gray=True) 158 | calibration_image = 255 * im 159 | 160 | return calibration_image 161 | 162 | def calibration(self, ref_length=None, pixel_dim=None, path=None, file_name=None, 163 | point_a=None, point_b=None, verbose=True): 164 | """ 165 | Method for the analysis calibration. 166 | 167 | **Input:** 168 | * **ref_length** (`str`) 169 | Length used as reference. 170 | 171 | * **pixel_dim** (`float`) 172 | Size of each pixel in length dimension. 173 | 174 | * **path** (`str`) 175 | Path for the calibration image used in the DIC analysis. 176 | 177 | * **file_name** (`str`) 178 | Name of the calibration image. 179 | 180 | * **point_a** (`float`) 181 | First corner of the region of interest (ROI). 182 | 183 | * **point_b** (`float`) 184 | Corner opposed to point_b of the region of interest (ROI). 185 | 186 | * **verbose** (`bool`) 187 | Boolean variable to print some information on screen. 188 | 189 | **Output/Returns:** 190 | """ 191 | 192 | if pixel_dim is None: 193 | 194 | if ref_length is None: 195 | raise TypeError('DICpy: ref_length cannot be NoneType.') 196 | 197 | self.path_calibration = path 198 | cal_img = self._read_calibration_images(path=path, file_name=file_name, verbose=verbose) 199 | self.calibration_image = cal_img 200 | (lx, ly) = np.shape(cal_img) 201 | maxl = max(lx, ly) 202 | 203 | global rcirc, ax, fig, coords, cid 204 | rcirc = maxl / 160 205 | 206 | if point_a is None or point_b is None: 207 | coords = [] 208 | 209 | fig = plt.figure() 210 | ax = fig.add_subplot(111) 211 | cid = fig.canvas.mpl_connect('button_press_event', self._mouse_click) 212 | plt.imshow(cal_img, cmap="gray") 213 | plt.show() 214 | 215 | point_a = coords[0] 216 | point_b = coords[1] 217 | 218 | else: 219 | 220 | fig = plt.figure() 221 | ax = fig.add_subplot(111) 222 | 223 | circle = plt.Circle(point_a, rcirc, color='red') 224 | ax.add_patch(circle) 225 | fig.canvas.draw() # this line was missing earlier 226 | circle = plt.Circle(point_b, rcirc, color='red') 227 | ax.add_patch(circle) 228 | fig.canvas.draw() # this line was missing earlier 229 | 230 | lx = (point_b[0] - point_a[0]) 231 | ly = (point_b[1] - point_a[1]) 232 | 233 | fig.canvas.draw() # this line was missing earlier 234 | 235 | plt.imshow(cal_img, cmap="gray") 236 | plt.show(block=False) 237 | plt.close() 238 | 239 | point_a = (round(point_a[0]),round(point_a[1])) 240 | point_b = (round(point_b[0]),round(point_b[1])) 241 | 242 | fig = plt.figure() 243 | ax = fig.add_subplot(111) 244 | 245 | lx = (point_b[0] - point_a[0]) 246 | ly = (point_b[1] - point_a[1]) 247 | 248 | circle = plt.Circle(point_a, rcirc, color='red') 249 | ax.add_patch(circle) 250 | fig.canvas.draw() # this line was missing earlier 251 | circle = plt.Circle(point_b, rcirc, color='red') 252 | ax.add_patch(circle) 253 | fig.canvas.draw() # this line was missing earlier 254 | 255 | ax.plot([point_a[0], point_b[0]], [point_a[1], point_b[1]], linewidth=2) 256 | fig.canvas.draw() 257 | plt.imshow(cal_img, cmap="gray") 258 | axclose = plt.axes([0.81, 0.05, 0.1, 0.075]) 259 | bclose = Button(axclose, 'Next') 260 | bclose.on_clicked(_close) 261 | plt.show() 262 | 263 | self.pixel_dim = ref_length/np.sqrt(lx**2 + ly**2) 264 | 265 | if verbose: 266 | print("Points: ",point_a,point_b) 267 | print("mm/pixel: ",self.pixel_dim) 268 | 269 | else: 270 | 271 | if ref_length is not None: 272 | raise Warning('DICpy: ref_length not used when pixel_dim is provided.') 273 | 274 | self.pixel_dim = pixel_dim 275 | 276 | if verbose: 277 | print("mm/pixel: ",self.pixel_dim) 278 | 279 | @staticmethod 280 | def _mouse_click(event): 281 | """ 282 | Private method: mouse click to select the ROI. 283 | 284 | **Input:** 285 | * **event** (`event`) 286 | Click event. 287 | 288 | **Output/Returns:** 289 | """ 290 | 291 | global x, y 292 | x, y = event.xdata, event.ydata 293 | 294 | if event.button: 295 | circle = plt.Circle((event.xdata, event.ydata), rcirc, color='red') 296 | ax.add_patch(circle) 297 | fig.canvas.draw() # this line was missing earlier 298 | 299 | global coords 300 | coords.append((x, y)) 301 | 302 | if len(coords) == 2: 303 | fig.canvas.mpl_disconnect(cid) 304 | 305 | xa = coords[0][0] 306 | ya = coords[0][1] 307 | xb = coords[1][0] 308 | yb = coords[1][1] 309 | lx = (xb - xa) 310 | ly = (yb - ya) 311 | rect = patches.Rectangle((xa, ya), lx, ly, linewidth=1, edgecolor='None', facecolor='b', alpha=0.4) 312 | ax.add_patch(rect) 313 | fig.canvas.draw() # this line was missing earlier 314 | plt.close() 315 | 316 | return coords 317 | 318 | 319 | 320 | -------------------------------------------------------------------------------- /src/DICpy/DIC_2D/_lucas_kanade.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import cv2 3 | from DICpy.DIC_2D._image_registration import ImageRegistration 4 | 5 | 6 | class LucasKanade(ImageRegistration): 7 | """ 8 | DIC with subpixel resolution using the Lucas-Kanade algorithm implemented in OpenCV-Python, and It is a child class 9 | of `ImageRegistration`. 10 | 11 | **Input:** 12 | * **mesh_obj** (`object`) 13 | Object of ``RegularGrid``. 14 | 15 | **Attributes:** 16 | 17 | * **pixel_dim** (`float`) 18 | Constant to convert pixel into a physical quantity (e.g., mm). 19 | 20 | * **mesh_obj** (`object`) 21 | Object of the ``RegularGrid``. 22 | 23 | * **u** (`ndarray`) 24 | Displacements in the x (columns) dimension at the center of each cell. 25 | 26 | * **v** (`ndarray`) 27 | Displacements in the y (rows) dimension at the center of each cell. 28 | 29 | **Methods:** 30 | """ 31 | 32 | def __init__(self, mesh_obj=None): 33 | 34 | self.pixel_dim = mesh_obj.images_obj.pixel_dim 35 | self.mesh_obj = mesh_obj 36 | self.u = None 37 | self.v = None 38 | 39 | super().__init__(mesh_obj=mesh_obj) 40 | 41 | def run(self): 42 | """ 43 | Method for performing the DIC analysis. It overrides the one in ``ImageRegistration`` 44 | """ 45 | 46 | # Get images and the number of images in the object images. 47 | images = self.mesh_obj.images_obj.images 48 | num_img = self.mesh_obj.images_obj.num_img 49 | 50 | # Get the nodes of the grid in the mesh object. 51 | xp = self.mesh_obj.xp 52 | yp = self.mesh_obj.yp 53 | 54 | # initialize the lists receiving the horizontal (u) and vertical (v) displacements. 55 | u = [] 56 | v = [] 57 | 58 | # Initialize the accumulator of displacements. 59 | usum = np.zeros((self.mesh_obj.ny - 1, self.mesh_obj.nx - 1)) 60 | vsum = np.zeros((self.mesh_obj.ny - 1, self.mesh_obj.nx - 1)) 61 | 62 | # Loop over the images. 63 | for k in range(num_img - 1): 64 | 65 | img_0 = images[k] # image in the time t. 66 | img_1 = images[k + 1] # image in the time t + dt. 67 | 68 | centers = [] # list for the centers of the elements in the grid. 69 | positions = [] # list for the position for the pixel positions of the upper left nodes of each element. 70 | lx_list = [] # length in the direction x (columns) of each element. 71 | ly_list = [] # length in the direction y (columns) of each element. 72 | 73 | # Loop over the upper left nodes of each element. 74 | for i in range(self.mesh_obj.ny - 1): 75 | for j in range(self.mesh_obj.nx - 1): 76 | positions.append([i, j]) 77 | l_x = abs(xp[i + 1, j + 1] - xp[i, j]) 78 | l_y = abs(yp[i + 1, j + 1] - yp[i, j]) 79 | xc = (xp[i + 1, j + 1] + xp[i, j]) / 2 80 | yc = (yp[i + 1, j + 1] + yp[i, j]) / 2 81 | centers.append(np.array([np.float32(xc), np.float32(yc)])) 82 | lx_list.append(l_x) 83 | ly_list.append(l_y) 84 | 85 | l_x = np.max(lx_list) 86 | l_y = np.max(ly_list) 87 | centers = np.array(centers) 88 | positions = np.array(positions) 89 | 90 | # Parameters for the Lucas-Kanade method using opencv-python. 91 | lk_params = dict(winSize=(l_x, l_y), maxLevel=10, 92 | criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03)) 93 | 94 | # Get the final positions for the optical flow using Lucas-Kanade. 95 | final_positions, st, err = cv2.calcOpticalFlowPyrLK(img_0, img_1, centers, None, **lk_params) 96 | 97 | # The next steps are the update of the displacement for the template. 98 | u0 = np.zeros((self.mesh_obj.ny - 1, self.mesh_obj.nx - 1)) 99 | v0 = np.zeros((self.mesh_obj.ny - 1, self.mesh_obj.nx - 1)) 100 | for i in range(len(final_positions)): 101 | ii = positions[i, 0] 102 | jj = positions[i, 1] 103 | u0[ii, jj] = final_positions[i, 0] - centers[i, 0] 104 | v0[ii, jj] = final_positions[i, 1] - centers[i, 1] 105 | 106 | usum = usum + (u0 * self.pixel_dim) 107 | vsum = vsum + (v0 * self.pixel_dim) 108 | 109 | u.append(usum) 110 | v.append(vsum) 111 | 112 | # Instantiate the displacements. 113 | self.u = np.array(u) 114 | self.v = np.array(v) -------------------------------------------------------------------------------- /src/DICpy/DIC_2D/_oversampling.py: -------------------------------------------------------------------------------- 1 | from DICpy.utils import * 2 | from DICpy.DIC_2D._image_registration import ImageRegistration 3 | 4 | 5 | class Oversampling(ImageRegistration): 6 | """ 7 | DIC with subpixel resolution using oversampling. 8 | 9 | **Input:** 10 | * **mesh_obj** (`object`) 11 | Object of ``RegularGrid``. 12 | 13 | **Attributes:** 14 | 15 | * **pixel_dim** (`float`) 16 | Constant to convert pixel into a physical quantity (e.g., mm). 17 | 18 | * **mesh_obj** (`object`) 19 | Object of the ``RegularGrid``. 20 | 21 | * **u** (`ndarray`) 22 | Displacements in the x (columns) dimension at the center of each cell. 23 | 24 | * **v** (`ndarray`) 25 | Displacements in the y (rows) dimension at the center of each cell. 26 | 27 | * **over_x** (`int`) 28 | Parameter used for oversampling in the x direction (columns). 29 | 30 | * **over_y** (`int`) 31 | Parameter used for oversampling in the y direction (rows). 32 | 33 | **Methods:** 34 | """ 35 | 36 | def __init__(self, mesh_obj=None, over_x=1, over_y=1): 37 | 38 | self.pixel_dim = mesh_obj.images_obj.pixel_dim 39 | self.mesh_obj = mesh_obj 40 | self.u = None 41 | self.v = None 42 | self.over_x = over_x 43 | self.over_y = over_y 44 | 45 | super().__init__(mesh_obj=mesh_obj) 46 | 47 | def _registration(self, img_0, img_1, psearch, ptem, lengths, windows, gaps): 48 | """ 49 | Private method for estimating the displacements using the template matching technique with subpixel resolution. 50 | This method overrides the one in the parent class. 51 | 52 | **Input:** 53 | * **img_0** (`ndarray`) 54 | Image in time t. 55 | 56 | * **img_1** (`ndarray`) 57 | Image in time t + dt. 58 | 59 | * **psearch** (`tuple`) 60 | Upper left corner of the searching area. 61 | 62 | * **ptem** (`tuple`) 63 | Upper left corner of the template. 64 | 65 | * **lengths** (`tuple`) 66 | Lengths in x and y of the searching area (length_x, length_y). 67 | 68 | * **windows** (`tuple`) 69 | Lengths in x and y of the template (window_x, window_y). 70 | 71 | * **gaps** (`tuple`) 72 | Gaps between template and searching area (gap_x, gap_y). 73 | 74 | **Output/Returns:** 75 | * **px** (`float`) 76 | Displacement in x (columns). 77 | 78 | * **px** (`float`) 79 | Displacement in y (rows). 80 | """ 81 | 82 | l_x = lengths[0] 83 | l_y = lengths[1] 84 | window_x = windows[0] 85 | window_y = windows[1] 86 | 87 | # Image of the template. 88 | img_template = get_template_left(im_source=img_0, point=ptem, sidex=window_x, sidey=window_y) 89 | 90 | # Image of the searching area. 91 | img_search = get_template_left(im_source=img_1, point=psearch, sidex=l_x, sidey=l_y) 92 | 93 | # No subpixel resolution required. 94 | px, py = self._template_match_sk(img_search, img_template, mlx=self.over_x, mly=self.over_y) 95 | px = int(px) 96 | py = int(py) 97 | 98 | return px, py -------------------------------------------------------------------------------- /src/DICpy/DIC_2D/_post_processing.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import scipy as sp 3 | from sklearn.gaussian_process import GaussianProcessRegressor 4 | import matplotlib.pyplot as plt 5 | 6 | 7 | class PostProcessing: 8 | """ 9 | This class contains the methods for visualizing the results of the DIC analysis. 10 | **Input:** 11 | * **analysis_obj** (`object`) 12 | Object of the Analysis class. 13 | 14 | **Attributes:** 15 | 16 | * **analysis_obj** (`float`) 17 | Object of the Analysis class. 18 | 19 | * **mesh_obj** (`object`) 20 | Object of the RectangularMesh class. 21 | 22 | * **strain11** (`ndarray`) 23 | Strain xx at the center of each cell. 24 | 25 | * **strain22** (`ndarray`) 26 | Strain yy at the center of each cell. 27 | 28 | * **strain12** (`ndarray`) 29 | Strain xy at the center of each cell. 30 | 31 | * **strain21** (`ndarray`) 32 | Strain yx (equal to Strain xy) at the center of each cell. 33 | 34 | **Methods:** 35 | """ 36 | 37 | def __init__(self, analysis_obj=None): 38 | self.mesh_obj = analysis_obj.mesh_obj 39 | self.analysis_obj = analysis_obj 40 | self.strain11 = None 41 | self.strain22 = None 42 | self.strain12 = None 43 | self.strain21 = None 44 | 45 | def get_fields(self): 46 | 47 | """ 48 | Method to estimate the strain fields. 49 | 50 | **Input:** 51 | 52 | **Output/Returns:** 53 | """ 54 | 55 | # Derivative 56 | # d_ker = np.matrix([-1., 0, 1.]) 57 | u = self.analysis_obj.u 58 | v = self.analysis_obj.v 59 | pixel_dim = self.analysis_obj.pixel_dim 60 | 61 | centers = self.mesh_obj.centers 62 | zero_mat = np.zeros((np.shape(u)[1], np.shape(u)[2])) 63 | strain_matrix_11 = [] 64 | strain_matrix_12 = [] 65 | strain_matrix_21 = [] 66 | strain_matrix_22 = [] 67 | 68 | strain_matrix_11.append(zero_mat) 69 | strain_matrix_12.append(zero_mat) 70 | strain_matrix_21.append(zero_mat) 71 | strain_matrix_22.append(zero_mat) 72 | for k in range(np.shape(u)[0]): 73 | 74 | points = [] 75 | dx = [] 76 | dy = [] 77 | c = 0 78 | 79 | for i in range(np.shape(u)[1]): 80 | for j in range(np.shape(u)[2]): 81 | points.append([centers[c][0], centers[c][1]]) 82 | dx.append(u[k, i, j]) 83 | dy.append(v[k, i, j]) 84 | c = c + 1 85 | 86 | gpu = GaussianProcessRegressor(n_restarts_optimizer=10, normalize_y=True) 87 | gpu.fit(points, dx) 88 | 89 | gpv = GaussianProcessRegressor(n_restarts_optimizer=10, normalize_y=False) 90 | gpv.fit(points, dy) 91 | 92 | strain_11 = np.zeros((np.shape(u)[1], np.shape(u)[2])) 93 | strain_22 = np.zeros((np.shape(u)[1], np.shape(u)[2])) 94 | strain_12 = np.zeros((np.shape(u)[1], np.shape(u)[2])) 95 | strain_21 = np.zeros((np.shape(u)[1], np.shape(u)[2])) 96 | c = 0 97 | h = pixel_dim / 100 98 | 99 | for i in range(np.shape(u)[1]): 100 | for j in range(np.shape(u)[2]): 101 | p0 = [[centers[c][0], centers[c][1]]] 102 | p1 = [[centers[c][0] + h, centers[c][1]]] 103 | pred0ux = gpu.predict(p0)[0] 104 | pred1ux = gpu.predict(p1)[0] 105 | pred0vx = gpv.predict(p0)[0] 106 | pred1vx = gpv.predict(p1)[0] 107 | 108 | p0 = [[centers[c][0], centers[c][1]]] 109 | p1 = [[centers[c][0], centers[c][1] + h]] 110 | pred0uy = gpu.predict(p0)[0] 111 | pred1uy = gpu.predict(p1)[0] 112 | pred0vy = gpv.predict(p0)[0] 113 | pred1vy = gpv.predict(p1)[0] 114 | 115 | d11 = (pred1ux - pred0ux) / h 116 | d12 = (pred1uy - pred0uy) / h 117 | d21 = (pred1vx - pred0vx) / h 118 | d22 = (pred1vy - pred0vy) / h 119 | 120 | # Compute strain fields. 121 | strain_11[i, j] = d11 + 0.5 * (d11 ** 2 + d22 ** 2) 122 | strain_22[i, j] = d22 + 0.5 * (d11 ** 2 + d22 ** 2) 123 | strain_12[i, j] = 0.5 * (d12 + d21 + d11 * d12 + d21 * d22) 124 | strain_21[i, j] = 0.5 * (d12 + d21 + d11 * d12 + d21 * d22) 125 | 126 | c = c + 1 127 | 128 | strain_matrix_11.append(strain_11) 129 | strain_matrix_22.append(strain_22) 130 | strain_matrix_12.append(strain_12) 131 | strain_matrix_21.append(strain_21) 132 | 133 | self.strain11 = np.array(strain_matrix_11) 134 | self.strain22 = np.array(strain_matrix_22) 135 | self.strain12 = np.array(strain_matrix_12) 136 | self.strain21 = np.array(strain_matrix_21) 137 | 138 | def visualization(self, results="u", step=0, smooth=False): 139 | """ 140 | Method to plot the results in terms of displacements and strains. 141 | 142 | **Input:** 143 | * **results** (`str`) 144 | Visulaize the results: 145 | -'u': displacement x. 146 | -'v': displacement y. 147 | -'e11': strain xx. 148 | -'e22': strain yy: 149 | -'e12': strain xy. 150 | -'e21': strain yx. 151 | 152 | * **step** (`int`) 153 | Load step of interest. 154 | 155 | * **smooth** (`bool`) 156 | Gaussian filtering. 157 | 158 | **Output/Returns:** 159 | """ 160 | 161 | if step < 0: 162 | raise ValueError("DICpy: `step` must be larger than or equal to 0.") 163 | 164 | if step > len(self.analysis_obj.u): 165 | raise ValueError("DICpy: `step` cannot be larger than the number of steps in the analysis.") 166 | 167 | if not isinstance(step, int): 168 | raise TypeError("DICpy: step must be an integer.") 169 | 170 | point_a = self.mesh_obj.point_a 171 | point_b = self.mesh_obj.point_b 172 | images = self.mesh_obj.images_obj.images 173 | stepx = self.mesh_obj.stepx 174 | stepy = self.mesh_obj.stepy 175 | 176 | self.get_fields() 177 | 178 | u_ = self.analysis_obj.u 179 | v_ = self.analysis_obj.v 180 | 181 | if step == 0: 182 | u = np.zeros(np.shape(u_[0, :, :])) 183 | v = np.zeros(np.shape(u_[0, :, :])) 184 | else: 185 | u = u_[step - 1, :, :] 186 | v = v_[step - 1, :, :] 187 | 188 | e11 = self.strain11[step, :, :] 189 | e12 = self.strain12[step, :, :] 190 | e21 = self.strain21[step, :, :] 191 | e22 = self.strain22[step, :, :] 192 | 193 | if results == 'u': 194 | mask = u 195 | elif results == 'v': 196 | mask = v 197 | elif results == 'abs': 198 | mask = np.sqrt(v ** 2 + u ** 2) 199 | elif results == 'e11': 200 | mask = e11 201 | elif results == 'e12': 202 | mask = e12 203 | elif results == 'e21': 204 | mask = e21 205 | elif results == 'e22': 206 | mask = e22 207 | else: 208 | raise ValueError('DICpy: not valid option for results.') 209 | 210 | img = images[step] 211 | 212 | x = np.arange(0, np.shape(img)[1]) 213 | y = np.arange(0, np.shape(img)[0]) 214 | X, Y = np.meshgrid(x, y) 215 | 216 | xm = np.arange(min(point_a[0], point_b[0]), max(point_a[0], point_b[0])) 217 | ym = np.arange(min(point_a[1], point_b[1]), max(point_a[1], point_b[1])) 218 | Xm, Ym = np.meshgrid(xm, ym) 219 | 220 | extent = np.min(x), np.max(x), np.min(y), np.max(y) 221 | extentm = np.min(xm), np.max(xm), np.shape(img)[0] - np.max(ym), np.shape(img)[0] - np.min(ym) 222 | 223 | if smooth: 224 | lx = stepx[1] - stepx[0] 225 | ly = stepy[1] - stepy[0] 226 | sigma = 0.005 * max(lx, ly) 227 | mask = sp.ndimage.gaussian_filter(mask, sigma=sigma) 228 | 229 | plt.close() 230 | fig = plt.figure(frameon=False) 231 | im1 = plt.imshow(img, cmap=plt.cm.gray, interpolation='bilinear', extent=extent) 232 | im2 = plt.imshow(mask, cmap=plt.cm.hsv, alpha=.7, interpolation='bilinear', extent=extentm) 233 | im3 = plt.plot([0, np.shape(img)[1]], [0, np.shape(img)[0]], '.') 234 | plt.xlim(0, np.shape(img)[1]) 235 | plt.ylim(0, np.shape(img)[0]) 236 | plt.colorbar(im2) 237 | plt.show() 238 | -------------------------------------------------------------------------------- /src/DICpy/DIC_2D/_regular_grid.py: -------------------------------------------------------------------------------- 1 | from DICpy.utils import _close 2 | import numpy as np 3 | import matplotlib.patches as patches 4 | import matplotlib.pyplot as plt 5 | from matplotlib.widgets import Button 6 | 7 | 8 | class RegularGrid: 9 | """ 10 | This class contains the methods for creating a rectangular grid used in the DIC analysis. 11 | 12 | **Input:** 13 | * **images_obj** (`object`) 14 | Object containing the speckle images, reference images, and calibration images, as well as calibration 15 | parameters. 16 | 17 | **Attributes:** 18 | * **images_obj** (`object`) 19 | Object containing the speckle images, reference images, and calibration images, as well as calibration 20 | parameters. 21 | 22 | * **stepx** (`ndarray`) 23 | Steps for the grid in the x direction (columns). 24 | 25 | * **stepy** (`ndarray`) 26 | Steps for the grid in the y direction (rows). 27 | 28 | * **centers** (`list`) 29 | Center of each cell in the grid. 30 | 31 | * **wind** (`list`) 32 | Dimensions in both x and y dimensions of each cell. 33 | 34 | * **xp** (`ndarray`) 35 | Position of the upper right corner of each cell (grid in the x direction - columns). 36 | 37 | * **yp** (`ndarray`) 38 | Position of the upper right corner of each cell (grid in the y direction - rows). 39 | 40 | * **nx** (`int`) 41 | Number of nodes in the x direction (columns). 42 | 43 | * **ny** (`int`) 44 | Number of nodes in the y direction (rows). 45 | 46 | * **point_a** (`float`) 47 | First corner of the region of interest (ROI). 48 | 49 | * **point_b** (`float`) 50 | Opposed corner of the region of interest (ROI). 51 | 52 | **Methods:** 53 | """ 54 | 55 | def __init__(self, images_obj=None): 56 | self.images_obj = images_obj 57 | self.stepx = None 58 | self.stepy = None 59 | self.centers = None 60 | self.wind = None 61 | self.xp = None 62 | self.yp = None 63 | self.nx = None 64 | self.ny = None 65 | self.elem = None 66 | self.point_a = None 67 | self.point_b = None 68 | 69 | def define_mesh(self, point_a=None, point_b=None, nx=2, ny=2, show_grid=False): 70 | """ 71 | Method to construct the rectangular grid used in the DIC analysis. 72 | 73 | **Input:** 74 | * **point_a** (`float`) 75 | First corner of the region of interest (ROI). 76 | 77 | * **point_b** (`float`) 78 | Corner opposed to point_b of the region of interest (ROI). 79 | 80 | * **nx** (`int`) 81 | Number of nodes in the x direction (columns), default 2. 82 | 83 | * **ny** (`int`) 84 | Number of nodes in the y direction (rows), default 2. 85 | 86 | * **show_grid** (`int`) 87 | This functionality is only available when `point_a` and `point_b` are provided. 88 | When `noplot` is True, the grid is not plotted for express calculations. 89 | 90 | **Output/Returns:** 91 | """ 92 | 93 | maxl = max(self.images_obj.lx, self.images_obj.ly) 94 | self.nx = nx 95 | self.ny = ny 96 | 97 | global rcirc, ax, fig, coords, cid 98 | rcirc = maxl / 160 99 | 100 | if point_a is None or point_b is None: 101 | 102 | coords = [] 103 | 104 | fig = plt.figure() 105 | ax = fig.add_subplot(111) 106 | cid = fig.canvas.mpl_connect('button_press_event', self._mouse_click) 107 | plt.imshow(self.images_obj.images[0], cmap="gray") 108 | plt.show() 109 | 110 | point_a = coords[0] 111 | point_b = coords[1] 112 | 113 | else: 114 | 115 | if show_grid: 116 | fig = plt.figure() 117 | ax = fig.add_subplot(111) 118 | 119 | circle = plt.Circle(point_a, rcirc, color='red') 120 | ax.add_patch(circle) 121 | fig.canvas.draw() # this line was missing earlier 122 | circle = plt.Circle(point_b, rcirc, color='red') 123 | ax.add_patch(circle) 124 | fig.canvas.draw() # this line was missing earlier 125 | 126 | lx = (point_b[0] - point_a[0]) 127 | ly = (point_b[1] - point_a[1]) 128 | rect = patches.Rectangle(point_a, lx, ly, linewidth=1, edgecolor='None', facecolor='b', alpha=0.4) 129 | ax.add_patch(rect) 130 | fig.canvas.draw() # this line was missing earlier 131 | 132 | plt.imshow(self.images_obj.images[0], cmap="gray") 133 | plt.show(block=False) 134 | plt.close() 135 | 136 | xa = point_a[0] 137 | xb = point_b[0] 138 | ya = point_a[1] 139 | yb = point_b[1] 140 | 141 | self.point_a = point_a 142 | self.point_b = point_b 143 | 144 | stepx = np.sort(np.linspace(xa, xb, nx)) 145 | stepy = np.sort(np.linspace(ya, yb, ny)) 146 | 147 | # Transform from continuous to discrete. 148 | stepx = np.array([round(x) for x in stepx]) 149 | stepy = np.array([round(y) for y in stepy]) 150 | 151 | if min(np.diff(stepx)) < 15: 152 | raise ValueError('DICpy: small subset, reduce nx.') 153 | 154 | if min(np.diff(stepy)) < 15: 155 | raise ValueError('DICpy: small subset, reduce ny.') 156 | 157 | self.stepx = stepx 158 | self.stepy = stepy 159 | 160 | xp, yp = np.meshgrid(stepx, stepy) 161 | 162 | centers = [] 163 | wind = [] 164 | 165 | for i in range(ny - 1): 166 | for j in range(nx - 1): 167 | l_x = abs(xp[i + 1, j + 1] - xp[i, j]) 168 | l_y = abs(yp[i + 1, j + 1] - yp[i, j]) 169 | xc = (xp[i + 1, j + 1] + xp[i, j]) / 2 170 | yc = (yp[i + 1, j + 1] + yp[i, j]) / 2 171 | centers.append((xc, yc)) 172 | wind.append((l_x, l_y)) 173 | 174 | self.xp = xp 175 | self.yp = yp 176 | self.centers = centers 177 | self.wind = wind 178 | 179 | # plt.close() 180 | if show_grid: 181 | fig = plt.figure() 182 | ax = fig.add_subplot(111) 183 | for i in range(nx): 184 | for j in range(ny): 185 | xx = stepx[i] 186 | yy = stepy[j] 187 | circle = plt.Circle((xx, yy), rcirc, color='red') 188 | ax.add_patch(circle) 189 | fig.canvas.draw() # this line was missing earlier 190 | 191 | lx = (point_b[0] - point_a[0]) 192 | ly = (point_b[1] - point_a[1]) 193 | rect = patches.Rectangle(point_a, lx, ly, linewidth=1, edgecolor='None', facecolor='b', alpha=0.4) 194 | ax.add_patch(rect) 195 | fig.canvas.draw() 196 | plt.imshow(self.images_obj.images[0], cmap="gray") 197 | axclose = plt.axes([0.81, 0.05, 0.1, 0.075]) 198 | bclose = Button(axclose, 'Next') 199 | # bclose.on_clicked(self._close) 200 | bclose.on_clicked(_close) 201 | plt.show() 202 | 203 | def _get_elements_Q4(self): 204 | """ 205 | Private method to determine the elements of the mesh (under construction!). 206 | 207 | **Input:** 208 | 209 | **Output/Returns:** 210 | """ 211 | 212 | xp = self.xp 213 | yp = self.yp 214 | nelem = (self.nx - 1) * (self.ny - 1) 215 | 216 | node_id = 0 217 | elem = [] 218 | for i in range(self.ny - 1): 219 | for j in range(self.nx - 1): 220 | nodes = [(xp[i, j], yp[i, j]), (xp[i, j + 1], yp[i, j + 1]), (xp[i + 1, j], yp[i + 1, j]), 221 | (xp[i + 1, j + 1], yp[i + 1, j + 1])] 222 | dic = {"nodes": nodes, "id": node_id} 223 | elem.append(dic) 224 | node_id = node_id + 1 225 | 226 | self.elem = elem 227 | 228 | @staticmethod 229 | def _mouse_click(event): 230 | """ 231 | Private method: mouse click to select the ROI. 232 | 233 | **Input:** 234 | * **event** (`event`) 235 | Click event. 236 | 237 | **Output/Returns:** 238 | """ 239 | 240 | global x, y 241 | x, y = event.xdata, event.ydata 242 | 243 | if event.button: 244 | circle = plt.Circle((event.xdata, event.ydata), rcirc, color='red') 245 | ax.add_patch(circle) 246 | fig.canvas.draw() # this line was missing earlier 247 | 248 | global coords 249 | coords.append((x, y)) 250 | 251 | if len(coords) == 2: 252 | fig.canvas.mpl_disconnect(cid) 253 | 254 | xa = coords[0][0] 255 | ya = coords[0][1] 256 | xb = coords[1][0] 257 | yb = coords[1][1] 258 | lx = (xb - xa) 259 | ly = (yb - ya) 260 | rect = patches.Rectangle((xa, ya), lx, ly, linewidth=1, edgecolor='None', facecolor='b', alpha=0.4) 261 | ax.add_patch(rect) 262 | fig.canvas.draw() # this line was missing earlier 263 | plt.close() 264 | 265 | return coords 266 | 267 | 268 | 269 | -------------------------------------------------------------------------------- /src/DICpy/DIC_2D/_synthetic.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import cv2 3 | import skimage as sk 4 | 5 | 6 | class Synthetic: 7 | """ 8 | This class contains the methods for generate synthetic data for the DIC analysis. 9 | **Input:** 10 | 11 | **Attributes:** 12 | 13 | * **num_images** (`int`) 14 | Number of images. 15 | 16 | * **size** (`tuple`) 17 | Images shapse. 18 | 19 | * **extension** (`str`) 20 | Extension to save the files. 21 | 22 | * **random_state** (`int`) 23 | Random seed 24 | 25 | * **save_images** (`bool`) 26 | Boolean variable to save the generated images. 27 | 28 | **Methods:** 29 | """ 30 | 31 | def __init__(self, num_images=None, size=None, extension='png', random_state=None, save_images=True): 32 | 33 | self.random_state = random_state 34 | self.num_images = num_images 35 | self.size = size 36 | self.extension = extension 37 | self.images = None 38 | self.save_images = save_images 39 | 40 | if self.random_state is None: 41 | self.random_state = np.random.randint(0, high=99999, size=1, dtype=int)[0] 42 | 43 | def generate_images(self, num_speckles=100, sigma=2, displacement_x=None, displacement_y=None, shear=None): 44 | """ 45 | Generate images. 46 | 47 | **Input:** 48 | * **num_speckles** (`int`) 49 | Number of speckles per images. 50 | 51 | * **sigma** (`float`) 52 | Variable to control the size the speckles. 53 | 54 | * **displacement_x** (`list`) 55 | Displacement x to include in each image. 56 | 57 | * **displacement_y** (`list`) 58 | Displacement y to include in each image. 59 | 60 | * **shear** (`list`) 61 | Shear to include in each image. 62 | 63 | * **Output/Returns:** 64 | """ 65 | 66 | n = self.num_images 67 | 68 | if len(displacement_x) != n: 69 | raise ValueError('DICpy: size of `displacement_x` must be equal to num_images.') 70 | 71 | if len(displacement_y) != n: 72 | raise ValueError('DICpy: size of `displacement_y` must be equal to num_images.') 73 | 74 | if len(shear) != n: 75 | raise ValueError('DICpy: size of `shear` must be equal to num_images.') 76 | 77 | images = [] 78 | img0 = self._gen_gaussian(dx=displacement_x[0], dy=displacement_y[0], num_speckles=num_speckles, sigma=sigma) 79 | images.append(img0) 80 | if self.save_images: 81 | cv2.imwrite(str(0) + '.' + self.extension, img0) 82 | 83 | print(img0) 84 | 85 | for i in np.arange(1, n): 86 | 87 | t = (displacement_x[i], displacement_y[i]) 88 | 89 | # Create Afine transform 90 | afine_tf = sk.transform.AffineTransform(matrix=None, scale=None, rotation=None, shear=shear, translation=t) 91 | 92 | # Apply transform to image data 93 | img = sk.transform.warp(img0, inverse_map=afine_tf,mode='constant', cval=1) 94 | img = np.round(img * 255).astype(np.uint8) 95 | images.append(img) 96 | 97 | if self.save_images: 98 | cv2.imwrite(str(i)+'.'+self.extension, img) 99 | 100 | self.images = images 101 | 102 | def _gen_gaussian(self, dx=None, dy=None, num_speckles=100, sigma=2): 103 | """ 104 | Private method: generate the bivariate Gaussians. 105 | 106 | **Input:** 107 | * **dx** (`float`) 108 | Displacements in x. 109 | 110 | * **dy** (`float`) 111 | Displacements in y. 112 | 113 | * **sigma** (`float`) 114 | Variable to control the size the speckles. 115 | 116 | * **num_speckles** (`int`) 117 | Number of speckles per images. 118 | 119 | * **Output/Returns:** 120 | * **img** (`ndarray`) 121 | Generated image. 122 | """ 123 | 124 | nx, ny = self.size 125 | np.random.seed(self.random_state) 126 | 127 | xk = np.random.randint(0, high=nx, size=num_speckles, dtype=int) 128 | yk = np.random.randint(0, high=ny, size=num_speckles, dtype=int) 129 | vald = np.zeros((ny, nx, num_speckles)) 130 | img = np.zeros((ny, nx)) 131 | 132 | for k in range(num_speckles): 133 | 134 | I0 = np.random.randint(0, high=256, size=1, dtype=int)[0] 135 | 136 | for i in range(ny): 137 | for j in range(nx): 138 | vald[i, j, k] = I0 * np.exp(-((i - yk[k] - dy) ** 2 + (j - xk[k] - dx) ** 2) / (sigma ** 2)) 139 | 140 | img = img + np.floor(vald[:, :, k]) 141 | 142 | img = np.floor(255 * (1 - img / np.max(img))) 143 | img = img.astype(np.uint8) 144 | 145 | return img 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /src/DICpy/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | DICpy: digital imahe correlation with python 3 | ======================================== 4 | """ 5 | 6 | import pkg_resources 7 | 8 | import DICpy.DIC_2D 9 | import DICpy.utils 10 | 11 | from DICpy.DIC_2D import * 12 | from DICpy.utils import * 13 | 14 | try: 15 | __version__ = pkg_resources.get_distribution("DICpy").version 16 | except pkg_resources.DistributionNotFound: 17 | __version__ = None 18 | 19 | try: 20 | __version__ = pkg_resources.get_distribution("DICpy").version 21 | except pkg_resources.DistributionNotFound: 22 | __version__ = None 23 | -------------------------------------------------------------------------------- /src/DICpy/math4dic.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy import signal 3 | from scipy.interpolate import RectBivariateSpline 4 | import cv2 5 | 6 | 7 | def norm_xcorr(f, g): 8 | """ 9 | Normalized cross correlation. 10 | 11 | **Input:** 12 | * **f** (`ndarray`) 13 | Image. 14 | 15 | * **g** (`ndarray`) 16 | Image. 17 | 18 | **Output/Returns:** 19 | * **c** (`float`) 20 | Correlation. 21 | """ 22 | mean_f = np.mean(f) 23 | mean_g = np.mean(g) 24 | 25 | sum_fg = np.sum((f - mean_f) * (g - mean_g)) 26 | sum_f2 = np.sum((f - mean_f) ** 2) 27 | sum_g2 = np.sum((g - mean_g) ** 2) 28 | 29 | c = sum_fg / np.sqrt(sum_f2 * sum_g2) 30 | 31 | return c 32 | 33 | 34 | def interpolate_template2(f=None, x=None, y=None, dx=0, dy=0): 35 | """ 36 | Method of interpolation. 37 | 38 | **Input:** 39 | * **f** (`ndarray`) 40 | Source image. 41 | 42 | * **x** (`ndarray`) 43 | Integer pixel position in the x direction. 44 | 45 | * **y** (`ndarray`) 46 | Integer pixel position in the y direction. 47 | 48 | * **dx** (`float`) 49 | Sub-pixel increment in the x direction. 50 | 51 | * **dy** (`float`) 52 | Sub-pixel increment in the y direction. 53 | 54 | **Output/Returns:** 55 | * **z** (`ndarray`) 56 | Interpolated image. 57 | """ 58 | 59 | ly, lx = np.shape(f) 60 | # Regularly-spaced, coarse grid 61 | x0 = np.arange(0, lx) 62 | y0 = np.arange(0, ly) 63 | # X, Y = np.meshgrid(x, y) 64 | 65 | interp_spline = RectBivariateSpline(y0, x0, f) 66 | 67 | xt = x + dx 68 | yt = y + dy 69 | 70 | z = np.zeros((len(yt), len(xt))) 71 | for i in range(len(yt)): 72 | for j in range(len(xt)): 73 | z[i, j] = interp_spline(yt[i], xt[j]) 74 | 75 | return z 76 | 77 | 78 | def interpolate_template(f=None, x=None, y=None, dx=0, dy=0, dim=None): 79 | """ 80 | Method of interpolation. 81 | 82 | **Input:** 83 | * **f** (`ndarray`) 84 | Source image. 85 | 86 | * **x** (`ndarray`) 87 | Integer pixel position in the x direction. 88 | 89 | * **y** (`ndarray`) 90 | Integer pixel position in the y direction. 91 | 92 | * **dx** (`float`) 93 | Sub-pixel increment in the x direction. 94 | 95 | * **dy** (`float`) 96 | Sub-pixel increment in the y direction. 97 | 98 | **Output/Returns:** 99 | * **z** (`ndarray`) 100 | Interpolated image. 101 | """ 102 | 103 | if not isinstance(f, RectBivariateSpline): 104 | dim = np.shape(f) 105 | ly = dim[0] 106 | lx = dim[1] 107 | 108 | x0 = np.arange(0, lx) 109 | y0 = np.arange(0, ly) 110 | 111 | interp_spline = RectBivariateSpline(y0, x0, f) 112 | 113 | else: 114 | ly = dim[0] 115 | lx = dim[1] 116 | interp_spline = f 117 | 118 | xt = x + dx 119 | yt = y + dy 120 | 121 | z = np.zeros((len(yt), len(xt))) 122 | for i in range(len(yt)): 123 | for j in range(len(xt)): 124 | z[i, j] = interp_spline(yt[i], xt[j]) 125 | 126 | return z 127 | 128 | def gradient(img, k): 129 | """ 130 | Estimate the gradient of images using Sobel filters from OpenCV-Python. 131 | 132 | **Input:** 133 | * **img** (`ndarray`) 134 | Image. 135 | 136 | * **k** (`ndarray`) 137 | Order of approximation. 138 | 139 | **Output/Returns:** 140 | * **gx** (`ndarray`) 141 | Derivative in x (columns). 142 | 143 | * **gy** (`ndarray`) 144 | Derivative in y (rows). 145 | """ 146 | gx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=k) 147 | gy = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=k) 148 | 149 | return gx, gy 150 | 151 | 152 | def derivatives(img1, img2=None): 153 | """ 154 | First order derivatives in x, y, and time (for two images). 155 | 156 | **Input:** 157 | * **img1** (`ndarray`) 158 | Image in time t. 159 | 160 | * **img2** (`ndarray`) 161 | Image in time t + dt. 162 | 163 | **Output/Returns:** 164 | * **gx** (`ndarray`) 165 | Derivative in x (columns) for img1. 166 | 167 | * **gy** (`ndarray`) 168 | Derivative in y (rows) for img1. 169 | 170 | * **gt** (`ndarray`) 171 | Derivative in time for img1 (optiional). 172 | """ 173 | # Derivatives 174 | kernel_x = np.array([[-1., 1.], [-1., 1.]]) 175 | kernel_y = np.array([[-1., -1.], [1., 1.]]) 176 | kernel_t = np.array([[1., 1.], [1., 1.]]) 177 | 178 | img1 = img1 / 255. # normalize pixels 179 | 180 | mode = 'same' 181 | bdry = 'symm' 182 | gx = signal.convolve2d(img1, kernel_x, boundary=bdry, mode=mode) 183 | gy = signal.convolve2d(img1, kernel_y, boundary=bdry, mode=mode) 184 | 185 | if img2 is not None: 186 | img2 = img2 / 255. # normalize pixels 187 | gt = signal.convolve2d(img2, kernel_t, boundary=bdry, mode=mode) + \ 188 | signal.convolve2d(img2, -kernel_t, boundary=bdry, mode=mode) 189 | 190 | return gx, gy, gt 191 | 192 | else: 193 | 194 | return gx, gy 195 | -------------------------------------------------------------------------------- /src/DICpy/utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | 4 | 5 | def _mouse_click(event): 6 | """ 7 | Private: function to capture the coordinates of a mouse click over an image. 8 | 9 | **Input:** 10 | * **event** (`object`) 11 | Event of mouse click. 12 | 13 | **Output/Returns:** 14 | * **coords** (`list`) 15 | Position of the mouse click in the image. 16 | """ 17 | 18 | global x, y 19 | x, y = event.xdata, event.ydata 20 | 21 | if event.button: 22 | circle = plt.Circle((event.xdata, event.ydata), rcirc, color='red') 23 | ax.add_patch(circle) 24 | fig.canvas.draw() # this line was missing earlier 25 | 26 | global coords 27 | coords.append((x, y)) 28 | print(x, y) 29 | if len(coords) == 2: 30 | fig.canvas.mpl_disconnect(cid) 31 | 32 | xa = coords[0][0] 33 | ya = coords[0][1] 34 | xb = coords[1][0] 35 | yb = coords[1][1] 36 | lx = (xb - xa) 37 | ly = (yb - ya) 38 | rect = patches.Rectangle((xa, ya), lx, ly, linewidth=1, edgecolor='None', facecolor='b', alpha=0.4) 39 | ax.add_patch(rect) 40 | fig.canvas.draw() # this line was missing earlier 41 | plt.close() 42 | 43 | return coords 44 | 45 | 46 | def get_template(im_source=None, center=None, side=None): 47 | """ 48 | Get a square template from image considering the a central coordinate. 49 | 50 | **Input:** 51 | * **im_source** (`ndarray`) 52 | Image source is the image from where the template is retrieved. 53 | 54 | * **center** (`list/tuple/ndarray`) 55 | Coordinates of the central pixel of the template. 56 | 57 | * **side** (`int`) 58 | How many pixels in each side of the central pixel will be used to construct the template. 59 | 60 | * **Output/Returns:** 61 | * **im_template** (`ndarray`) 62 | Template retrieved from `im_source`. 63 | 64 | * **id_row** (`ndarray`) 65 | Coordinates (rows) for locating the template in `im_source`. 66 | 67 | * **id_col** (`ndarray`) 68 | Coordinates (columns) for locating the template in `im_source`. 69 | """ 70 | 71 | # Crop image (im_source) to get a square patch (im_template) with center in 72 | # (center_row, center_col) with width (in pixels) given as an argument. 73 | if not isinstance(side, int): 74 | raise ValueError('pyCrack: side must be an integer!') 75 | 76 | else: 77 | center_row = center[0] 78 | center_col = center[1] 79 | row_0 = center_row - side 80 | col_0 = center_col - side 81 | 82 | row_1 = center_row + side 83 | col_1 = center_col + side 84 | 85 | id_row = np.arange(row_0, row_1 + 1) 86 | id_col = np.arange(col_0, col_1 + 1) 87 | 88 | im_template = im_source[np.ix_(id_row, id_col)] 89 | 90 | return im_template, id_row, id_col 91 | 92 | 93 | def get_template_left(im_source=None, point=None, sidex=None, sidey=None): 94 | """ 95 | Get a rectangular template from image considering the upper left corner as a reference. 96 | 97 | **Input:** 98 | * **im_source** (`ndarray`) 99 | Image source is the image from where the template is retrieved. 100 | 101 | * **point** (`list/tuple/ndarray`) 102 | Coordinates of the upper left coorner pixel (reference pixel) of the template. 103 | 104 | * **sidex** (`int`) 105 | Template size in x direction (columns). 106 | 107 | * **sidey** (`int`) 108 | Template size in y direction (rows). 109 | 110 | * **Output/Returns:** 111 | * **im_template** (`ndarray`) 112 | Template retrieved from `im_source`. 113 | """ 114 | 115 | p_row = point[0] 116 | p_col = point[1] 117 | row_0 = p_row 118 | col_0 = p_col 119 | 120 | row_1 = p_row + sidey - 1 121 | col_1 = p_col + sidex - 1 122 | 123 | id_row = np.arange(row_0, row_1 + 1) 124 | id_col = np.arange(col_0, col_1 + 1) 125 | 126 | im_template = im_source[np.ix_(id_row, id_col)] 127 | 128 | return im_template 129 | 130 | 131 | def _close(event): 132 | """ 133 | Private: function to close events such as mouse click. 134 | 135 | **Input:** 136 | * **event** (`object`) 137 | Event of mouse click. 138 | """ 139 | 140 | plt.close() 141 | --------------------------------------------------------------------------------