karolinska 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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: []