Skip to content

Commit 236f0dc

Browse files
authored
Merge pull request #11 from TomSlater/New-shape-measures
Changing how particle properties work and adding new properties / measures
2 parents 6d0bbf2 + 86ae57a commit 236f0dc

12 files changed

Lines changed: 254 additions & 34 deletions

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])

docs/_static/segui.png

404 KB
Loading

docs/getting_started.rst

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
.. _getting_started:
22

3-
43
***************
54
Getting started
65
***************
76

8-
.. _installing-pspy:
9-
107
Installing ParticleSpy
118
======================
129

@@ -32,7 +29,7 @@ you can use the following command to install the package.
3229

3330
.. code-block:: bash
3431
35-
pip install -e git+https://github.com/ePSIC-DLS/ParticleSpy
32+
$ pip install -e git+https://github.com/ePSIC-DLS/ParticleSpy
3633
3734
Using ParticleSpy
3835
=================

docs/index.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,15 @@ ParticleSpy is a python package which eases segmentation and analysis of nanopar
1010
It extends the functionality of the `Hyperspy <http://hyperspy.org/>`_ package in order to easily segment and analyse regions of Hyperspy signal objects.
1111

1212
.. toctree::
13-
:maxdepth: 2
13+
:maxdepth: -1
1414
:caption: Contents:
1515

1616
getting_started.rst
17+
segmentation.rst
18+
particle_analysis.rst
1719
source/modules.rst
1820

1921

20-
2122
Indices and tables
2223
==================
2324

0 commit comments

Comments
 (0)