karolinska 0.1.1 → 0.1.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 46e27f8030e3b7ffdb594d60e71aa45acef8e200
4
- data.tar.gz: f69ed370032df5f392337236f7935071827b50ea
3
+ metadata.gz: bc54653db22d4ec980e3ae52664f2641f6457621
4
+ data.tar.gz: 31139b2b11bf17c530cf04c78725dbed0deb6d51
5
5
  SHA512:
6
- metadata.gz: fb86d46bdd109e84562e8ea3f4d4bd7d24ed82fbcfda37cd510dd9f8444fc0a2b9daeaaba773d1d41b4d551fea0cc7f34c65cedca200993fa7632e52e617a871
7
- data.tar.gz: 11f27ddc344b14a85736d294a0e64341f562164b6adcc95a27e2b9a57922a9d5212b50cdc7b9a26f39a1439dfe0109c721b78ea65450a79aef8218dbd0b70f05
6
+ metadata.gz: bd7f048729fec8caa7e7787d0519cdeaa14034e0530486b7e1c6417d1d2d17b44ee26cac9efdc9dc6a8c4557649d81dc66cfd22fbd708eece28b4dfd65339100
7
+ data.tar.gz: 6bc22d75a74574529d2d4cba01ef35f34065341c96d7a49afe1929dffc172b542edb2cea22ee74805c895fb5749ae8ed0ef45b7009f0998300f421b2786d0dae
@@ -0,0 +1,59 @@
1
+ module Karolinska
2
+ class ClusterRequest
3
+
4
+ include BenchmarkMethods
5
+
6
+ # benchmark :karolinska_request
7
+
8
+ require 'net/http/post/multipart'
9
+
10
+ ENDPOINT = 'http://35.161.196.215/Dps.aspx?SaveImagesAtServer=true&Timeout=30000'
11
+ PASSWORD = 'Karolinska'
12
+
13
+ def initialize(paths)
14
+ @paths = paths
15
+ end
16
+
17
+ def start
18
+ url = URI.parse ENDPOINT
19
+ request = Net::HTTP::Post::Multipart.new url.path,
20
+ "file1" => UploadIO.new(@paths[:front], "image/jpeg", "front.jpg"),
21
+ "file2" => UploadIO.new(@paths[:left], "image/jpeg", "left.jpg"),
22
+ "file3" => UploadIO.new(@paths[:right], "image/jpeg", "right.jpg"),
23
+ "file4" => UploadIO.new(@paths[:up], "image/jpeg", "upward.jpg"),
24
+ "file5" => UploadIO.new(@paths[:down], "image/jpeg", "downward.jpg")
25
+
26
+ json, status = karolinska_request(request)
27
+ # Rails.logger.debug json
28
+ return [json, status]
29
+ end
30
+
31
+ private
32
+
33
+ def karolinska_request(request)
34
+ url = URI.parse ENDPOINT
35
+ response = Net::HTTP.start(url.host, url.port) do |http|
36
+ http.request(request)
37
+ end
38
+ rescue Timeout::Error => error
39
+ Rails.logger.error "#{error}".red
40
+ [nil, error]
41
+ else
42
+
43
+ case response
44
+ when Net::HTTPOK
45
+ unless response.body.blank?
46
+ [JSON.parse(response.body), 200]
47
+ else
48
+ Rails.logger.error "Karolinska returned an empty string".red
49
+ [{}, "Empty String"]
50
+ end
51
+ when Net::HTTPClientError, Net::HTTPInternalServerError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError
52
+ msg = "#{response.class}: #{response.code}, #{response.message}"
53
+ Rails.logger.error msg.red
54
+ [{}, msg]
55
+ end
56
+
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,16 @@
1
+ # From Karolinska 2.2.2 documentation:
2
+ #
3
+ # 400 Bad Request. Bad format.
4
+ # 401 Unauthorized. Authorization error.
5
+ # 403 Forbidden. For example more than five posted images.
6
+ # 408 Request Timeout. Timeout exception.
7
+ # 501 Not Supported. For example not supported image type.
8
+ # 503 Service Unavailable. Web service is unavailable.
9
+
10
+ module Karolinska
11
+ class Error
12
+ class Base < StandardError; end
13
+
14
+
15
+ end
16
+ end
@@ -0,0 +1,5 @@
1
+ module Karolinska
2
+ module Fake
3
+ SET = {"AfroTexture"=>"misses", "Age"=>"15 - 25", "Cheekbones"=>"low (0,88)", "Cheeks"=>"wide (0,57)", "Chin"=>"oval (0,81)", "Corners"=>"up", "CrowFeet"=>"no", "Detected"=>true, "EarLeft"=>"misses", "EarRight"=>"misses", "EyeDistance"=>"normal (0,96)", "EyeSizeLeft"=>"normal (0,68)", "EyeSizeOpposed"=>"big (0,90)", "EyebrowDistance"=>"normal (0,28)", "EyebrowLeftEyedistance"=>"high (0,89)", "EyebrowRightEyedistance"=>"high (0,94)", "Eyecircles"=>"no", "Filtrum"=>"Visible (0.00)", "FiltrumAltitude"=>"high (0,31)", "FiltrumCurvature"=>"concave (0,95)", "Fitted"=>true, "Forehead_central"=>"misses", "Forehead_top"=>"misses", "Forehead_under"=>"misses", "Gender"=>"Male (0,73)", "HairGrade"=>"misses", "HairLength"=>"misses", "HairLow"=>"misses", "HairStretch"=>"misses", "HairStyle"=>"misses", "Iris"=>"normal (0,79)", "Jawbones"=>"normal (0,73)", "LeftCheekbone"=>"small (0,99)", "LeftEarChin"=>"misses", "LeftEarForehead"=>"misses", "LeftEarHeadDistance"=>"misses", "LeftEarLowerChin"=>"misses", "LeftEarNosebridge"=>"misses", "LeftEarNoseroot"=>"misses", "Lines"=>"yes (0.17)", "LipDistDecideBent"=>"straight (0,00)", "LowerLip"=>"thin (0,97)", "MouthSize"=>"normal (0,82)", "MouthWidth"=>"normal (0,21)", "NoseBridge_central"=>"misses", "NoseBridge_top"=>"misses", "NoseBridge_under"=>"misses", "NoseCurve"=>"curveless (0,86)", "NoseLength"=>"long (0,98)", "NoseNostrilsWidth"=>"normal (0,98)", "NoseSideWidth"=>"normal (0,73)", "NoseTipAltitude"=>"high (0,99)", "NoseTipWidthStretch"=>"wide (0,14)", "NoseWidth"=>"normal (0,98)", "OpposedCheekbone"=>"wide (1,00)", "OpposedEarChin"=>"misses", "OpposedEarForehead"=>"misses", "OpposedEarHeadDistance"=>"misses", "OpposedEarLowerChin"=>"misses", "OpposedEarNosebridge"=>"misses", "OpposedEarNoseroot"=>"misses", "UpperLip"=>"thin (0,54)", "VerticalEarDiff"=>"misses", "VerticalMagnitude"=>"normal", "WidthMagnitude"=>"small", "Wrinkle"=>"Visible (1.00)", "interest"=>"misses"}
4
+ end
5
+ end
@@ -0,0 +1,425 @@
1
+ module Karolinska
2
+ class FeatureSet < Struct.new( :raw, :view, :statuscode)
3
+
4
+ # Use Fakeset: Karolinska::Fake::SET
5
+ # fs = Karolinska::FeatureSet.new(Karolinska::Fake::SET, :front)
6
+
7
+ # => [#<struct Karolinska::FeatureSet raw={"Age"=>"30 - 40", "Cheekbones"=>"average (0,69)", "Cheeks"=>"normal (0,89)", "Chin"=>"oval (0,59)", "Corners"=>"straight", "CrowFeet"=>"yes (1.00)", "Detected"=>true, "EarL
8
+ # eft"=>"misses", "EarRight"=>"misses", "EyeDistance"=>"normal (0,93)", "EyeSizeLeft"=>"normal (0,66)", "EyeSizeOpposed"=>"small (0,78)", "EyebrowDistance"=>"normal (0,96)", "EyebrowLeftEyedistance"=>"high (0,67)", "
9
+ # EyebrowRightEyedistance"=>"average (0,91)", "Eyecircles"=>"no", "Filtrum"=>"Visible (0.00)", "FiltrumAltitude"=>"high (0,92)", "FiltrumCurvature"=>"fleet (0,82)", "Fitted"=>true, "Forehead_central"=>"misses", "Fore
10
+ # head_top"=>"misses", "Forehead_under"=>"misses", "Gender"=>"Female (0,96)", "HairLow"=>"misses", "Iris"=>"normal (0,71)", "Jawbones"=>"wide (0,48)", "LeftCheekbone"=>"wide (1,00)", "LeftEarChin"=>"misses", "LeftEar
11
+ # Forehead"=>"misses", "LeftEarHeadDistance"=>"misses", "LeftEarLowerChin"=>"misses", "LeftEarNosebridge"=>"misses", "LeftEarNoseroot"=>"misses", "Lines"=>"no", "LipDistDecideBent"=>"misses", "LowerLip"=>"thin (0,98)
12
+ # ", "MouthSize"=>"normal (0,15)", "MouthWidth"=>"small (0,78)", "NoseBridge_central"=>"misses", "NoseBridge_top"=>"misses", "NoseBridge_under"=>"misses", "NoseCurve"=>"convex (1,00)", "NoseLength"=>"normal (0,99)",
13
+ # "NoseNostrilsWidth"=>"normal (0,99)", "NoseSideWidth"=>"small (0,88)", "NoseTipAltitude"=>"average (0,92)", "NoseTipWidthStretch"=>"normal (0,85)", "NoseWidth"=>"small (0,87)", "OpposedCheekbone"=>"small (0,99)", "
14
+ # OpposedEarChin"=>"misses", "OpposedEarForehead"=>"misses", "OpposedEarHeadDistance"=>"misses", "OpposedEarLowerChin"=>"misses", "OpposedEarNosebridge"=>"misses", "OpposedEarNoseroot"=>"misses", "UpperLip"=>"medium
15
+ # (0,38)", "VerticalEarDiff"=>"misses", "VerticalMagnitude"=>"normal", "WidthMagnitude"=>"small", "Wrinkle"=>"Visible (0.63)", "interest"=>"misses"}, view=:left>]
16
+
17
+ # A feature description: "low (0,88)"
18
+
19
+ FEATURES = [:age,:center_forehead_width,:cheek_width,:cheeks_height,:cheeks_width,:chin_width,:chin_wrinkles,:crow_feet,:detected,:ear_chin_axis,:ear_lower_chin_axis,:ear_nosebridge_axis,:ear_noseroot_axis,:ear_noseroot_axis,:ear_protruding,:ear_size,:eye_size,:eyebrow_distance,:eyecircles,:eyes_distance,:face_strechiness,:face_width,:filtrum,:fitted,:forehead_protruding,:gender,:iris_orientation,:jaw_width,:left_cheek_width,:left_ear_chin_axis,:left_ear_lower_chin_axis,:left_ear_nosebridge_axis,:left_ear_noseroot_axis,:left_ear_protruding,:left_ear_size,:left_eye_size,:left_eyebrow_eye_distance,:left_forehead_protruding,:lower_forehead_width,:lower_lip_fullness,:mouth_size,:mouth_width,:mouthcorner_orientation,:nose_bottom_width,:nose_center_width,:nose_length,:nose_top_width,:nose_width,:nosetip_orientation,:nosetip_width,:nosewings_width,:nostrills_width,:pallium,:pallium_height,:prominence,:right_cheek_width,:right_ear_chin_axis,:right_ear_lower_chin_axis,:right_ear_nosebridge_axis,:right_ear_noseroot_axis,:right_ear_protruding,:right_ear_size,:right_eye_size,:right_forehead_protruding,:upper_forehead_width,:upper_lip_curvature,:upper_lip_fullness,:wrinkles]
20
+
21
+
22
+ def to_hsh
23
+ FEATURES.inject({}){|hsh, m| hsh.update({m => self.send(m.to_s)} )}
24
+ end
25
+
26
+
27
+ def detected
28
+ raw["Detected"] if raw
29
+ end
30
+
31
+ # Is the detected face fitted (true, false)
32
+ def fitted
33
+ raw["Fitted"] if raw
34
+ end
35
+
36
+ # The direction of the face (left, frontal face, right, upwards, downwards), inclusive certainty [0.0, 1.0]
37
+ # TODO not yet in the result string
38
+
39
+ # def face_direction
40
+ # rate raw["Gaze"]
41
+ # end
42
+
43
+ # obvious
44
+ def gender
45
+ rate raw["Gender"], strong: nil, average: nil, weak: nil
46
+ end
47
+
48
+ # obvious
49
+ def age
50
+ raw["Age"] if raw
51
+ end
52
+
53
+ # Prominence part (upper, central, lower) inclusive certainty [0.0, 1.0].
54
+ def prominence
55
+ rate raw["interest"], strong: "upper", average: "central", weak: "lower"
56
+ end
57
+
58
+ # Vertical hole between the nose and the upper border of the mouth (visible, notvisible) inclusive certainty [0.0, 1.0]
59
+ def filtrum
60
+ rate raw["Filtrum"], strong: "visible", average: nil, weak: "notvisible"
61
+ end
62
+
63
+ # Filtrum (pallium) curvature (convex, fleet, concave), inclusive certainty [0.0, 1.0]
64
+ def pallium
65
+ rate raw["FiltrumCurvature"], strong: "convex", average: "fleet", weak: "concave"
66
+ end
67
+
68
+ # filtrum (pallium) altitude (low, average, high), inclusive certainty [0.0, 1.0]
69
+ def pallium_height
70
+ rate raw["FiltrumAltitude"], strong: "high", average: "average", weak: "low"
71
+ end
72
+
73
+ # The distance between the eyebrows (close, normal, far apart), inclusive certainty [0.0, 1.0]
74
+ def eyebrow_distance
75
+ rate raw["EyebrowDistance"], strong: "far apart", average: "normal", weak: "close"
76
+ end
77
+
78
+ # The distance between the eyebrows and left eye (low, average, high), inclusive certainty [0.0, 1.0]
79
+ def left_eyebrow_eye_distance
80
+ rate raw["EyebrowLeftEyedistance"], strong: "high", average: "average", weak: "low"
81
+ end
82
+
83
+ # The distance between the eyebrows and right eye (low, average, high), inclusive certainty [0.0, 1.0]
84
+ def right_eyebrow_eye_distance
85
+ rate raw["EyebrowRightEyedistance"], strong: "high", average: "average", weak: "low"
86
+ end
87
+
88
+ # The size of the left eye (small, normal, big), inclusive certainty [0.0, 1.0]
89
+ def left_eye_size
90
+ rate raw["EyeSizeLeft"], strong: "big", average: "normal", weak: "small"
91
+ end
92
+
93
+ # The size of the right eye (small, normal, big), inclusive certainty [0.0, 1.0]
94
+ def right_eye_size
95
+ rate raw["EyeSizeOpposed"], strong: "big", average: "normal", weak: "small"
96
+ end
97
+
98
+ # Distance between the eyes (close, normal, far apart), inclusive certainty [0.0, 1.0]
99
+ def eyes_distance
100
+ rate raw["EyeDistance"], strong: "far apart", average: "normal", weak: "close"
101
+ end
102
+
103
+ # The stretchiness of the face (withdrawn, normal, stretched)
104
+ def face_strechiness
105
+ rate raw["VerticalMagnitude"], strong: "stretched", average: "normal", weak: "withdrawn"
106
+ end
107
+
108
+ # The width of the face (small, average, wide)
109
+ def face_width
110
+ rate raw["WidthMagnitude"], strong: "wide", average: "average", weak: "small"
111
+ end
112
+
113
+ # The width magnitude of the nose (small, normal, big), inclusive certainty [0.0, 1.0]
114
+ def nose_width
115
+ rate raw["NoseWidth"], strong: "big", average: "normal", weak: "small"
116
+ end
117
+
118
+ # The length magnitude of the nose (short, normal, long), inclusive certainty [0.0, 1.0]
119
+ def nose_length
120
+ rate raw["NoseLength"], strong: "long", average: "normal", weak: "short"
121
+ end
122
+
123
+ # Width of the nose upper bridge (small, normal, wide)
124
+ def nose_top_width
125
+ rate raw["NoseBridge_top"], strong: "wide", average: "normal", weak: "small"
126
+ end
127
+
128
+ # Width of the nose central bridge (small, normal, wide)
129
+ def nose_center_width
130
+ rate raw["NoseBridge_central"], strong: "wide", average: "normal", weak: "small"
131
+ end
132
+
133
+ # Width of the nose lower bridge (small, normal, wide)
134
+ def nose_bottom_width
135
+ rate raw["NoseBridge_under"], strong: "wide", average: "normal", weak: "small"
136
+ end
137
+
138
+ # Nose tip width stretch (small, normal, wide), inclusive certainty [0.0, 1.0]
139
+ def nostrills_width
140
+ rate raw["NoseNostrilsWidth"], strong: "wide", average: "normal", weak: "small"
141
+ end
142
+
143
+ # Nose tip width stretch (small, normal, wide), inclusive certainty [0.0, 1.0]
144
+ def nosetip_width
145
+ rate raw["NoseTipWidthStretch"], strong: "wide", average: "normal", weak: "small"
146
+ end
147
+
148
+ # Nose wings width (small, normal, wide), inclusive certainty [0.0, 1.0]
149
+ def nosewings_width
150
+ rate raw["NoseSideWidth"], strong: "wide", average: "normal", weak: "small"
151
+ end
152
+
153
+ def nosetip_orientation
154
+ rate raw["NoseTipAltitude"], strong: "convex", average: "normal", weak: "concave"
155
+ end
156
+
157
+ # Direction of the mouth corners (down, straight, up), inclusive certainty [0.0, 1.0]
158
+ def mouthcorner_orientation
159
+ rate raw["Corners"], strong: "wide", average: "normal", weak: "small"
160
+ end
161
+
162
+ # Size of the mouth (small, normal, big), inclusive certainty [0.0, 1.0]
163
+ def mouth_size
164
+ rate raw["MouthSize"], strong: "big", average: "normal", weak: "small"
165
+ end
166
+
167
+ # The width of the mouth (small, normal, big), inclusive certainty [0.0, 1.0]
168
+ def mouth_width
169
+ rate raw["MouthWidth"], strong: "big", average: "normal", weak: "small"
170
+ end
171
+
172
+ # Upper lip curvature (straight, light curved, deep curved), inclusive certainty [0.0, 1.0]
173
+ def upper_lip_curvature
174
+ rate raw["LipDistDecideBent"], strong: "deep curved", average: "light curved", weak: "straight"
175
+ end
176
+
177
+ # The thickness of the upper lip (thin, medium, full), inclusive certainty [0.0, 1.0]
178
+ def upper_lip_fullness
179
+ rate raw["UpperLip"], strong: "full", average: "medium", weak: "thin"
180
+ end
181
+
182
+ # The fullness of the lower lip (thin, medium, full), inclusive certainty [0.0, 1.0]
183
+ def lower_lip_fullness
184
+ rate raw["LowerLip"], strong: "full", average: "medium", weak: "thin"
185
+ end
186
+
187
+ # The appearance of the chin (round, oval, pointy tip), inclusive certainty [0.0, 1.0]
188
+ def chin_width
189
+ rate raw["Chin"], strong: "round", average: "oval", weak: "pointy tip"
190
+ end
191
+
192
+ # The appearance of the jawbones (narrow, normal, wide), inclusive certainty [0.0, 1.0]
193
+ def jaw_width
194
+ rate raw["Jawbones"], strong: "wide", average: "normal", weak: "narrow"
195
+ end
196
+
197
+ # The location of the cheekbones (low, average, high), inclusive certainty [0.0, 1.0]
198
+ def cheeks_height
199
+ rate raw["Cheekbones"], strong: "high", average: "average", weak: "low"
200
+ end
201
+
202
+ # The width of the left cheekbone (small, normal, wide), inclusive certainty [0.0, 1.0]
203
+ def left_cheek_width
204
+ rate raw["LeftCheekbone"], strong: "wide", average: "normal", weak: "small"
205
+ end
206
+
207
+ # The width of the right cheekbone (small, normal, wide), inclusive certainty [0.0, 1.0]
208
+ def right_cheek_width
209
+ rate raw["OpposedCheekbone"], strong: "wide", average: "normal", weak: "small"
210
+ end
211
+
212
+ # Width of both cheekbones (small, normal, wide) inclusive certainty [0.0, 1.0]
213
+ def cheeks_width
214
+ rate raw["Cheeks"], strong: "wide", average: "normal", weak: "small"
215
+ end
216
+
217
+ # Width of the upper third forehead (small, normal, wide)
218
+ def upper_forehead_width
219
+ rate raw["Forehead_top"], strong: "wide", average: "normal", weak: "small"
220
+ end
221
+
222
+ # Width of the central third forehead (small, normal, wide)
223
+ def center_forehead_width
224
+ rate raw["Forehead_central"], strong: "wide", average: "normal", weak: "small"
225
+ end
226
+
227
+ # Width of the lower third forehead (small, normal, wide)
228
+ def lower_forehead_width
229
+ rate raw["Forehead_under"], strong: "wide", average: "normal", weak: "small"
230
+ end
231
+
232
+ # The size of the left ear (small, normal, big)
233
+ def left_ear_size
234
+ rate raw["EarLeft"], strong: "big", average: "normal", weak: "small"
235
+ end
236
+
237
+ # The size of the right ear (small, normal, big)
238
+ def right_ear_size
239
+ rate raw["EarRight"], strong: "big", average: "normal", weak: "small"
240
+ end
241
+
242
+ # Vertical ear difference (left higher, equal, right higher)
243
+ # def ears_diff
244
+ # rate raw["VerticalEarDiff"]
245
+ # end
246
+
247
+ # Distance from average left ear center to chin (small, average, large)
248
+ def left_ear_chin_axis
249
+ rate raw["LeftEarChin"], strong: "large", average: "average", weak: "small"
250
+ end
251
+
252
+ # Distance from average right ear center to chin (small, average, large)
253
+ def right_ear_chin_axis
254
+ rate raw["OpposedEarChin"], strong: "large", average: "average", weak: "small"
255
+ end
256
+
257
+ # Distance from average left ear center to lower chin (small, average, large)
258
+ def left_ear_lower_chin_axis
259
+ rate raw["LeftEarLowerChin"], strong: "large", average: "average", weak: "small"
260
+ end
261
+
262
+ # Distance from average right ear center to lower chin (small, average, large)
263
+ def right_ear_lower_chin_axis
264
+ rate raw["OpposedEarLowerChin"], strong: "large", average: "average", weak: "small"
265
+ end
266
+
267
+ # Distance from average left ear center to nose upper bridge (small, average, large)
268
+ def left_ear_nosebridge_axis
269
+ rate raw["LeftEarNosebridge"], strong: "large", average: "average", weak: "small"
270
+ end
271
+
272
+ # istance from average right ear center to nose upper bridge (small, average, large)
273
+ def right_ear_nosebridge_axis
274
+ rate raw["OpposedEarNosebridge"], strong: "large", average: "average", weak: "small"
275
+ end
276
+
277
+ # Distance from average left ear center to nose root (small, average, large)
278
+ def left_ear_noseroot_axis
279
+ rate raw["LeftEarNoseroot"], strong: "large", average: "average", weak: "small"
280
+ end
281
+
282
+ # Distance from average right ear center to nose root (small, average, large)
283
+ def right_ear_noseroot_axis
284
+ rate raw["OpposedEarNoseroot"], strong: "large", average: "average", weak: "small"
285
+ end
286
+
287
+ # Distance from average left ear center to the forehead (small, average, large)
288
+ def left_forehead_protruding
289
+ rate raw["LeftEarForehead"], strong: "large", average: "average", weak: "small"
290
+ end
291
+
292
+ # Distance from average right ear center to the forehead (small, average, large)
293
+ def right_forehead_protruding
294
+ rate raw["OpposedEarForehead"], strong: "large", average: "average", weak: "small"
295
+ end
296
+
297
+ # Left ear distance from head (small, average, large)
298
+ def left_ear_protruding
299
+ rate raw["LeftEarHeadDistance"], strong: "large", average: "average", weak: "small"
300
+ end
301
+
302
+ # Right ear distance from head (small, average, large)
303
+ def right_ear_protruding
304
+ rate raw["OpposedEarHeadDistance"], strong: "large", average: "average", weak: "small"
305
+ end
306
+
307
+ # Chin wrinkle (visible, not visible) inclusive certainty [0.0, 1.0]
308
+ def chin_wrinkles
309
+ rate raw["Wrinkle"], strong: "visible", average: nil, weak: "not visible"
310
+ end
311
+
312
+ # Facial lines as exposed by laughing or in the case of wrinkles (yes, no)
313
+ def wrinkles
314
+ rate raw["Lines"], strong: "yes", average: "", weak: "no"
315
+ end
316
+
317
+ # Crow features near the outside of the eyes (yes, no) inclusive certainty [0.0, 1.0]
318
+ def crow_feet
319
+ rate raw["CrowFeet"], strong: "yes", average: nil, weak: "no"
320
+ end
321
+
322
+ # Iris (upward, normal, downward) inclusive certainty [0.0, 1.0]
323
+ def iris_orientation
324
+ rate raw["Iris"], strong: "upward", average: "normal", weak: "downward"
325
+ end
326
+
327
+ # Presence of eye circles below the eye location (no, light, heavy)
328
+ def eyecircles
329
+ rate raw["Eyecircles"], strong: "heavy", average: "light", weak: "no"
330
+ end
331
+
332
+ # viewindependet merges below this line:
333
+
334
+ def ear_noseroot_axis
335
+ choose_certain_side left_ear_noseroot_axis, right_ear_noseroot_axis
336
+ end
337
+
338
+ def eye_size
339
+ choose_certain_side left_eye_size, right_eye_size
340
+ end
341
+
342
+ def cheek_width
343
+ choose_certain_side left_cheek_width, right_cheek_width
344
+ end
345
+
346
+ def ear_size
347
+ choose_certain_side left_ear_size, right_ear_size
348
+ end
349
+
350
+ def ear_chin_axis
351
+ choose_certain_side left_ear_chin_axis, right_ear_chin_axis
352
+ end
353
+
354
+ def ear_lower_chin_axis
355
+ choose_certain_side left_ear_lower_chin_axis, right_ear_lower_chin_axis
356
+ end
357
+
358
+ def ear_nosebridge_axis
359
+ choose_certain_side left_ear_nosebridge_axis, right_ear_nosebridge_axis
360
+ end
361
+
362
+ def ear_noseroot_axis
363
+ choose_certain_side left_ear_noseroot_axis, right_ear_noseroot_axis
364
+ end
365
+
366
+ def forehead_protruding
367
+ choose_certain_side left_forehead_protruding, right_forehead_protruding
368
+ end
369
+
370
+ def ear_protruding
371
+ choose_certain_side left_ear_protruding, right_ear_protruding
372
+ end
373
+
374
+
375
+ private
376
+
377
+ # this method rates the analysation and groups it into values of strong / average / weak by attaching integer values of +1, 0 and -1.
378
+ def rate value, *args
379
+ return nil if value == "misses"
380
+ # {certainty: nil, strong: nil, average: nil, weak: nil, description: nil, strength: nil, weight: nil}
381
+ opts = args.extract_options!
382
+
383
+ raise StandardError.new "missing param :strong" if !opts.has_key?(:strong)
384
+ raise StandardError.new "missing param :average" if !opts.has_key?(:average)
385
+ raise StandardError.new "missing param :weak" if !opts.has_key?(:weak)
386
+
387
+ # autodetect certainty
388
+ has_certainty = !!value[/\(.+\)/]
389
+
390
+
391
+ out = Hash.new
392
+ if has_certainty
393
+
394
+ out[:certainty] = value[/(\d[,.]\d+)/].gsub(',', '.').to_f
395
+ out[:description] = value.remove(/.\(.+\)/)
396
+ else
397
+ out[:description] = value
398
+ end
399
+
400
+ out[:weight] = case out[:description].downcase
401
+ when (opts[:strong] || "").downcase then :strong rescue 0
402
+ when (opts[:average] || "").downcase then :average rescue false
403
+ when (opts[:weak] || "").downcase then :weak rescue false
404
+ end
405
+ out[:view] = view
406
+ out
407
+ end
408
+
409
+ def choose_certain_side left, right
410
+ if (!left.nil? && !right.nil?)
411
+ order = [left, right]
412
+ [left, right].sort_by {|side|
413
+ side[:certainty]
414
+ }
415
+ order.last
416
+ else
417
+ left || right
418
+ end
419
+ end
420
+
421
+
422
+
423
+
424
+ end
425
+ end
@@ -0,0 +1,415 @@
1
+ module Karolinska
2
+
3
+ require 'delegate'
4
+
5
+ class FeatureSets < DelegateClass(Array)
6
+
7
+
8
+ # fs = Karolinska::TestSet.test_monique
9
+
10
+ def to_hsh
11
+ Karolinska::FeatureSet::FEATURES.inject({}){|hsh, m| hsh.update({m => self.send(m.to_s)} )}
12
+ end
13
+
14
+ def initialize(arr=[])
15
+ super(arr)
16
+ end
17
+
18
+ def front
19
+ @front || find{|fs| fs.view == :front}
20
+ end
21
+
22
+ def up
23
+ @up || find{|fs| fs.view == :up}
24
+ end
25
+
26
+ def down
27
+ @down || find{|fs| fs.view == :down}
28
+ end
29
+
30
+ def left
31
+ @left || find{|fs| fs.view == :left}
32
+ end
33
+
34
+ def right
35
+ @right || find{|fs| fs.view == :right}
36
+ end
37
+
38
+ def without_error
39
+ collect{|fs| fs.statuscode == 200} || []
40
+ end
41
+
42
+ def detected
43
+ self.inject({}){|h,s| h.update({s.view => s.detected}); h}
44
+ end
45
+
46
+ # Is the detected face fitted (true, false)
47
+ def fitted
48
+ self.inject({}){|h,s| h.update({s.view => s.fitted}); h}
49
+ end
50
+
51
+ def status
52
+ self.inject({}){|h,s| h.update({s.view => s.statuscode}); h}
53
+ end
54
+
55
+ def views
56
+ self.collect(&:view)
57
+ end
58
+
59
+ # def face_direction
60
+ # rate raw["Gaze"]
61
+ # end
62
+
63
+ # we have a certainty value in gender, so we can preiorize it.
64
+ def gender
65
+ priorize(attribute: "gender", left:1, front:1, right:2, up:1, down:1)
66
+ end
67
+
68
+ # Without certainty value, guessing, that the front view has most precise estimation.
69
+ def age
70
+ front.age
71
+ end
72
+
73
+ # Prominence part (upper, central, lower) inclusive certainty [0.0, 1.0].
74
+ def prominence
75
+ priorize(attribute: "prominence")
76
+ end
77
+
78
+ # Vertical hole between the nose and the upper border of the mouth (visible, notvisible) inclusive certainty [0.0, 1.0]
79
+ def filtrum
80
+ priorize(attribute: "filtrum")
81
+ end
82
+
83
+ # Filtrum (pallium) curvature (convex, fleet, concave), inclusive certainty [0.0, 1.0]
84
+ def pallium
85
+ priorize(attribute: "pallium")
86
+ end
87
+
88
+ # filtrum (pallium) altitude (low, average, high), inclusive certainty [0.0, 1.0]
89
+ def pallium_height
90
+ priorize(attribute: "pallium_height")
91
+ end
92
+
93
+ # The distance between the eyebrows (close, normal, far apart), inclusive certainty [0.0, 1.0]
94
+ def eyebrow_distance
95
+ priorize(attribute: "eyebrow_distance")
96
+ end
97
+
98
+ # The distance between the eyebrows and left eye (low, average, high), inclusive certainty [0.0, 1.0]
99
+ def left_eyebrow_eye_distance
100
+ priorize(attribute: "left_eyebrow_eye_distance", left: 1, front: 2, right:3)
101
+ end
102
+
103
+ # The distance between the eyebrows and right eye (low, average, high), inclusive certainty [0.0, 1.0]
104
+ def right_eyebrow_eye_distance
105
+ priorize(attribute: "right_eyebrow_eye_distance", right: 1, front: 2, left:3 )
106
+ end
107
+
108
+ # The size of the left eye (small, normal, big), inclusive certainty [0.0, 1.0]
109
+ def left_eye_size
110
+ priorize(attribute: "left_eye_size", left: 1, front: 2, right:3)
111
+ end
112
+
113
+ # The size of the right eye (small, normal, big), inclusive certainty [0.0, 1.0]
114
+ def right_eye_size
115
+ priorize(attribute: "right_eye_size", right: 1, front: 2, left:3)
116
+ end
117
+
118
+ # Distance between the eyes (close, normal, far apart), inclusive certainty [0.0, 1.0]
119
+ def eyes_distance
120
+ priorize(attribute: "eyes_distance")
121
+ end
122
+
123
+ # The stretchiness of the face (withdrawn, normal, stretched)
124
+ def face_strechiness
125
+ priorize(attribute: "face_strechiness")
126
+ end
127
+
128
+ # The width of the face (small, average, wide)
129
+ def face_width
130
+ priorize(attribute: "face_width")
131
+ end
132
+
133
+ # The width magnitude of the nose (small, normal, big), inclusive certainty [0.0, 1.0]
134
+ def nose_width
135
+ priorize(attribute: "nose_width")
136
+ end
137
+
138
+ # The length magnitude of the nose (short, normal, long), inclusive certainty [0.0, 1.0]
139
+ def nose_length
140
+ priorize(attribute: "nose_length", right:1, left:1, front:2, up:3, down:3, )
141
+ end
142
+
143
+ # Width of the nose upper bridge (small, normal, wide)
144
+ def nose_top_width
145
+ priorize(attribute: "nose_top_width")
146
+ end
147
+
148
+ # Width of the nose central bridge (small, normal, wide)
149
+ def nose_center_width
150
+ priorize(attribute: "nose_center_width")
151
+ end
152
+
153
+ # Width of the nose lower bridge (small, normal, wide)
154
+ def nose_bottom_width
155
+ priorize(attribute: "nose_bottom_width")
156
+ end
157
+
158
+ # Nose tip width stretch (small, normal, wide), inclusive certainty [0.0, 1.0]
159
+ def nostrills_width
160
+ priorize(attribute: "nostrills_width")
161
+ end
162
+
163
+ # Nose tip width stretch (small, normal, wide), inclusive certainty [0.0, 1.0]
164
+ def nosetip_width
165
+ priorize(attribute: "nosetip_width")
166
+ end
167
+
168
+ # Nose wings width (small, normal, wide), inclusive certainty [0.0, 1.0]
169
+ def nosewings_width
170
+ priorize(attribute: "nosewings_width")
171
+ end
172
+
173
+ # Direction of the mouth corners (down, straight, up), inclusive certainty [0.0, 1.0]
174
+ def mouthcorner_orientation
175
+ priorize(attribute: "mouthcorner_orientation")
176
+ end
177
+
178
+ # Size of the mouth (small, normal, big), inclusive certainty [0.0, 1.0]
179
+ def mouth_size
180
+ priorize(attribute: "mouth_size")
181
+ end
182
+
183
+ # The width of the mouth (small, normal, big), inclusive certainty [0.0, 1.0]
184
+ def mouth_width
185
+ priorize(attribute: "mouth_width")
186
+ end
187
+
188
+ # Upper lip curvature (straight, light curved, deep curved), inclusive certainty [0.0, 1.0]
189
+ def upper_lip_curvature
190
+ priorize(attribute: "upper_lip_curvature")
191
+ end
192
+
193
+ # The thickness of the upper lip (thin, medium, full), inclusive certainty [0.0, 1.0]
194
+ def upper_lip_fullness
195
+ priorize(attribute: "upper_lip_fullness")
196
+ end
197
+
198
+ # The fullness of the lower lip (thin, medium, full), inclusive certainty [0.0, 1.0]
199
+ def lower_lip_fullness
200
+ priorize(attribute: "lower_lip_fullness")
201
+ end
202
+
203
+ # The appearance of the chin (round, oval, pointy tip), inclusive certainty [0.0, 1.0]
204
+ def chin_width
205
+ priorize(attribute: "chin_width")
206
+ end
207
+
208
+ # The appearance of the jawbones (narrow, normal, wide), inclusive certainty [0.0, 1.0]
209
+ def jaw_width
210
+ priorize(attribute: "jaw_width")
211
+ end
212
+
213
+ # The location of the cheekbones (low, average, high), inclusive certainty [0.0, 1.0]
214
+ def cheeks_height
215
+ priorize(attribute: "cheeks_height")
216
+ end
217
+
218
+ # The width of the left cheekbone (small, normal, wide), inclusive certainty [0.0, 1.0]
219
+ def left_cheek_width
220
+ priorize(attribute: "left_cheek_width")
221
+ end
222
+
223
+ # The width of the right cheekbone (small, normal, wide), inclusive certainty [0.0, 1.0]
224
+ def right_cheek_width
225
+ priorize(attribute: "right_cheek_width")
226
+ end
227
+
228
+ # Width of both cheekbones (small, normal, wide) inclusive certainty [0.0, 1.0]
229
+ def cheeks_width
230
+ priorize(attribute: "cheeks_width")
231
+ end
232
+
233
+ # Width of the upper third forehead (small, normal, wide)
234
+ def upper_forehead_width
235
+ priorize(attribute: "upper_forehead_width")
236
+ end
237
+
238
+ # Width of the central third forehead (small, normal, wide)
239
+ def center_forehead_width
240
+ priorize(attribute: "center_forehead_width")
241
+ end
242
+
243
+ # Width of the lower third forehead (small, normal, wide)
244
+ def lower_forehead_width
245
+ priorize(attribute: "lower_forehead_width")
246
+ end
247
+
248
+ # The size of the left ear (small, normal, big)
249
+ def left_ear_size
250
+ priorize(attribute: "left_ear_size")
251
+ end
252
+
253
+ # The size of the right ear (small, normal, big)
254
+ def right_ear_size
255
+ priorize(attribute: "right_ear_size")
256
+ end
257
+
258
+ # Vertical ear difference (left higher, equal, right higher)
259
+ # def ears_diff
260
+ # rate raw["VerticalEarDiff"]
261
+ # end
262
+
263
+ # Distance from average left ear center to chin (small, average, large)
264
+ def left_ear_chin_axis
265
+ priorize(attribute: "left_ear_chin_axis", left:1, front:2, right:3)
266
+ end
267
+
268
+ # Distance from average right ear center to chin (small, average, large)
269
+ def right_ear_chin_axis
270
+ priorize(attribute: "right_ear_chin_axis", right:1, front:2, left:3)
271
+ end
272
+
273
+ # Distance from average left ear center to lower chin (small, average, large)
274
+ def left_ear_lower_chin_axis
275
+ priorize(attribute: "left_ear_lower_chin_axis", left:1, front:2, right:3)
276
+ end
277
+
278
+ # Distance from average right ear center to lower chin (small, average, large)
279
+ def right_ear_lower_chin_axis
280
+ priorize(attribute: "right_ear_lower_chin_axis", right:1, front:2, left:3)
281
+ end
282
+
283
+ # Distance from average left ear center to nose upper bridge (small, average, large)
284
+ def left_ear_nosebridge_axis
285
+ priorize(attribute: "left_ear_nosebridge_axis", left:1, front:2, right:3)
286
+ end
287
+
288
+ # istance from average right ear center to nose upper bridge (small, average, large)
289
+ def right_ear_nosebridge_axis
290
+ priorize(attribute: "right_ear_nosebridge_axis", right:1, front:2, left:3)
291
+ end
292
+
293
+ # Distance from average left ear center to nose root (small, average, large)
294
+ def left_ear_noseroot_axis
295
+ priorize(attribute: "left_ear_noseroot_axis", left:1, front:2, right:3)
296
+ end
297
+
298
+ # Distance from average right ear center to nose root (small, average, large)
299
+ def right_ear_noseroot_axis
300
+ priorize(attribute: "right_ear_noseroot_axis", right:1, front:2, left:3)
301
+ end
302
+
303
+ # Distance from average left ear center to the forehead (small, average, large)
304
+ def forehead_protruding
305
+ priorize(attribute: "forehead_protruding", right:1, left:2, front:3)
306
+ end
307
+
308
+ # Left ear distance from head (small, average, large)
309
+ def left_ear_protruding
310
+ priorize(attribute: "left_ear_protruding")
311
+ end
312
+
313
+ # Chin wrinkle (visible, not visible) inclusive certainty [0.0, 1.0]
314
+ def chin_wrinkles
315
+ priorize(attribute: "chin_wrinkles")
316
+ end
317
+
318
+ # Facial lines as exposed by laughing or in the case of wrinkles (yes, no)
319
+ def wrinkles
320
+ priorize(attribute: "wrinkles")
321
+ end
322
+
323
+ # Crow features near the outside of the eyes (yes, no) inclusive certainty [0.0, 1.0]
324
+ def crow_feet
325
+ priorize(attribute: "crow_feet")
326
+ end
327
+
328
+ # Iris (upward, normal, downward) inclusive certainty [0.0, 1.0]
329
+ def iris_orientation
330
+ priorize(attribute: "iris_orientation")
331
+ end
332
+
333
+ # Presence of eye circles below the eye location (no, light, heavy)
334
+ def eyecircles
335
+ priorize(attribute: "eyecircles")
336
+ end
337
+
338
+ def nosetip_orientation
339
+ priorize(attribute: "nosetip_orientation")
340
+ end
341
+
342
+ def left_forehead_protruding
343
+ priorize(attribute: "left_forehead_protruding", left:1, front:2, right:3)
344
+ end
345
+
346
+ def right_forehead_protruding
347
+ priorize(attribute: "right_forehead_protruding", right:3, front:2, left:1)
348
+ end
349
+
350
+ def right_ear_protruding
351
+ priorize(attribute: "right_ear_protruding", right:3, front:2, left:1)
352
+ end
353
+
354
+ def ear_noseroot_axis
355
+ priorize(attribute: "ear_noseroot_axis", left:1, front:2, right:1)
356
+ end
357
+
358
+ def eye_size
359
+ priorize(attribute: "ear_noseroot_axis")
360
+ end
361
+
362
+ def cheek_width
363
+ priorize(attribute: "ear_noseroot_axis")
364
+ end
365
+
366
+ def ear_size
367
+ priorize(attribute: "ear_size")
368
+ end
369
+
370
+ def ear_chin_axis
371
+ priorize(attribute: "ear_size")
372
+ end
373
+
374
+ def ear_lower_chin_axis
375
+ priorize(attribute: "ear_size")
376
+ end
377
+
378
+ def ear_nosebridge_axis
379
+ priorize(attribute: "ear_nosebridge_axis")
380
+ end
381
+
382
+ def ear_protruding
383
+ priorize(attribute: "ear_protruding")
384
+ end
385
+
386
+
387
+ private
388
+
389
+ # priorize the features by estimated view logically, secondly on the certainty of the given attribute
390
+ # def priorize(*args)
391
+ # prios = {front: 1, left: 2, right: 2, up: 3, down:3}
392
+ # @prios = prios.update(args.extract_options!)
393
+ # raise StandardError.new "missing param :attribute" if !prios.has_key?(:attribute)
394
+ # self.sort_by!{ |fs| [@prios[fs.view], (fs.send(@prios[:attribute])[:certainty] rescue 1) ] }
395
+ # self.first.send(@prios[:attribute]) rescue nil
396
+ # end
397
+
398
+ def priorize(*args)
399
+ @prios = {front: 1, left: 2, right: 2, up: 3, down:3}.update(args.extract_options!)
400
+ raise Karolinska::Error::Base.new "missing param :attribute" if !@prios.has_key?(:attribute)
401
+ method = @prios.delete(:attribute)
402
+ sorted_views = Hash[@prios.sort_by{ |_, v| -v }.reverse].keys
403
+ sorted_views = sorted_views & views
404
+
405
+ feature_values = sorted_views.collect do |view|
406
+ self.send(view).send(method)
407
+ end
408
+
409
+ feature_values.compact.first
410
+
411
+ end
412
+
413
+
414
+ end
415
+ end
@@ -0,0 +1,71 @@
1
+ module Karolinska
2
+ class SingleRequest
3
+
4
+ include BenchmarkMethods
5
+
6
+ benchmark :karolinska_request
7
+
8
+ require 'net/http/post/multipart'
9
+
10
+
11
+ ENDPOINT = 'http://92.111.221.231:85/Fid.aspx'
12
+ PASSWORD = 'Karolinska'
13
+
14
+
15
+ def initialize(path, view)
16
+ @path = path
17
+ @view = case view
18
+ when :front
19
+ "Frontal"
20
+ when :left
21
+ "Left"
22
+ when :right
23
+ "Right"
24
+ when :up
25
+ "Upward"
26
+ when :down
27
+ "Downward"
28
+ end
29
+ end
30
+
31
+ def start
32
+ url = URI.parse ENDPOINT
33
+ request = Net::HTTP::Post::Multipart.new url.path,
34
+ "file" => UploadIO.new(@path, "image/jpeg", "image.jpg"),
35
+ "Sid" => PASSWORD,
36
+ "View" => @view
37
+
38
+ json, status = karolinska_request(request)
39
+ # Rails.logger.debug json
40
+ return [json, status]
41
+ end
42
+
43
+ private
44
+
45
+ def karolinska_request(request)
46
+ url = URI.parse ENDPOINT
47
+ response = Net::HTTP.start(url.host, url.port) do |http|
48
+ http.request(request)
49
+ end
50
+ rescue Timeout::Error => error
51
+ Rails.logger.error "#{error}".red
52
+ [nil, error]
53
+ else
54
+
55
+ case response
56
+ when Net::HTTPOK
57
+ unless response.body.blank?
58
+ [JSON.parse(response.body), 200]
59
+ else
60
+ Rails.logger.error "Karolinska returned an empty string".red
61
+ [{}, "Empty String"]
62
+ end
63
+ when Net::HTTPClientError, Net::HTTPInternalServerError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError
64
+ msg = "#{response.class}: #{response.code}, #{response.message}"
65
+ Rails.logger.error msg.red
66
+ [{}, msg]
67
+ end
68
+
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,53 @@
1
+ module Karolinska
2
+ module TestSet
3
+
4
+ # usage: Karolinska::TestSet.test_sander
5
+
6
+ FILESLOC = "./test/fixtures/files"
7
+
8
+ class Person < Struct.new(:name, :res)
9
+
10
+ def paths
11
+ {
12
+ front: "#{FILESLOC}/#{name}/#{res}/front.jpg",
13
+ left: "#{FILESLOC}/#{name}/#{res}/left.jpg",
14
+ right: "#{FILESLOC}/#{name}/#{res}/right.jpg",
15
+ up: "#{FILESLOC}/#{name}/#{res}/up.jpg",
16
+ down: "#{FILESLOC}/#{name}/#{res}/down.jpg"
17
+ }
18
+ end
19
+
20
+ end
21
+
22
+ # usage: Karolinska::TestSet.test_peter
23
+ def self.test_peter
24
+ paths = Person.new("peter", "highres").paths
25
+ paths.delete(:up)
26
+ paths.delete(:down)
27
+ Karolinska::Scan.new(paths).connect
28
+ end
29
+
30
+ # usage: Karolinska::TestSet.test_sander
31
+ def self.test_sander
32
+ Karolinska::Scan.new(Person.new("sander", "lowres").paths).connect
33
+ end
34
+
35
+ # usage: Karolinska::TestSet.test_monique
36
+ def self.test_monique
37
+ Karolinska::Scan.new(Person.new("monique", "lowerres").paths).connect(true)
38
+ end
39
+
40
+ def self.test_marcus
41
+ Karolinska::Scan.new(Person.new("marcus", "lowerres").paths).connect
42
+ end
43
+
44
+ def self.test_marieke
45
+ Karolinska::Scan.new(Person.new("marieke", "lowerres").paths).connect
46
+ end
47
+
48
+ def self.test_chris
49
+ Karolinska::Scan.new(Person.new("chris", "lowres").paths).connect
50
+ end
51
+
52
+ end
53
+ end
@@ -1,3 +1,3 @@
1
1
  module Karolinska
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.2"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: karolinska
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sebastian Arcila Valenzuela
@@ -69,6 +69,13 @@ files:
69
69
  - bin/setup
70
70
  - karolinska.gemspec
71
71
  - lib/karolinska.rb
72
+ - lib/karolinska/cluster_request.rb
73
+ - lib/karolinska/error.rb
74
+ - lib/karolinska/fake.rb
75
+ - lib/karolinska/feature_set.rb
76
+ - lib/karolinska/feature_sets.rb
77
+ - lib/karolinska/single_request.rb
78
+ - lib/karolinska/test_set.rb
72
79
  - lib/karolinska/version.rb
73
80
  homepage: http://your.mom
74
81
  licenses: []