Skip to content

Commit 0693df7

Browse files
authored
Rebasing from upstream (#7)
* Create initial documentation * Added tests for functions in Particle Analysis Added testing functions for the following functions: -ParticleAnalysis() -store_image() -store_maps() -store_spectrum() -get_composition() * Added files for travis Added travis yml file and requirements file for travis builds. * Added test for particle list loading * Update requirements.txt Changed hyperspy dependency. * Update requirements.txt Updated PyQt5 requirements * Update requirements.txt * Syntax corrections Corrected import syntax * Revert "Syntax corrections" This reverts commit 600e2bf. * Updated import syntax * Update .travis.yml Added installation in virtual env * Changed path to parameters file * Updated the example notebook * Improve Find Zone-axis procedure and bug fixes Update find_zoneaxis and particleanalysis in order to more reliably find zone axes from noisy data. Bug fixed in reading old segmentation parameters. * Add kernel size for local thresholding Added option to change the kernel size for local thresholding (was previously set to default 21 pixels). * Changed max limits in SegUI Changed the max values for the spin boxes in SegUI. They should now scale with the image size rather than being fixed to 99. * Added Local Otsu Added Local Otsu to thresholding options. * Added (Local + Global) Otsu Threshold Added Threshold Option that combines the result of the Local and Global Otsu Thresholds. * Fixed uint8 bug Fixed bug that would incorrectly scale integer images. Now converts to float before scaling. * Update find_zoneaxis.py * Updated requirements for uploading to PyPI * Update README.md * Update setup.py Include check for PyQT5 import to avoid clash with pyqt package in conda. * Update setup.py * Updates for building a wheel Updated file requirements in setup.py nad MANIFEST.in for correctly building. * Update setup.py Bug fix * Update setup.py * Add radial profile Added function to plot a radial profile of signals from a particle. Also added the option of defining the number of bins in the histogram plotters. * Create requirements.txt * Update to radial profile plotting * Fix list issue and add radius marker. - Finally fixed the issue to do with adding particles to previously created particle lists. - Added a radius marker to the radial profiles. * Update Particle Analysis to include zone axis determination * Set theme jekyll-theme-architect * Added tests for functions in Particle Analysis Added testing functions for the following functions: -ParticleAnalysis() -store_image() -store_maps() -store_spectrum() -get_composition() * Added files for travis Added travis yml file and requirements file for travis builds. * Added test for particle list loading * Update requirements.txt Changed hyperspy dependency. * Update requirements.txt Updated PyQt5 requirements * Update requirements.txt * Syntax corrections Corrected import syntax * Revert "Syntax corrections" This reverts commit 600e2bf. * Updated import syntax * Update .travis.yml Added installation in virtual env * Changed path to parameters file * Updated the example notebook * Improve Find Zone-axis procedure and bug fixes Update find_zoneaxis and particleanalysis in order to more reliably find zone axes from noisy data. Bug fixed in reading old segmentation parameters. * Add kernel size for local thresholding Added option to change the kernel size for local thresholding (was previously set to default 21 pixels). * Changed max limits in SegUI Changed the max values for the spin boxes in SegUI. They should now scale with the image size rather than being fixed to 99. * Added Local Otsu Added Local Otsu to thresholding options. * Added (Local + Global) Otsu Threshold Added Threshold Option that combines the result of the Local and Global Otsu Thresholds. * Fixed uint8 bug Fixed bug that would incorrectly scale integer images. Now converts to float before scaling. * Update find_zoneaxis.py * Updated requirements for uploading to PyPI * Update README.md * Update setup.py Include check for PyQT5 import to avoid clash with pyqt package in conda. * Update setup.py * Updates for building a wheel Updated file requirements in setup.py nad MANIFEST.in for correctly building. * Update setup.py Bug fix * Update setup.py * Add radial profile Added function to plot a radial profile of signals from a particle. Also added the option of defining the number of bins in the histogram plotters. * Create requirements.txt * Update to radial profile plotting * Fix list issue and add radius marker. - Finally fixed the issue to do with adding particles to previously created particle lists. - Added a radius marker to the radial profiles. * Update Particle Analysis to include zone axis determination * Set theme jekyll-theme-architect * Add initial documentation source files * Update .travis.yml * Update .travis.yml * Update .travis.yml * Update .travis.yml * Update .travis.yml * Update README.md * Revamped particle properties and added new properties Revamped particle properties so that properties are held as a dictionary rather than as separate class members. Added eccentricity, major and minor axes and equivalent circular diameter to properties. Added new plot function to particle list that plots a histogram of any particle property. * Minor corrections Updated diameter equation and modified property names. * Fix tests * Update .travis.yml * Update doc strings * Documentation Update Updated documentation to include page on segmentation. * Add particle intensity Added total particle intensity measure. * Add Particle Analysis Documentation
1 parent 7e74bc8 commit 0693df7

55 files changed

Lines changed: 15852 additions & 29 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.travis.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,20 @@ python:
66
install:
77
- pip install -r requirements.txt
88
- pip install -e .
9+
- pip install sphinx
10+
- pip install sphinx_rtd_theme
911

1012
script:
1113
- pytest
14+
- cd docs
15+
- make html
16+
- touch _build/html/.nojekyll
17+
18+
deploy:
19+
provider: pages
20+
skip_cleanup: true
21+
github_token: $GITHUB_TOKEN # Set in travis-ci.org dashboard, marked secure
22+
keep-history: true
23+
on:
24+
branch: master
25+
local_dir: docs/_build/html/

ParticleSpy/ParticleAnalysis.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ def ParticleAnalysis(acquisition,parameters,particles=None,mask=np.zeros((1))):
2525
acquisition: Hyperpsy signal object or list of hyperspy signal objects.
2626
Hyperpsy signal object containing a nanoparticle image or a list of signal
2727
objects that contains an image at position 0 and other datasets following.
28-
process_param: Dictionary of parameters
28+
parameters: Dictionary of parameters
2929
The parameters can be input manually in to a dictionary or can be generated
3030
using param_generator().
31-
particle_list: List
31+
particles: list
3232
List of already analysed particles that the output can be appended
3333
to.
3434
mask: Numpy array
@@ -37,7 +37,7 @@ def ParticleAnalysis(acquisition,parameters,particles=None,mask=np.zeros((1))):
3737
3838
Returns
3939
-------
40-
list: List of Particle objects.
40+
Particle_list object
4141
"""
4242

4343
if particles==None:
@@ -80,13 +80,28 @@ def ParticleAnalysis(acquisition,parameters,particles=None,mask=np.zeros((1))):
8080
area_units = image.axes_manager[0].units+"^2"
8181
p.set_area(cal_area,area_units)
8282

83+
#Set diam measures
84+
cal_circdiam = 2*(cal_area**0.5)/np.pi
85+
diam_units = image.axes_manager[0].units
86+
p.set_circdiam(cal_circdiam,diam_units)
87+
88+
cal_axes_lengths = (region.major_axis_length*image.axes_manager[0].scale,region.minor_axis_length*image.axes_manager[0].scale)
89+
#Note: the above only works for square pixels
90+
p.set_axes_lengths(cal_axes_lengths,diam_units)
91+
8392
#Set shape measures
8493
peri = image.axes_manager[0].scale*perimeter(maskp,neighbourhood=4)
85-
circularity = 4*3.14159265*p.area/(peri**2)
94+
circularity = 4*3.14159265*p.properties['area']['value']/(peri**2)
8695
p.set_circularity(circularity)
96+
eccentricity = region.eccentricity
97+
p.set_eccentricity(eccentricity)
98+
99+
#Set total image intensity
100+
intensity = (image*mask).sum()
101+
p.set_intensity(intensity)
87102

88103
#Set zoneaxis
89-
im_smooth = filters.gaussian(p_im,1)
104+
im_smooth = filters.gaussian(np.uint16(p_im),1)
90105
im_zone = np.zeros_like(im_smooth)
91106
im_zone[im_smooth>0] = im_smooth[im_smooth>0] - im_smooth[im_smooth>0].mean()
92107
im_zone[im_zone<0] = 0

ParticleSpy/SegUI.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,9 @@ def main(haadf):
262262
return(ex)
263263

264264
def SegUI(image):
265+
"""
266+
Function to launch the Segmentation User Interface.
267+
"""
265268
app = QApplication(sys.argv)
266269
app.aboutToQuit.connect(app.deleteLater)
267270

ParticleSpy/find_zoneaxis.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def find_zoneaxis(im):
2424
2525
Returns
2626
-------
27-
string: Zone axis of material in image (eg. '011')
27+
str : Zone axis of material in image (eg. '011')
2828
"""
2929

3030
#Get FFT of particle image

ParticleSpy/ptcl_class.py

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,54 @@
99
from ParticleSpy.particle_save import save_plist
1010

1111
class Particle(object):
12-
"""A segmented particle object."""
12+
"""A segmented particle object.
13+
14+
Attributes
15+
----------
16+
properties : dict
17+
Dictionary of particle properties created by the ParticleAnalysis() function.
18+
origin : str
19+
Origin of particle data, e.g. filename or acquisition number.
20+
zone : str
21+
Zone axis of particle.
22+
mask : array
23+
Boolean array corresponding to the particle pixels on the original image.
24+
image : Hyperspy signal object
25+
Image of particle.
26+
maps : dict
27+
Dictionary containing elemental maps of the particle.
28+
spectrum : Hyperspy signal object
29+
Spectrum obtained from the particle.
30+
composition : dict
31+
Dictionary of composition values for the particle.
32+
33+
"""
34+
35+
def __init__(self):
36+
self.properties = {}
1337

1438
def set_origin(self, origin):
1539
"""A container for the origin of data (filename, acquisition number etc.)"""
1640
self.origin = origin
1741

1842
def set_area(self, area, units):
19-
self.area = area
20-
self.area_units = units
43+
self.properties['area'] = {'value':area,'units':units}
44+
45+
def set_circdiam(self, circdiam, units):
46+
self.properties['equivalent circular diameter'] = {'value':circdiam,'units':units}
47+
48+
def set_axes_lengths(self,axeslengths,units):
49+
self.properties['major axis length'] = {'value':axeslengths[0],'units':units}
50+
self.properties['minor axis length'] = {'value':axeslengths[1],'units':units}
2151

2252
def set_circularity(self, circularity):
23-
self.circularity = circularity
53+
self.properties['circularity'] = {'value':circularity,'units':None}
54+
55+
def set_eccentricity(self,eccentricity):
56+
self.properties['eccentricity'] = {'value':eccentricity,'units':None}
57+
58+
def set_intensity(self,intensity):
59+
self.properties['intensity'] = {'value':intensity,'units':None}
2460

2561
def set_zone(self, zone):
2662
self.zone = zone
@@ -64,10 +100,10 @@ def plot_area(self,bins=20):
64100
areas = []
65101

66102
for p in self.list:
67-
areas.append(p.area)
103+
areas.append(p.properties['area']['value'])
68104

69105
plt.hist(areas,bins=bins)
70-
plt.xlabel("Area ("+self.list[0].area_units+")")
106+
plt.xlabel("Area ("+self.list[0].properties['area']['units']+")")
71107
plt.ylabel("No. of particles")
72108

73109
def plot_circularity(self,bins=20):
@@ -78,8 +114,33 @@ def plot_circularity(self,bins=20):
78114
circularities = []
79115

80116
for p in self.list:
81-
circularities.append(p.circularity)
117+
circularities.append(p.properties['circularity']['value'])
82118

83119
plt.hist(circularities,bins=bins)
84120
plt.xlabel("Circularity")
121+
plt.ylabel("No. of particles")
122+
123+
def plot(self,prop='area',bins=20):
124+
"""
125+
Displays a histogram of the chosen particle property.
126+
127+
Parameters
128+
----------
129+
prop : str
130+
The name of the property to plot as a histogram.
131+
bins : int
132+
The number of bins in the histogram.
133+
134+
"""
135+
136+
property_list = []
137+
138+
for p in self.list:
139+
property_list.append(p.properties[prop]['value'])
140+
141+
plt.hist(property_list,bins=bins)
142+
if self.list[0].properties[prop]['units'] == None:
143+
plt.xlabel(prop.capitalize())
144+
else:
145+
plt.xlabel(prop.capitalize()+" ("+self.list[0].properties[prop]['units']+")")
85146
plt.ylabel("No. of particles")

ParticleSpy/radial_profile.py

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,22 @@
77
import numpy as np
88
import matplotlib.pyplot as plt
99

10-
def radial_profile(particle,signals,plot=True, mark_radius=False, radius=1):
10+
def radial_profile(particle,signals,plot=True, mark_radius=False):
11+
"""
12+
Function to calculate and plot a radial profile of a signal from an individual
13+
particle.
14+
15+
Parameters
16+
----------
17+
particle : ParticleSpy particle object
18+
The particle object.
19+
signals : list
20+
List of signals to plot, either 'Image' or element name, e.g. 'Ag'.
21+
plot : bool
22+
True if plotting, False otherwise.
23+
mark_radius : bool
24+
If true, mark the particle radius on the profile plot.
25+
"""
1126

1227
dist_count_dic = {}
1328
for sig in signals:
@@ -20,7 +35,7 @@ def radial_profile(particle,signals,plot=True, mark_radius=False, radius=1):
2035
units = particle.image.axes_manager[0].units
2136

2237
if plot==True:
23-
plot_profile(dist_count_dic,scale,units, mark_radius=mark_radius, radius=radius)
38+
plot_profile(dist_count_dic,scale,units, mark_radius=mark_radius, radius=particle.properties['equivalent circular diameter']['value']/2)
2439
return(dist_count_dic)
2540

2641
'''def sum_profiles(profiles):
@@ -85,14 +100,23 @@ def getkey(item):
85100

86101
return distance_unique, count_unique
87102

88-
def plot_profile(dist_count_dic, scale, units, mark_radius=False, radius=1, save=False, dir_save=None):
89-
'''
90-
DisNorm: check if the distance from particle centre
91-
is normalised by the particle size
92-
'''
103+
def plot_profile(dist_count_dic, scale, units, mark_radius=False, radius=1.0, save=False, dir_save=None):
104+
"""
105+
Function to plot a radial profile of particle signals.
93106
94-
'''if DisNorm == True:
95-
plt.xlim([0,100])'''
107+
Parameters
108+
----------
109+
dist_count_dic : dict
110+
Dictionary containing the distances and counts of the profile.
111+
scale : float
112+
units : str
113+
mark_radius : bool
114+
If true, mark the particle radius on the profile plot.
115+
radius : float
116+
save : bool
117+
dir_save : str
118+
Default : None
119+
"""
96120

97121
plt.xlabel('Distance from particle centre ('+units+')')
98122
plt.ylabel('Normalised intensity (counts)')

ParticleSpy/tests/test_particle_analysis.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,9 @@ def test_particleanalysis():
8585

8686
p = p_list.list[0]
8787

88-
nptest.assert_almost_equal(p.area,20069.0)
89-
assert p.area_units == 'nm^2'
90-
nptest.assert_almost_equal(p.circularity,0.9095832157785668)
88+
nptest.assert_almost_equal(p.properties['area']['value'],20069.0)
89+
assert p.properties['area']['units'] == 'nm^2'
90+
nptest.assert_almost_equal(p.properties['circularity']['value'],0.9095832157785668)
9191
assert p.zone == None
9292
nptest.assert_allclose(p.mask,mask)
9393
nptest.assert_allclose(p.image.data,image.data[16:184,16:184])

ParticleSpy/tests/test_particle_io.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ def test_load_particles():
1212
image = gen_test.generate_test_image(hspy=True)
1313
mask = gen_test.generate_test_image(hspy=False)
1414

15-
nptest.assert_almost_equal(p.area,20069.0)
16-
assert p.area_units == 'nm^2'
17-
nptest.assert_almost_equal(p.circularity,0.9095832157785668)
15+
nptest.assert_almost_equal(p.properties['area']['value'],20069.0)
16+
assert p.properties['area']['units'] == 'nm^2'
17+
nptest.assert_almost_equal(p.properties['circularity']['value'],0.9095832157785668)
1818
nptest.assert_allclose(p.mask,mask)
1919
nptest.assert_allclose(p.image.data,image.data[16:184,16:184])

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# ParticleSpy
22
Package for analysing particles in electron microscopy data sets.
33

4-
The package can be installed from PyPI using pip install ParticleSpy.
4+
The package can be installed from PyPI using pip install ParticleSpy.
5+
6+
Documentation can be found at https://epsic-dls.github.io/ParticleSpy/index.html.

docs/Makefile

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Minimal makefile for Sphinx documentation
2+
#
3+
4+
# You can set these variables from the command line.
5+
SPHINXOPTS =
6+
SPHINXBUILD = sphinx-build
7+
SOURCEDIR = .
8+
BUILDDIR = _build
9+
10+
# Put it first so that "make" without argument is like "make help".
11+
help:
12+
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
13+
14+
.PHONY: help Makefile
15+
16+
# Catch-all target: route all unknown targets to Sphinx using the new
17+
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
18+
%: Makefile
19+
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

0 commit comments

Comments
 (0)