cicada 0.9.0-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+