forked from CellProfiler/CellProfiler-plugins
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmeasureradialentropy.py
231 lines (177 loc) · 9.19 KB
/
measureradialentropy.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
'''
<b>MeasureRadialEntropy</b> measures the variability of an image's
intensity inside a certain object
<hr>
<p>MeasureRadialEntropy divides an object into pie-shaped wedges, emanating from the centroid of the object, and
measures either the mean, median, or integrated intensity of each. Once the intensity
of each wedge has been calculated, the entropy of the bin measurements is calculated. It is not guaranteed that every
slice has an intensity value if the centroid of an object lies outside of the object area. In these cases a NAN will be reported.</p>
'''
import numpy
import scipy.stats
import skimage.measure
import cellprofiler.module as cpm
import cellprofiler.measurement as cpmeas
import cellprofiler.setting as cps
ENTROPY = "Entropy"
class MeasurementTemplate(cpm.Module):
module_name = "MeasureRadialEntropy"
category = "Measurement"
variable_revision_number = 1
def create_settings(self):
self.input_object_name = cps.ObjectNameSubscriber(
"Select objects to measure", cps.NONE,
doc="""Select the objects whose radial entropy you want to measure.""")
self.input_image_name = cps.ImageNameSubscriber(
"Select an image to measure", cps.NONE, doc="""Select the
grayscale image you want to measure the entropy of.""" )
self.bin_number=cps.Integer(
"Input number of bins", 6, minval=3, maxval=60,
doc="""Number of radial bins to divide your object into. The minimum number
of bins allowed is 3, the maximum number is 60.""")
self.intensity_measurement=cps.Choice(
"Which intensity measurement should be used?", ['Mean','Median','Integrated'], value='Mean',doc="""
Whether each wedge's mean, median, or integrated intensity
should be used to calculate the entropy.""" )
def settings(self):
return [self.input_image_name, self.input_object_name,
self.intensity_measurement, self.bin_number]
def run(self, workspace):
measurements = workspace.measurements
statistics = [["Entropy"]]
workspace.display_data.statistics = statistics
input_image_name = self.input_image_name.value
input_object_name = self.input_object_name.value
metric = self.intensity_measurement.value
bins = self.bin_number.value
image_set = workspace.image_set
input_image = image_set.get_image(input_image_name,
must_be_grayscale=True)
pixels = input_image.pixel_data
object_set = workspace.object_set
objects = object_set.get_objects(input_object_name)
labels = objects.segmented
indexes = objects.indices
my_props = skimage.measure.regionprops(labels)
centers = numpy.asarray([props.centroid for props in my_props])
feature = self.get_measurement_name(input_image_name,metric,bins)
#Do the actual calculation
entropy,slicemeasurements=self.slice_and_measure_intensity(pixels,labels,indexes,centers,metric,bins)
#Add the measurement back into the workspace
measurements.add_measurement(input_object_name,feature,entropy)
for eachbin in range(bins):
feature_bin = self.get_measurement_name_bins(input_image_name,metric,bins,eachbin+1)
measurements.add_measurement(input_object_name,feature_bin,slicemeasurements[:,eachbin])
emean = numpy.mean(entropy)
statistics.append([feature, emean])
#add statistics at some point
################################
#
# DISPLAY
#
def display(self, workspace, figure=None):
statistics = workspace.display_data.statistics
if figure is None:
figure = workspace.create_or_find_figure(subplots=(1, 1,))
else:
figure.set_subplots((1, 1))
figure.subplot_table(0, 0, statistics)
def slice_and_measure_intensity(self, pixels, labels, indexes, centers, metric, nbins):
'''For each object, iterate over the pixels that make up the object, assign them to a bin,
then call calculate_entropy and return it to run. Needs an update to numpy vector operations'''
entropylist=[]
slicemeasurementlist=[]
for eachindex in range(len(indexes)):
objects = numpy.zeros_like(pixels)
objects[objects==0] = -1
objects[labels==indexes[eachindex]]= pixels[labels==indexes[eachindex]]
pixeldict={}
objectiter=numpy.nditer(objects, flags=['multi_index'])
while not objectiter.finished:
if objectiter[0] != -1:
i1,i2=objectiter.multi_index
#Normalize the x,y coordinates to zero
center_y,center_x = centers[eachindex]
#Do the actual bin calculation
sliceno = int(numpy.ceil((numpy.pi + numpy.arctan2(i1 - center_y, i2 - center_x)) * (nbins / (2 * numpy.pi))))
if sliceno not in pixeldict.keys():
pixeldict[sliceno]=[objects[i1,i2]]
else:
pixeldict[sliceno] += [objects[i1, i2]]
objectiter.iternext()
# in the case that the object will not have pixels in a given slice, a value must still be given
for sliceno in range(1,nbins+1):
if sliceno not in pixeldict.keys():
pixeldict[sliceno]=[numpy.nan]
entropy,slicemeasurements=self.calculate_entropy(pixeldict,metric)
entropylist.append(entropy)
slicemeasurementlist.append(slicemeasurements)
entropyarray=numpy.array(entropylist)
slicemeasurementarray=numpy.array(slicemeasurementlist)
return entropyarray,slicemeasurementarray
def calculate_entropy(self,pixeldict,metric):
'''Calculates either the mean, median, or integrated intensity
of each bin as per the user's request then calculates the entropy'''
slicemeasurements=[]
for eachslice in pixeldict.keys():
if metric=='Mean':
slicemeasurements.append(numpy.mean(pixeldict[eachslice]))
elif metric=='Median':
slicemeasurements.append(numpy.median(pixeldict[eachslice]))
else:
slicemeasurements.append(numpy.sum(pixeldict[eachslice]))
slicemeasurements=numpy.array(slicemeasurements, dtype=float)
#Calculate entropy, and let scipy handle the normalization for you
# ignore the nan values
entropy=scipy.stats.entropy(slicemeasurements[~numpy.isnan(slicemeasurements)])
return entropy, slicemeasurements
def get_feature_name(self,input_image_name,metric,bins):
'''Return a measurement feature name '''
return "%s_%s_%d" % (input_image_name, metric, bins)
def get_feature_name_bins(self,input_image_name,metric,bins, binno):
'''Return a measurement feature name '''
return "%s_%s_Bin%d_of_%d" % (input_image_name, metric, binno, bins)
def get_measurement_name(self, input_image_name, metric, bins):
'''Return the whole measurement name'''
return '_'.join([ENTROPY,
self.get_feature_name(input_image_name,metric,bins)])
def get_measurement_name_bins(self, input_image_name, metric, bins, binno):
'''Return the whole measurement name'''
return '_'.join([ENTROPY,
self.get_feature_name_bins(input_image_name,metric,bins,binno)])
def get_measurement_columns(self, pipeline):
'''Return the column definitions for measurements made by this module'''
input_object_name = self.input_object_name.value
input_image_name=self.input_image_name.value
metric = self.intensity_measurement.value
bins = self.bin_number.value
bincollist=[]
for eachbin in range(bins):
bincollist.append((input_object_name,
self.get_measurement_name_bins(input_image_name,metric,bins,eachbin+1),
cpmeas.COLTYPE_FLOAT))
return [(input_object_name,
self.get_measurement_name(input_image_name,metric,bins),
cpmeas.COLTYPE_FLOAT)]+bincollist
def get_categories(self, pipeline, object_name):
"""Get the categories of measurements supplied for the given object name
pipeline - pipeline being run
object_name - name of labels in question (or 'Images')
returns a list of category names
"""
if object_name == self.input_object_name:
return [ENTROPY]
else:
return []
def get_measurements(self, pipeline, object_name, category):
"""Get the measurements made on the given object in the given category"""
if (object_name == self.input_object_name and
category == ENTROPY):
bins=self.bin_number.value
metric = self.intensity_measurement.value
binmeaslist=[]
for eachbin in range(bins):
binmeaslist.append(metric+'_Bin'+str(eachbin+1)+'_of_'+str(bins))
return ["Entropy"]+binmeaslist
else:
return []