cicada 0.9.0-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,377 @@
1
+ #--
2
+ # /* ***** BEGIN LICENSE BLOCK *****
3
+ # *
4
+ # * Copyright (c) 2012 Colin J. Fuller
5
+ # *
6
+ # * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # * of this software and associated documentation files (the Software), to deal
8
+ # * in the Software without restriction, including without limitation the rights
9
+ # * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # * copies of the Software, and to permit persons to whom the Software is
11
+ # * furnished to do so, subject to the following conditions:
12
+ # *
13
+ # * The above copyright notice and this permission notice shall be included in
14
+ # * all copies or substantial portions of the Software.
15
+ # *
16
+ # * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ # * SOFTWARE.
23
+ # *
24
+ # * ***** END LICENSE BLOCK ***** */
25
+ #++
26
+
27
+ require 'ostruct'
28
+ require 'base64'
29
+ require 'rexml/document'
30
+
31
+ require 'rimageanalysistools'
32
+ require 'rimageanalysistools/get_image'
33
+
34
+ module Cicada
35
+
36
+ ##
37
+ # Handles the serialization of the ImageObjects for storing them in a file.
38
+ #
39
+ # The serialized output will consist of an XML file describing the objects
40
+ # in human-readable form and including a binary data sectiobn containing the
41
+ # serialized object. This binary section is the only thing read back in, so
42
+ # changing the XML human-readable portion of the file manually will not affect
43
+ # the data read back in.
44
+ #
45
+ class Serialization
46
+
47
+ ##
48
+ # Converts an array of ImageObjects to an XML-formatted string containing
49
+ # the serialized data.
50
+ #
51
+ # @param [Enumerable<ImageObject>] image_objects an Enumerable list of image objects
52
+ #
53
+ # @return [String] a string containing formatted XML and binary representations of the
54
+ # image objects.
55
+ #
56
+ def self.serialize_image_objects(image_objects)
57
+
58
+ doc = REXML::Document.new
59
+
60
+
61
+ doc.add_element "root"
62
+
63
+ image_objects.each do |iobj|
64
+
65
+ in_doc = REXML::Document.new iobj.writeToXMLString
66
+
67
+ in_doc.root.elements[1, "serialized_form"].text = Base64.encode64(Marshal.dump(iobj))
68
+
69
+ doc.root.add in_doc.elements[1,"image_object"]
70
+
71
+ end
72
+
73
+ output = ""
74
+
75
+ doc.write(output, 2)
76
+
77
+ output
78
+
79
+ end
80
+
81
+ ##
82
+ # Restores an image object from a byte string.
83
+ #
84
+ # @param [String] bin_data the bytes representing the image object. This should be
85
+ # a standard byte string. The XML files use base64 encoding, and the data should already
86
+ # be unencoded.
87
+ #
88
+ # @return [ImageObject] the encoded ImageObject
89
+ #
90
+ def self.image_object_from_bytes(bin_data)
91
+
92
+ Marshal.load(bin_data)
93
+
94
+ #j_bytes = bin_data.to_java_bytes
95
+
96
+ #oi = Java::java.io.ObjectInputStream.new(Java::java.io.ByteArrayInputStream.new(j_bytes))
97
+
98
+ #oi.readObject
99
+
100
+ end
101
+
102
+ ##
103
+ # Restores image objects from an XML string containing their serialized representation.
104
+ # (This uses the base64 encoded binary information in the XML file, not the human readable portion.)
105
+ #
106
+ # @param [String] data the XML string
107
+ #
108
+ # @return [Array<ImageObject>] the image objects encoded in the string
109
+ #
110
+ def self.unserialize_image_objects(data)
111
+
112
+ objs = []
113
+
114
+ doc = REXML::Document.new data
115
+
116
+ doc.elements.each("*/image_object/serialized_form") do |el|
117
+
118
+ bin_data = Base64.decode64(el.text)
119
+
120
+ objs << image_object_from_bytes(bin_data)
121
+
122
+ end
123
+
124
+ objs
125
+
126
+ end
127
+
128
+ end
129
+
130
+
131
+ ##
132
+ # A collection of methods for interacting with input and output files for cicada.
133
+ #
134
+ class FileInteraction
135
+
136
+ # parameters required by the methods in this class
137
+ REQUIRED_PARAMETERS = [:dirname_set, :basename_set, :mask_relative_dirname, :mask_extra_extension, :data_directory, :correction_date, :output_positions_to_directory]
138
+
139
+ # parmeters used but not required in this class or only required for optional functionality
140
+ OPTIONAL_PARAMETERS = [:in_situ_aberr_corr_basename_set]
141
+
142
+ # extension on position data (image object) files.
143
+ POS_XML_EXTENSION = "_position_data.xml"
144
+
145
+ # extension on the correction files
146
+ CORR_XML_EXTENSION = "_correction.xml"
147
+
148
+ # extension on the distance measurement files
149
+ DIFFS_TXT_EXTENSION = "_diffs.txt"
150
+
151
+ # separator used in the parameter file for multiple files, directories, etc.
152
+ MULTI_NAME_SEP = ","
153
+
154
+
155
+ ##
156
+ # Loads an image from the specified file.
157
+ #
158
+ # @param [String] image_fn the image's filename
159
+ #
160
+ # @return [ReadOnlyImage] the image at the specified filename
161
+ #
162
+ def self.load_image(image_fn)
163
+
164
+ RImageAnalysisTools.get_image(image_fn)
165
+
166
+ end
167
+
168
+
169
+ ##
170
+ # Gets the filename to which / from which image object positions will be written /
171
+ # read from a parameter dictionary.
172
+ # @param [ParameterDictionary, Hash] p a hash-like object specifying the filename for the positions.
173
+ #
174
+ # @return [String] the absolute path to the position file.
175
+ #
176
+ def self.position_data_filename(p)
177
+ dir = p[:data_directory]
178
+ File.expand_path(p[:basename_set].split(MULTI_NAME_SEP)[0] + POS_XML_EXTENSION, dir)
179
+ end
180
+
181
+ ##
182
+ # Gets the filename of data to use for in situ correction from a parameter dictionary.
183
+ #
184
+ # @param [ParameterDictionary, Hash] p a hash-like object specifying the filename for the
185
+ # in situ correction data
186
+ #
187
+ # @return [String] the absolute path to the in situ correction data file
188
+ #
189
+ def self.in_situ_corr_data_filename(p)
190
+ dir = [:data_directory]
191
+ File.expand_path(p[:in_situ_aberr_corr_basename_set].split(MULTI_NAME_SEP)[0] + POS_XML_EXTENSION, dir)
192
+ end
193
+
194
+ ##
195
+ # Checks if the position data file already exists.
196
+ #
197
+ # @param [ParameterDictionary, Hash] p a hash-like object specifying the filename for the positions.
198
+ #
199
+ # @return [Boolean] whether the position data file exists.
200
+ #
201
+ def self.position_file_exists?(p)
202
+ File.exist?(FileInteraction.position_data_filename(p))
203
+ end
204
+
205
+ ##
206
+ # Unserializes image object position data from a specified file using the methods in the Serialization
207
+ # class.
208
+ #
209
+ # @param [String] fn the name of the data file.
210
+ #
211
+ # @return [Array<ImageObject>] the image objects contained in the file.
212
+ #
213
+ def self.unserialize_position_data_file(fn)
214
+
215
+ data_str = nil
216
+
217
+ File.open(fn) do |f|
218
+ data_str = f.read
219
+ end
220
+
221
+ Serialization.unserialize_image_objects(data_str)
222
+
223
+ end
224
+
225
+ ##
226
+ # Reads the image objects associated with an analysis specified by a parameter dictionary.
227
+ #
228
+ # @param [ParameterDictionary, Hash] p a hash-like object specifying the filename for the positions.
229
+ #
230
+ # @return [Array<ImageObject>] the image objects associated with the analysis
231
+ #
232
+ def self.read_position_data(p)
233
+
234
+ fn = FileInteraction.position_data_filename(p)
235
+
236
+ FileInteraction.unserialize_position_data_file(fn)
237
+
238
+ end
239
+
240
+ ##
241
+ # Reads the image objects for in situ correction associated with an analysis specified by
242
+ # a parameter dictionary.
243
+ #
244
+ # @param [ParameterDictionary, Hash] p a hash-like object specifying the filename for the
245
+ # in situ correction data
246
+ #
247
+ # @return [Array<ImageObject>] the image objects for in situ correction associated with the analysis
248
+ #
249
+ def self.read_in_situ_corr_data(p)
250
+
251
+ fn = FileInteraction.in_situ_corr_data_filename(p)
252
+
253
+ FileInteraction.unserialize_position_data_file(fn)
254
+
255
+ end
256
+
257
+ ##
258
+ # Lists all the files and masks to be analyzed given a parameter dictionary.
259
+ #
260
+ # @param [ParameterDictionary, Hash] p a hash-like object specifying the analysis
261
+ #
262
+ # @return [Array<OpenStruct>] an array of objects that respond to #image_fn and #mask_fn,
263
+ # which return each image's filename and its paired mask's filename respectively.
264
+ #
265
+ def self.list_files(p)
266
+
267
+ dirnames = p[:dirname_set].split(MULTI_NAME_SEP)
268
+ basenames = p[:basename_set].split(MULTI_NAME_SEP)
269
+
270
+ image_sets = []
271
+
272
+ dirnames.each do |d|
273
+
274
+ mask_dirname = File.join(d, p[:mask_relative_dirname])
275
+
276
+ Dir.foreach(d) do |f|
277
+
278
+ if basenames.any? { |e| f.match(e) } then
279
+
280
+ im = File.expand_path(f, d)
281
+ msk = File.expand_path(f + p[:mask_extra_extension], mask_dirname)
282
+
283
+ current = OpenStruct.new(image_fn: im, mask_fn: msk)
284
+
285
+ image_sets << current
286
+
287
+ end
288
+
289
+ end
290
+
291
+ end
292
+
293
+ image_sets
294
+
295
+ end
296
+
297
+ ##
298
+ # Writes the provided image objects to file to the location specified in a parameter dictionary.
299
+ #
300
+ # @param [Enumerable<ImageObject>] image_objects the objects that will be written to file.
301
+ # @param [ParameterDictionary, Hash] p a hash-like object specifying the filename for the data.
302
+ #
303
+ # @return [void]
304
+ #
305
+ def self.write_position_data(image_objects, p)
306
+
307
+ fn = position_data_filename(p)
308
+
309
+ write_position_data_file(image_objects,fn)
310
+
311
+ end
312
+
313
+ ##
314
+ # Writes the provided image objects to file to the location specified.
315
+ #
316
+ # @param [Enumerable<ImageObject>] image_objects the objects that will be written to file.
317
+ # @param [String] fn the filename of the file to which to write the data
318
+ #
319
+ # @return [void]
320
+ #
321
+ def self.write_position_data_file(image_objects, fn)
322
+
323
+ File.open(fn, 'w') do |f|
324
+
325
+ f.write(Serialization.serialize_image_objects(image_objects))
326
+
327
+ end
328
+
329
+ end
330
+
331
+ ##
332
+ # Gets the filename for storing/reading the correction based upon the supplied parameter dictionary.
333
+ #
334
+ # @param [ParameterDictionary, Hash] p a hash-like object specifying the correction file location.
335
+ #
336
+ # @return [String] the filename for the correction file.
337
+ #
338
+ def self.correction_filename(p)
339
+
340
+ dir = p[:data_directory]
341
+ fn = p[:correction_date]
342
+
343
+ File.expand_path(fn + CORR_XML_EXTENSION, dir)
344
+
345
+ end
346
+
347
+ ##
348
+ # Writes an array of distance measurements to file based upon the supplied parameter dictionary.
349
+ #
350
+ # @param [Enumerable<#to_s>] diffs an enumerable list of distance meaurements.
351
+ # @param [ParameterDictionary, hash] p a hash-like object specifying the file location.
352
+ #
353
+ # @return [void]
354
+ #
355
+ def self.write_differences(diffs, p)
356
+
357
+ dirname = p[:output_positions_to_directory]
358
+
359
+ fn = File.expand_path(p[:basename_set] + DIFFS_TXT_EXTENSION, dirname)
360
+
361
+ File.open(fn, 'w') do |f|
362
+
363
+ diffs.each do |d|
364
+
365
+ f.puts(d.to_s)
366
+
367
+ end
368
+
369
+ end
370
+
371
+ end
372
+
373
+ end
374
+
375
+ end
376
+
377
+
@@ -0,0 +1,235 @@
1
+ # /* ***** BEGIN LICENSE BLOCK *****
2
+ # *
3
+ # * Copyright (c) 2012 Colin J. Fuller
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
13
+ # * all 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
+ # *
23
+ # * ***** END LICENSE BLOCK ***** */
24
+
25
+ require 'rimageanalysistools'
26
+
27
+ require 'facets/math/mean'
28
+ require 'facets/math/std'
29
+
30
+ module Cicada
31
+
32
+ ##
33
+ # Interface for a class that fits a set of scalars with associated image object
34
+ # to some distribution.
35
+ #
36
+ class DistributionFitter
37
+
38
+ attr_accessor :parameters
39
+
40
+ ##
41
+ # Constructs a new distribution fitter from specified parameters. Derived
42
+ # classes should indicate which parameters are used.
43
+ #
44
+ # @param [ParameterDictionary, Hash] params a hash-like object containing the parameters
45
+ #
46
+ def initialize(params)
47
+
48
+ @parameters = params
49
+
50
+ end
51
+
52
+ ##
53
+ # Fits the data with associated image objects to the distribution.
54
+ #
55
+ # *Abstract*
56
+ #
57
+ # @param [Array<ImageObject>] objects the associated image objects
58
+ # @param [Array<Numeric>] diffs the scalar data being fit (in the same order
59
+ # as the image objects)
60
+ #
61
+ # @return [Array<Numeric>] the fitted distribution parameters
62
+ #
63
+ def fit(objects, diffs)
64
+ nil
65
+ end
66
+
67
+ end
68
+
69
+ ##
70
+ # An objective function that calculates the negative log likelihood of supplied data points
71
+ # being generated from a p3d distribution with specified parameters.
72
+ #
73
+ # The data points should be an array of (positive) scalars set using the attribute r.
74
+ #
75
+ #
76
+ class P3DObjectiveFunction
77
+
78
+ include Java::edu.stanford.cfuller.imageanalysistools.fitting.ObjectiveFunction
79
+
80
+ ##
81
+ # Constructs an empty P3DObjectiveFunction.
82
+ #
83
+ def initialize
84
+
85
+ @r = nil
86
+ @s = nil
87
+ @min_prob = nil
88
+ @use_min_prob = false
89
+ @should_fit_s = true
90
+
91
+ end
92
+
93
+ attr_accessor :r, :use_min_prob, :should_fit_s
94
+
95
+ attr_reader :s, :min_prob
96
+
97
+ ##
98
+ # Sets a static value for the parameter that is the standard deviation of the generating
99
+ # Gaussian distribution. Setting this parameter disables its fitting by the objective function.
100
+ #
101
+ # @param [Numeric] s_new the static value for the standard deviation parameter
102
+ #
103
+ # @return [void]
104
+ #
105
+ def s=(s_new)
106
+ @s = s_new
107
+ @should_fit_s = false
108
+ end
109
+
110
+ ##
111
+ # Sets a minimum probability cutoff for calculating the likelihood. Could be used for various
112
+ # robust fitting approaches.
113
+ #
114
+ # @param [Numeric] min_prob the minimum allowed probability for any data point. Probabilities
115
+ # smaller than this value will be set to this value.
116
+ #
117
+ # @return [void]
118
+ #
119
+ def min_prob=(min_prob)
120
+
121
+ @min_prob = min_prob
122
+ @use_min_prob = true
123
+
124
+ end
125
+
126
+ ##
127
+ # Calculates the probability density of the p3d distribution at a given point.
128
+ #
129
+ # @param [Numeric] r the distance at which to calculate the probability density
130
+ # @param [Numeric] m the mean-like parameter of the p3d distribution
131
+ # @param [Numeric] s the standard-deviation-like parameter of the p3d distribution
132
+ #
133
+ # @return [Float] the probability density at the given point
134
+ #
135
+ def p3d(r, m, s)
136
+
137
+ (Math.sqrt(2.0/Math::PI)*r/(2*m*s))*(Math.exp(-1 * (m-r)**2/(2*s**2)) - Math.exp( -1 * (m+r)**2/(2*s**2)))
138
+
139
+ end
140
+
141
+ ##
142
+ # Evaluates the negative log-likelihood of the data given the parameters specified.
143
+ #
144
+ # @param [Array<Numeric>] point a 2-element array containing the mean- and standard deviation-like
145
+ # parameters. If a static standard deviation parameter is being used, something should still be
146
+ # provided here, but it will be ignored.
147
+ #
148
+ # @return [Float] the negative log-likelihood of the data.
149
+ #
150
+ def evaluate(point)
151
+
152
+ point = point.toArray unless point.is_a? Array
153
+
154
+ m = point[0]
155
+ s = point[1]
156
+ s = @s unless @should_fit_s
157
+
158
+ return Float::MAX if (m < 0 or s < 0)
159
+
160
+ r.reduce(0.0) do |sum, ri|
161
+
162
+ temp_neg_log_p = -1.0*Math.log( p3d(ri, m, s))
163
+
164
+ if (@use_min_prob and temp_neg_log_p > @min_prob) then
165
+
166
+ sum + @min_prob
167
+
168
+ else
169
+
170
+ sum + temp_neg_log_p
171
+
172
+ end
173
+
174
+ end
175
+
176
+ end
177
+
178
+ end
179
+
180
+
181
+ ##
182
+ # A distribution fitter that fits data to a P3D distribution.
183
+ #
184
+ class P3DFitter < DistributionFitter
185
+
186
+ # parameters required by the methods in this class
187
+ REQUIRED_PARAMETERS = []
188
+
189
+ # parmeters used but not required in this class or only required for optional functionality
190
+ OPTIONAL_PARAMETERS = [:robust_p3d_fit_cutoff]
191
+
192
+
193
+ ##
194
+ # Fits the P3D mean- and standard-deviation-like parameters to the data.
195
+ #
196
+ # @param [Array<ImageObject>] objects the image objects whose distances are being fit
197
+ # @param [Array<Numeric>] diffs the distances being fit
198
+ #
199
+ # @return [Array] a two-element array containing the mean- and standard-deviation-like parameters.
200
+ #
201
+ def fit(objects, diffs)
202
+
203
+ of = P3DObjectiveFunction.new
204
+
205
+ of.r = diffs
206
+
207
+ tol = 1e-12
208
+
209
+ nmm = Java::edu.stanford.cfuller.imageanalysistools.fitting.NelderMeadMinimizer.new(tol)
210
+
211
+ initial_mean = Math.mean(diffs)
212
+
213
+ initial_width = Math.std(diffs)
214
+
215
+ starting_point = Java::org.apache.commons.math3.linear.ArrayRealVector.new(2, 0.0)
216
+
217
+ starting_point.setEntry(0, initial_mean)
218
+ starting_point.setEntry(1, initial_width)
219
+
220
+ if @parameters[:robust_p3d_fit_cutoff] then
221
+
222
+ of.min_prob= @parmaeters[:robust_p3d_fit_cutoff].to_f
223
+
224
+ end
225
+
226
+ nmm.optimize(of, starting_point).toArray.to_a
227
+
228
+ end
229
+
230
+ end
231
+
232
+ end
233
+
234
+
235
+