├── .ipynb_checkpoints ├── Particle Size and Separation-checkpoint.ipynb ├── Particle-Size-and-Separation-Copy1-checkpoint.ipynb ├── Particle-Size-and-Separation-checkpoint.ipynb └── Particle-Size-and-Separation-clean-checkpoint.ipynb ├── .travis.yml ├── 0_5cutoff.png ├── 3cutoff.png ├── 7cutoff.png ├── Particle Size and Separation.ipynb ├── Particle-Size-and-Separation-Copy1.ipynb ├── Particle-Size-and-Separation-clean.ipynb ├── Particle-Size-and-Separation.ipynb ├── PtnanoCenk0.5et_171218_AM.0_00002_1.png ├── initial ├── notebooks ├── .ipynb_checkpoints │ └── function_outlines-checkpoint.ipynb └── function_outlines.ipynb ├── npia.egg-info ├── .ipynb_checkpoints │ └── PKG-INFO-checkpoint ├── PKG-INFO ├── SOURCES.txt ├── dependency_links.txt └── top_level.txt ├── npia ├── .ipynb_checkpoints │ ├── core-checkpoint.py │ ├── preparation-checkpoint.py │ ├── processing-checkpoint.py │ └── version-checkpoint.py ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-36.pyc │ ├── core.cpython-36.pyc │ └── version.cpython-36.pyc ├── core.py ├── tests │ ├── .ipynb_checkpoints │ │ ├── __init__-checkpoint.py │ │ └── test_core-checkpoint.py │ ├── PtnanoCenk0.5et_171218_AM.0_00002_1.png │ ├── __init__.py │ └── test_core.py ├── untitled └── version.py ├── readme.md ├── requirements.txt ├── setup.py └── watershed.png /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - 3.6 5 | 6 | branches: 7 | only: 8 | - master 9 | 10 | install: 11 | - pip install coverage 12 | - pip install coveralls 13 | - pip install flake8 14 | - pip install nose 15 | - pip install -r requirements.txt 16 | 17 | # for production, remove the --exit-zero 18 | # this side-steps failing build on PEP8 violations 19 | before_script: 20 | - flake8 --exit-zero npia 21 | 22 | script: 23 | - nosetests --with-coverage --cover-erase --cover-package=npia 24 | 25 | after_success: 26 | - coverage report 27 | - coveralls 28 | -------------------------------------------------------------------------------- /0_5cutoff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirabala/Nano-Particle-Image-Analysis/225194d17a315653cfd5c9e07d47b87729f00ab0/0_5cutoff.png -------------------------------------------------------------------------------- /3cutoff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirabala/Nano-Particle-Image-Analysis/225194d17a315653cfd5c9e07d47b87729f00ab0/3cutoff.png -------------------------------------------------------------------------------- /7cutoff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirabala/Nano-Particle-Image-Analysis/225194d17a315653cfd5c9e07d47b87729f00ab0/7cutoff.png -------------------------------------------------------------------------------- /PtnanoCenk0.5et_171218_AM.0_00002_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirabala/Nano-Particle-Image-Analysis/225194d17a315653cfd5c9e07d47b87729f00ab0/PtnanoCenk0.5et_171218_AM.0_00002_1.png -------------------------------------------------------------------------------- /initial: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirabala/Nano-Particle-Image-Analysis/225194d17a315653cfd5c9e07d47b87729f00ab0/initial -------------------------------------------------------------------------------- /notebooks/.ipynb_checkpoints/function_outlines-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Function Stubs\n", 8 | "- break functions into **sub-modules**\n", 9 | "- decide on what the **_inputs_** and **_outputs_** are for each function" 10 | ] 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "metadata": {}, 15 | "source": [ 16 | "## Load Image and Proprocessing" 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": null, 22 | "metadata": {}, 23 | "outputs": [], 24 | "source": [ 25 | "def load_image(image_file, scale):\n", 26 | " return image\n", 27 | "\n", 28 | "def get_scale_bar(): # <<\n", 29 | " return #\n", 30 | " # These two functions are connected.\n", 31 | "def crop_image(): # \n", 32 | " get_scale_bar() # <<\n", 33 | " return #" 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "metadata": {}, 39 | "source": [ 40 | "## Image Processing" 41 | ] 42 | }, 43 | { 44 | "cell_type": "code", 45 | "execution_count": null, 46 | "metadata": {}, 47 | "outputs": [], 48 | "source": [ 49 | "def verify_thresholding(): # <<\n", 50 | " return #\n", 51 | " # These two functions are connected.\n", 52 | "def thresholding(): #\n", 53 | " verify_thresholding() # <<\n", 54 | " return #\n", 55 | "\n", 56 | "def fit_gaussians():\n", 57 | " return\n", 58 | "\n", 59 | "def distance_transformation():\n", 60 | " return\n", 61 | "\n", 62 | "def watershed():\n", 63 | " return" 64 | ] 65 | }, 66 | { 67 | "cell_type": "markdown", 68 | "metadata": {}, 69 | "source": [ 70 | "## Image Analysis" 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": null, 76 | "metadata": {}, 77 | "outputs": [], 78 | "source": [ 79 | "def particle_distribution():\n", 80 | " return\n", 81 | "\n", 82 | "def separation_analysis():\n", 83 | " return\n", 84 | "\n", 85 | "def save_results():\n", 86 | " return" 87 | ] 88 | }, 89 | { 90 | "cell_type": "markdown", 91 | "metadata": {}, 92 | "source": [ 93 | "## Uses" 94 | ] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": null, 99 | "metadata": {}, 100 | "outputs": [], 101 | "source": [ 102 | "import npia\n", 103 | "\n", 104 | "img = npia.load_image(image_file)\n", 105 | "threshold = npia.threshold(image_file)\n", 106 | "labels = npia.watershed(threshold)" 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": null, 112 | "metadata": {}, 113 | "outputs": [], 114 | "source": [ 115 | "class AFMImage:\n", 116 | " def __init__(self, image_file, scale_bar=None, ub=None, lb=None):\n", 117 | " self.image = cv2.imread(image_file)\n", 118 | " self.original_image = None\n", 119 | " self.scale_bar = scale_bar\n", 120 | " self.upper_bound = ub\n", 121 | " self.lower_bound = lb\n", 122 | " \n", 123 | " self.n_particles = None\n", 124 | " self.particle_distances = [] # (x, y, radius, nearest_neighbor_dist)\n", 125 | " return\n", 126 | " \n", 127 | " def load_image(self, image_file):\n", 128 | " return\n", 129 | " \n", 130 | " def watershed(self):\n", 131 | " cv2.watershed(self.image)" 132 | ] 133 | }, 134 | { 135 | "cell_type": "code", 136 | "execution_count": null, 137 | "metadata": {}, 138 | "outputs": [], 139 | "source": [ 140 | "img = npia.AFMImage(image_file, scale)\n", 141 | "img.prepare()\n", 142 | "img.threshold()\n", 143 | "num_particles = img.n_particles" 144 | ] 145 | } 146 | ], 147 | "metadata": { 148 | "kernelspec": { 149 | "display_name": "Python 3", 150 | "language": "python", 151 | "name": "python3" 152 | }, 153 | "language_info": { 154 | "codemirror_mode": { 155 | "name": "ipython", 156 | "version": 3 157 | }, 158 | "file_extension": ".py", 159 | "mimetype": "text/x-python", 160 | "name": "python", 161 | "nbconvert_exporter": "python", 162 | "pygments_lexer": "ipython3", 163 | "version": "3.6.5" 164 | } 165 | }, 166 | "nbformat": 4, 167 | "nbformat_minor": 2 168 | } 169 | -------------------------------------------------------------------------------- /notebooks/function_outlines.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Function Stubs\n", 8 | "- break functions into **sub-modules**\n", 9 | "- decide on what the **_inputs_** and **_outputs_** are for each function" 10 | ] 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "metadata": {}, 15 | "source": [ 16 | "## Load Image and Proprocessing" 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": null, 22 | "metadata": {}, 23 | "outputs": [], 24 | "source": [ 25 | "def load_image(image_file, scale):\n", 26 | " return image\n", 27 | "\n", 28 | "def get_scale_bar(): # <<\n", 29 | " return #\n", 30 | " # These two functions are connected.\n", 31 | "def crop_image(): # \n", 32 | " get_scale_bar() # <<\n", 33 | " return #" 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "metadata": {}, 39 | "source": [ 40 | "## Image Processing" 41 | ] 42 | }, 43 | { 44 | "cell_type": "code", 45 | "execution_count": null, 46 | "metadata": {}, 47 | "outputs": [], 48 | "source": [ 49 | "def verify_thresholding(): # <<\n", 50 | " return #\n", 51 | " # These two functions are connected.\n", 52 | "def thresholding(): #\n", 53 | " verify_thresholding() # <<\n", 54 | " return #\n", 55 | "\n", 56 | "def fit_gaussians():\n", 57 | " return\n", 58 | "\n", 59 | "def distance_transformation():\n", 60 | " return\n", 61 | "\n", 62 | "def watershed():\n", 63 | " return" 64 | ] 65 | }, 66 | { 67 | "cell_type": "markdown", 68 | "metadata": {}, 69 | "source": [ 70 | "## Image Analysis" 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": null, 76 | "metadata": {}, 77 | "outputs": [], 78 | "source": [ 79 | "def particle_distribution():\n", 80 | " return\n", 81 | "\n", 82 | "def separation_analysis():\n", 83 | " return\n", 84 | "\n", 85 | "def save_results():\n", 86 | " return" 87 | ] 88 | }, 89 | { 90 | "cell_type": "markdown", 91 | "metadata": {}, 92 | "source": [ 93 | "## Uses" 94 | ] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": null, 99 | "metadata": {}, 100 | "outputs": [], 101 | "source": [ 102 | "import npia\n", 103 | "\n", 104 | "img = npia.load_image(image_file)\n", 105 | "threshold = npia.threshold(image_file)\n", 106 | "labels = npia.watershed(threshold)" 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": null, 112 | "metadata": {}, 113 | "outputs": [], 114 | "source": [ 115 | "class AFMImage:\n", 116 | " def __init__(self, image_file, scale_bar=None, ub=None, lb=None):\n", 117 | " self.image = cv2.imread(image_file)\n", 118 | " self.original_image = None\n", 119 | " self.scale_bar = scale_bar\n", 120 | " self.upper_bound = ub\n", 121 | " self.lower_bound = lb\n", 122 | " \n", 123 | " self.n_particles = None\n", 124 | " self.particle_distances = [] # (x, y, radius, nearest_neighbor_dist)\n", 125 | " return\n", 126 | " \n", 127 | " def load_image(self, image_file):\n", 128 | " return\n", 129 | " \n", 130 | " def watershed(self):\n", 131 | " cv2.watershed(self.image)" 132 | ] 133 | }, 134 | { 135 | "cell_type": "code", 136 | "execution_count": null, 137 | "metadata": {}, 138 | "outputs": [], 139 | "source": [ 140 | "img = npia.AFMImage(image_file, scale)\n", 141 | "img.prepare()\n", 142 | "img.threshold()\n", 143 | "num_particles = img.n_particles" 144 | ] 145 | } 146 | ], 147 | "metadata": { 148 | "kernelspec": { 149 | "display_name": "Python 3", 150 | "language": "python", 151 | "name": "python3" 152 | }, 153 | "language_info": { 154 | "codemirror_mode": { 155 | "name": "ipython", 156 | "version": 3 157 | }, 158 | "file_extension": ".py", 159 | "mimetype": "text/x-python", 160 | "name": "python", 161 | "nbconvert_exporter": "python", 162 | "pygments_lexer": "ipython3", 163 | "version": "3.6.5" 164 | } 165 | }, 166 | "nbformat": 4, 167 | "nbformat_minor": 2 168 | } 169 | -------------------------------------------------------------------------------- /npia.egg-info/.ipynb_checkpoints/PKG-INFO-checkpoint: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.2 2 | Name: npia 3 | Version: 0.0.1.dev1 4 | Summary: npia: nano-particle image processing 5 | Home-page: http://github.com/mirabala/Nano-Particle-Image-Analysis 6 | Author: Alex Mirabala 7 | Author-email: 8 | Maintainer: Alex Mirabal 9 | Maintainer-email: 10 | License: MIT 11 | Description: 12 | 13 | NPIA 14 | ======== 15 | NPIA is a python package that is used to analyze nanoparticle AFM images 16 | to determine particle sizes and pairwise distances. 17 | 18 | License 19 | ======= 20 | ``npia`` is licensed under the terms of the MIT license. See the file 21 | "LICENSE" for information on the history of this software, terms & conditions 22 | for usage, and a DISCLAIMER OF ALL WARRANTIES. 23 | 24 | All trademarks referenced herein are property of their respective holders. 25 | 26 | Copyright (c) 2018--, Alex Mirabal, Michigan State University 27 | Department of Chemical Engineering. 28 | 29 | Platform: OS Independent 30 | Classifier: Development Status :: 3 - Alpha 31 | Classifier: Environment :: Console 32 | Classifier: Intended Audience :: Science/Research 33 | Classifier: License :: OSI Approved :: MIT License 34 | Classifier: Operating System :: OS Independent 35 | Classifier: Programming Language :: Python 36 | Classifier: Topic :: Scientific/Engineering 37 | -------------------------------------------------------------------------------- /npia.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.2 2 | Name: npia 3 | Version: 0.0.1.dev1 4 | Summary: npia: nano-particle image processing 5 | Home-page: http://github.com/mirabala/Nano-Particle-Image-Analysis 6 | Author: Alex Mirabala 7 | Author-email: 8 | Maintainer: Alex Mirabal 9 | Maintainer-email: 10 | License: MIT 11 | Description: 12 | 13 | NPIA 14 | ======== 15 | NPIA is a python package that is used to analyze nanoparticle AFM images 16 | to determine particle sizes and pairwise distances. 17 | 18 | License 19 | ======= 20 | ``npia`` is licensed under the terms of the MIT license. See the file 21 | "LICENSE" for information on the history of this software, terms & conditions 22 | for usage, and a DISCLAIMER OF ALL WARRANTIES. 23 | 24 | All trademarks referenced herein are property of their respective holders. 25 | 26 | Copyright (c) 2018--, Alex Mirabal, Michigan State University 27 | Department of Chemical Engineering. 28 | 29 | Platform: OS Independent 30 | Classifier: Development Status :: 3 - Alpha 31 | Classifier: Environment :: Console 32 | Classifier: Intended Audience :: Science/Research 33 | Classifier: License :: OSI Approved :: MIT License 34 | Classifier: Operating System :: OS Independent 35 | Classifier: Programming Language :: Python 36 | Classifier: Topic :: Scientific/Engineering 37 | -------------------------------------------------------------------------------- /npia.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | setup.py 2 | npia/__init__.py 3 | npia/core.py 4 | npia/version.py 5 | npia.egg-info/PKG-INFO 6 | npia.egg-info/SOURCES.txt 7 | npia.egg-info/dependency_links.txt 8 | npia.egg-info/top_level.txt -------------------------------------------------------------------------------- /npia.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /npia.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | npia 2 | -------------------------------------------------------------------------------- /npia/.ipynb_checkpoints/core-checkpoint.py: -------------------------------------------------------------------------------- 1 | from .preparation import * 2 | from .processing import * 3 | from .analysis import * 4 | 5 | 6 | def load_and_prepare(): 7 | return 8 | 9 | 10 | def apply_threshold(): 11 | return 12 | 13 | 14 | def get_distances(): 15 | return 16 | 17 | 18 | def show_image(): 19 | return 20 | -------------------------------------------------------------------------------- /npia/.ipynb_checkpoints/preparation-checkpoint.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirabala/Nano-Particle-Image-Analysis/225194d17a315653cfd5c9e07d47b87729f00ab0/npia/.ipynb_checkpoints/preparation-checkpoint.py -------------------------------------------------------------------------------- /npia/.ipynb_checkpoints/processing-checkpoint.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirabala/Nano-Particle-Image-Analysis/225194d17a315653cfd5c9e07d47b87729f00ab0/npia/.ipynb_checkpoints/processing-checkpoint.py -------------------------------------------------------------------------------- /npia/.ipynb_checkpoints/version-checkpoint.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | from os.path import join as pjoin 3 | 4 | # Format expected by setup.py and doc/source/conf.py: string of form "X.Y.Z" 5 | _version_major = 0 6 | _version_minor = 0 7 | _version_micro = 1 # use '' for first of series, number for 1 and above 8 | _version_extra = 'dev1' 9 | # _version_extra = '' # Uncomment this for full releases 10 | 11 | # Construct full version string from these. 12 | _ver = [_version_major, _version_minor] 13 | if _version_micro: 14 | _ver.append(_version_micro) 15 | if _version_extra: 16 | _ver.append(_version_extra) 17 | 18 | __version__ = '.'.join(map(str, _ver)) 19 | 20 | CLASSIFIERS = ["Development Status :: 3 - Alpha", 21 | "Environment :: Console", 22 | "Intended Audience :: Science/Research", 23 | "License :: OSI Approved :: MIT License", 24 | "Operating System :: OS Independent", 25 | "Programming Language :: Python", 26 | "Topic :: Scientific/Engineering"] 27 | 28 | # Description should be a one-liner: 29 | description = "npia: nano-particle image processing" 30 | # Long description will go up on the pypi page 31 | long_description = """ 32 | 33 | NPIA 34 | ======== 35 | NPIA is a python package that is used to analyze nanoparticle AFM images 36 | to determine particle sizes and pairwise distances. 37 | 38 | License 39 | ======= 40 | ``npia`` is licensed under the terms of the MIT license. See the file 41 | "LICENSE" for information on the history of this software, terms & conditions 42 | for usage, and a DISCLAIMER OF ALL WARRANTIES. 43 | 44 | All trademarks referenced herein are property of their respective holders. 45 | 46 | Copyright (c) 2018--, Alex Mirabala, Michigan State University 47 | Department of Chemical Engineering. 48 | """ 49 | 50 | NAME = "npia" 51 | MAINTAINER = "Alex Mirabala" 52 | MAINTAINER_EMAIL = "" 53 | DESCRIPTION = description 54 | LONG_DESCRIPTION = long_description 55 | URL = "http://github.com/mirabala/Nano-Particle-Image-Analysis" 56 | DOWNLOAD_URL = "" 57 | LICENSE = "MIT" 58 | AUTHOR = "Alex Mirabala" 59 | AUTHOR_EMAIL = "" 60 | PLATFORMS = "OS Independent" 61 | MAJOR = _version_major 62 | MINOR = _version_minor 63 | MICRO = _version_micro 64 | VERSION = __version__ 65 | PACKAGE_DATA = {'npia': [pjoin('data', '*')]} 66 | REQUIRES = [] 67 | -------------------------------------------------------------------------------- /npia/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | from .version import __version__ # noqa 3 | from .core import * # noqa 4 | -------------------------------------------------------------------------------- /npia/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirabala/Nano-Particle-Image-Analysis/225194d17a315653cfd5c9e07d47b87729f00ab0/npia/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /npia/__pycache__/core.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirabala/Nano-Particle-Image-Analysis/225194d17a315653cfd5c9e07d47b87729f00ab0/npia/__pycache__/core.cpython-36.pyc -------------------------------------------------------------------------------- /npia/__pycache__/version.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirabala/Nano-Particle-Image-Analysis/225194d17a315653cfd5c9e07d47b87729f00ab0/npia/__pycache__/version.cpython-36.pyc -------------------------------------------------------------------------------- /npia/core.py: -------------------------------------------------------------------------------- 1 | import mahotas as mh 2 | import matplotlib.pyplot as plt 3 | import numpy as np 4 | # from numpy.linalg import norm 5 | import pandas as pd # noqa 6 | 7 | 8 | def get_distance(x1, y1, x2, y2): 9 | return np.sqrt((x2 - x1)**2 + (y2 - y1)**2) 10 | 11 | 12 | class Image: 13 | def __init__(self, image_file, scale_bar, ub=None, lb=None): 14 | self.image = mh.imread(image_file) 15 | self.original_image = None 16 | self.scale_bar = scale_bar 17 | self.upper_bound = ub 18 | self.lower_bound = lb 19 | 20 | self.labels = None 21 | self.pix_to_micron = None 22 | self.threshold = None 23 | 24 | self.com = None 25 | self.sg = None 26 | self.n_pix = None 27 | self.ipx = None 28 | self.ipy = None 29 | self.locg = None 30 | self.pix_to_micron = None 31 | self.n_particles = None 32 | self.particle_distances = [] # (x, y, radius, nearest_neighbor_dist) 33 | self.seeds = None 34 | self.n_seeds = None 35 | self.spots = None 36 | 37 | self.Ta = None 38 | self.areas = None 39 | self.dist = None 40 | self.xloc = [] 41 | self.p_dist = None 42 | self.da = None 43 | self.maxes = None 44 | self.T = None 45 | 46 | return 47 | 48 | # Dami 49 | 50 | def rgb2gray(self): 51 | """ 52 | Converts the image from rgb to gray scale using the format provided at 53 | bit.ly/python-rgb-to-gray 54 | 55 | """ 56 | return np.dot(self.image[..., :3], [0.299, 0.587, 0.114]) 57 | 58 | def prepare(self, ipx=2015, ipy=2015): 59 | """ 60 | Crops the image and calculates pixel to micron parameter for later use 61 | 62 | Parameters 63 | ---------- 64 | ipx : int 65 | describes the AFM exported image pixel count in the x dimension 66 | ipy : int 67 | describes the AFM exported image pixel count in the y dimension 68 | """ 69 | self.ipx = ipx 70 | self.ipy = ipy 71 | self.image = self.rgb2gray() 72 | scal = self.image[2030, 0:2000] 73 | self.image = self.image[2:self.ipy, 2:self.ipx] # Crop image 74 | self.original_image = self.image 75 | bar = np.count_nonzero(scal - 255) 76 | # pixel to um scale conversion 77 | self.pix_to_micron = self.scale_bar / bar 78 | return 79 | 80 | # Luke 81 | def apply_threshold(self, upper_threshold=255, lower_threshold=20): 82 | """ 83 | Sets threshold for removing background noise. 84 | 85 | Parameters 86 | ---------- 87 | threshold : int 88 | """ 89 | 90 | 91 | self.image[self.image > upper_threshold] = upper_threshold 92 | self.image[self.image < lower_threshold] = 0 93 | 94 | return 95 | 96 | def apply_otsu_threshold(self, s = 0): 97 | """ 98 | Sets threshold for removing background noise. 99 | 100 | Parameters 101 | ---------- 102 | threshold : int 103 | """ 104 | # if threshold: 105 | # self.threshold = threshold 106 | # else: 107 | # # otsu bi-modal histogram thresholding 108 | # # https://en.wikipedia.org/wiki/Otsu%27s_method 109 | self.threshold = mh.thresholding.otsu(self.image.astype(np.uint8),s) 110 | 111 | return 112 | 113 | 114 | def intensity_hist(self, bins=(255/10), x_lim=None, y_lim=None): 115 | """ 116 | histogram for pixel intensity to help manual thresholding in the removal of background data 117 | 118 | Parameters 119 | ---------- 120 | bins : int 121 | number of bins to show in histogram 122 | x_lim : int 123 | x limit of histogram for purpose of zooming in on a region 124 | y_lim : int 125 | y limit of histogram if desired different than largest bin count 126 | """ 127 | plt.hist(self.image.ravel(), bins=int(bins), fc='k', ec='k') 128 | plt.xlabel('Intensity') 129 | plt.ylabel('Pixel Count') 130 | 131 | if x_lim: 132 | plt.xlim(x_lim) 133 | else: 134 | pass 135 | 136 | if y_lim: 137 | plt.ylim(y_lim) 138 | else: 139 | pass 140 | 141 | plt.show() 142 | return 143 | 144 | def apply_gaussian_filter(self, sigma=5): 145 | """ 146 | Applys a gaussian filter to image to smooth edges. 147 | 148 | Parameters 149 | ---------- 150 | sigma : float or int 151 | Gaussian width 152 | """ 153 | 154 | self.image = mh.gaussian_filter(self.image, sigma).astype('uint8') 155 | return 156 | 157 | def label_particles(self, T = 0, f = 0.7): 158 | """ 159 | Labels particles by connected pixels 160 | 161 | Parameters 162 | ----------- 163 | T : int 164 | threshold to identify particles 165 | f : int 166 | modification of threshold value to better capture entire particle region 167 | """ 168 | #all pixels in "one" particle same value 169 | labeled,nr_objects = mh.label(self.image > f*T) 170 | plt.imshow(labeled,extent=[0,self.pix_to_micron*self.ipx,0,self.pix_to_micron*self.ipy]) 171 | plt.xlabel('Distance / $\mu$m') 172 | plt.ylabel('Distance / $\mu$m') 173 | plt.show() 174 | 175 | def get_com(self, scan_filter=(10, 10)): 176 | """ 177 | Calculates center of mass of particle using regional maxima calculated over the entire matrix 178 | 179 | Parameters 180 | ----------- 181 | scan_filter : int 182 | size of a weighted square region for regional maxima identification 183 | """ 184 | self.maxes = mh.regmax(self.image, Bc=np.ones(scan_filter)).astype(int) 185 | self.spots, n_spots = mh.label(self.maxes, Bc=np.ones(scan_filter)) 186 | com = mh.center_of_mass(self.image, self.spots) 187 | plt.imshow(self.spots) 188 | self.com = com 189 | return 190 | 191 | def watershed(self, Ta = 0): 192 | """ 193 | Identification of particles through inverted slope comparisons 194 | 195 | Parameters 196 | ----------- 197 | Ta : int 198 | Threshold value in which the particles will be identified by 199 | """ 200 | self.Ta=Ta 201 | dist = mh.distance(self.image > 0.05 * self.Ta) 202 | dist1 = dist 203 | dist = dist.max() - dist 204 | dist -= dist.min() # inverting color 205 | dist = dist / float(dist.ptp()) * 255 206 | dist = dist.astype(np.uint8) 207 | self.dist = mh.stretch(dist, 0, 255) 208 | self.labels, self.n_particles = mh.label(self.image > 0.7 * self.Ta) 209 | 210 | thr = np.median(dist) 211 | 212 | # not accurate to particles detected(?) 213 | # but matches dist graph well 214 | thresh = (dist < thr) 215 | areas = 0.9 * mh.cwatershed(dist, self.labels) 216 | self.areas = areas * thresh 217 | return 218 | 219 | # Alex 220 | def get_position(self, n_pix=9): 221 | """ 222 | Gets particle position and size from watershed analysis 223 | 224 | Parameters 225 | ---------- 226 | n_pix : float or int 227 | number of pixels in square array for peak labeling 228 | """ 229 | 230 | self.n_pix = n_pix 231 | Bc1 = np.ones((self.n_pix, self.n_pix)) 232 | self.seeds, self.n_seeds = mh.label(self.areas, Bc=Bc1) 233 | self.locg = mh.center_of_mass(self.original_image, self.seeds) 234 | self.locg = self.locg.astype(int) 235 | sg = mh.labeled.labeled_size(self.seeds) 236 | particle_radius_mean = np.sqrt(np.mean(sg[1:]) / np.pi) *\ 237 | self.pix_to_micron 238 | sg = np.sqrt(sg[1:] / np.pi) * self.pix_to_micron 239 | pr_med = np.median(sg) 240 | pr_std = np.sqrt(np.std(sg[1:]) / np.pi) * self.pix_to_micron 241 | self.sg = sg 242 | return pr_med, particle_radius_mean, pr_std, sg 243 | 244 | def get_distances(self,cutoff = 3): 245 | """ 246 | calculates minimum distance between one particle to all other particles for all particles 247 | 248 | Parameters 249 | ----------- 250 | cutoff : int 251 | value of normalized particle separation which the desired particles must be greater than 252 | """ 253 | # Initialize empty array of minimum 254 | # particle distance for each particle 255 | self.xloc=np.empty([1,2]) 256 | d=np.empty([len(self.locg[:,0])-1,len(self.locg[:,0])-1]) 257 | self.da=np.empty(len(self.locg[:,0])-1) 258 | a=0 259 | 260 | for s in range(0,len(self.locg[:,0])-1): 261 | 262 | for ss in range(0,len(self.locg[:,0])-1): 263 | 264 | # distance between particle S and every other particle 265 | d[s,ss]=np.linalg.norm(self.locg[s,:]-self.locg[ss,:]) 266 | 267 | # distance to closest particle 268 | self.da[s]=np.amin(d[s,np.nonzero(d[s,:])]) 269 | 270 | if self.da[s]*self.pix_to_micron/self.sg[s]>cutoff: #thresholded value 271 | self.xloc[a,:]=self.locg[s,:] 272 | self.xloc=np.pad(self.xloc,[(0,1),(0,0)],'constant') 273 | a=a+1 274 | 275 | def Particle_Separation_Analysis(self, cutoff=3): 276 | """Applys a gaussian filter to image to smooth edges.""" 277 | Bc_ = np.ones((self.n_pix, self.n_pix)) 278 | rmaxg = mh.regmax(self.image, Bc_) 279 | xsg, ysg = rmaxg.shape 280 | x = xsg * self.pix_to_micron # x image length | um 281 | y = ysg * self.pix_to_micron # y image length | um 282 | rho = self.n_seeds / (y * x) # Particle Density 283 | 284 | # l, w = np.shape(xloc) 285 | # mask = np.ones((l, w), dtype=bool) 286 | # mask[0:int((l + 1) / 2), 2:w] = False 287 | # xloc = (np.reshape(xloc[mask, ...], (-1, 2))) 288 | # xloc = xloc[int(l / 2), :] 289 | # da_mean = np.mean(da) * self.pix_to_micron 290 | # da_std = np.std(da) * self.pix_to_micron 291 | # plt.matshow(d) 292 | # plt.jet() 293 | return rho # , da_mean, da_std 294 | 295 | def new_particle_distances(self, cutoff=3): 296 | 297 | 298 | for i in range(len(self.com) - 1): 299 | self.p_dist = get_distance(self.com[i, 0], self.com[i, 1], 300 | self.com[np.arange(len(self.com)) != i, 0], 301 | self.com[np.arange(len(self.com)) != i, 1]) 302 | if np.all(self.p_dist * self.pix_to_micron > cutoff): 303 | self.xloc.append((self.com[i, 0], self.com[i, 1])) 304 | return 305 | 306 | def show_image(self, color_map, x_label='Distance / $\mu$m', 307 | y_label='Distance / $\mu$m', overlay_com=None, 308 | fig_size=(5, 5),filename= None): 309 | """Display image input as has been updated 310 | 311 | Parameters 312 | ---------- 313 | color_map : matplotlib colormap 314 | Matplotlib colormap designation 315 | x_label : string 316 | x label of histogram 317 | y_label : string 318 | y label of histogram""" 319 | plt.figure(figsize=(fig_size)) 320 | plt.imshow(self.image, cmap=color_map, 321 | extent=[0, self.pix_to_micron * self.ipx, 322 | 0, self.pix_to_micron * self.ipy]) 323 | if overlay_com is not None: 324 | for x, y in overlay_com: 325 | plt.plot(y*self.pix_to_micron, (self.ipy-x)*self.pix_to_micron, 'o', markerfacecolor='none', 326 | markeredgecolor='r', markeredgewidth=2, ms=10) 327 | else: 328 | pass 329 | plt.xlabel(x_label) 330 | plt.ylabel(y_label) 331 | if filename is not None: 332 | plt.savefig(filename) 333 | plt.show() 334 | return 335 | -------------------------------------------------------------------------------- /npia/tests/.ipynb_checkpoints/__init__-checkpoint.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirabala/Nano-Particle-Image-Analysis/225194d17a315653cfd5c9e07d47b87729f00ab0/npia/tests/.ipynb_checkpoints/__init__-checkpoint.py -------------------------------------------------------------------------------- /npia/tests/.ipynb_checkpoints/test_core-checkpoint.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirabala/Nano-Particle-Image-Analysis/225194d17a315653cfd5c9e07d47b87729f00ab0/npia/tests/.ipynb_checkpoints/test_core-checkpoint.py -------------------------------------------------------------------------------- /npia/tests/PtnanoCenk0.5et_171218_AM.0_00002_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirabala/Nano-Particle-Image-Analysis/225194d17a315653cfd5c9e07d47b87729f00ab0/npia/tests/PtnanoCenk0.5et_171218_AM.0_00002_1.png -------------------------------------------------------------------------------- /npia/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirabala/Nano-Particle-Image-Analysis/225194d17a315653cfd5c9e07d47b87729f00ab0/npia/tests/__init__.py -------------------------------------------------------------------------------- /npia/tests/test_core.py: -------------------------------------------------------------------------------- 1 | from os.path import abspath, dirname, join 2 | 3 | import mahotas as mh # noqa 4 | import matplotlib.pyplot as plt # noqa 5 | import numpy as np 6 | 7 | from ..core import Image 8 | 9 | 10 | def test_rgb2gray(): 11 | default = abspath(join(dirname(__file__), 12 | 'PtnanoCenk0.5et_171218_AM.0_00002_1.png')) 13 | img2 = Image(default, 3) 14 | img2.image = np.array([[[255, 231, 97], [45, 23, 18]]]) 15 | test_output = img2.rgb2gray() 16 | assert np.isclose(test_output[0, 0], 222.9), 'bad grayscale' 17 | assert np.isclose(test_output[0, 1], 29.008) 18 | return 19 | 20 | 21 | def test_apply_threshold(): 22 | default = abspath(join(dirname(__file__), 23 | 'PtnanoCenk0.5et_171218_AM.0_00002_1.png')) 24 | img2 = Image(default, 3) 25 | img2.image = np.array([[[300, 231, 97], [45, 23, 18]]]) 26 | img2.apply_threshold() 27 | test_output2 = img2.image 28 | assert np.isclose(test_output2[0, 0, 0], 255), 'bad thresholding' 29 | assert np.isclose(test_output2[0, 1, 2], 0) 30 | return 31 | -------------------------------------------------------------------------------- /npia/untitled: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirabala/Nano-Particle-Image-Analysis/225194d17a315653cfd5c9e07d47b87729f00ab0/npia/untitled -------------------------------------------------------------------------------- /npia/version.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | from os.path import join as pjoin 3 | 4 | # Format expected by setup.py and doc/source/conf.py: string of form "X.Y.Z" 5 | _version_major = 0 6 | _version_minor = 0 7 | _version_micro = 1 # use '' for first of series, number for 1 and above 8 | _version_extra = 'dev1' 9 | # _version_extra = '' # Uncomment this for full releases 10 | 11 | # Construct full version string from these. 12 | _ver = [_version_major, _version_minor] 13 | if _version_micro: 14 | _ver.append(_version_micro) 15 | if _version_extra: 16 | _ver.append(_version_extra) 17 | 18 | __version__ = '.'.join(map(str, _ver)) 19 | 20 | CLASSIFIERS = ["Development Status :: 3 - Alpha", 21 | "Environment :: Console", 22 | "Intended Audience :: Science/Research", 23 | "License :: OSI Approved :: MIT License", 24 | "Operating System :: OS Independent", 25 | "Programming Language :: Python", 26 | "Topic :: Scientific/Engineering"] 27 | 28 | # Description should be a one-liner: 29 | description = "npia: nano-particle image processing" 30 | # Long description will go up on the pypi page 31 | long_description = """ 32 | 33 | NPIA 34 | ======== 35 | NPIA is a python package that is used to analyze nanoparticle AFM images 36 | to determine particle sizes and pairwise distances. 37 | 38 | License 39 | ======= 40 | ``npia`` is licensed under the terms of the MIT license. See the file 41 | "LICENSE" for information on the history of this software, terms & conditions 42 | for usage, and a DISCLAIMER OF ALL WARRANTIES. 43 | 44 | All trademarks referenced herein are property of their respective holders. 45 | 46 | Copyright (c) 2018--, Alex Mirabal, Michigan State University 47 | Department of Chemical Engineering. 48 | """ 49 | 50 | NAME = "npia" 51 | MAINTAINER = "Alex Mirabal" 52 | MAINTAINER_EMAIL = "" 53 | DESCRIPTION = description 54 | LONG_DESCRIPTION = long_description 55 | URL = "http://github.com/mirabala/Nano-Particle-Image-Analysis" 56 | DOWNLOAD_URL = "" 57 | LICENSE = "MIT" 58 | AUTHOR = "Alex Mirabala" 59 | AUTHOR_EMAIL = "" 60 | PLATFORMS = "OS Independent" 61 | MAJOR = _version_major 62 | MINOR = _version_minor 63 | MICRO = _version_micro 64 | VERSION = __version__ 65 | PACKAGE_DATA = {'npia': [pjoin('data', '*')]} 66 | REQUIRES = [] 67 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # NPIA: Nano-Particle Image Analysis 2 | ``NPIA`` is a python package that is used to process AFM images of nanoparticles and locates isolated particles based on a user-defined cutoff. 3 | 4 | [![Build Status](https://travis-ci.org/mirabala/Nano-Particle-Image-Analysis.svg?branch=master)](https://travis-ci.org/mirabala/Nano-Particle-Image-Analysis) 5 | 6 | [![Coverage Status](https://coveralls.io/repos/github/mirabala/Nano-Particle-Image-Analysis/badge.svg?branch=master)](https://coveralls.io/github/mirabala/Nano-Particle-Image-Analysis?branch=master) 7 | 8 | This repository contains a python package `npia` which can be used to identify nanoparticles in AFM images and determine their size and spacing. 9 | 10 | The dependencies are as follows: 11 | * numpy 12 | * mahotas - this is the core image analysis tool 13 | * matplotlib 14 | * pandas 15 | * pillow 16 | 17 | An example notebook can be found in the `notebooks` directory. 18 | 19 | Have fun! 20 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | mahotas 3 | matplotlib 4 | pandas 5 | pillow 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import setup, find_packages 3 | PACKAGES = find_packages() 4 | 5 | # Get version and release info, which is all stored in shablona/version.py 6 | ver_file = os.path.join('npia', 'version.py') 7 | with open(ver_file) as f: 8 | exec(f.read()) 9 | 10 | opts = dict(name=NAME, 11 | maintainer=MAINTAINER, 12 | maintainer_email=MAINTAINER_EMAIL, 13 | description=DESCRIPTION, 14 | long_description=LONG_DESCRIPTION, 15 | url=URL, 16 | download_url=DOWNLOAD_URL, 17 | license=LICENSE, 18 | classifiers=CLASSIFIERS, 19 | author=AUTHOR, 20 | author_email=AUTHOR_EMAIL, 21 | platforms=PLATFORMS, 22 | version=VERSION, 23 | packages=PACKAGES, 24 | package_data=PACKAGE_DATA, 25 | install_requires=REQUIRES, 26 | # requires=REQUIRES 27 | ) 28 | 29 | 30 | if __name__ == '__main__': 31 | setup(**opts) 32 | -------------------------------------------------------------------------------- /watershed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirabala/Nano-Particle-Image-Analysis/225194d17a315653cfd5c9e07d47b87729f00ab0/watershed.png --------------------------------------------------------------------------------