• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

paulmthompson / Whisker-Analysis / 23715729675

29 Mar 2026 06:13PM UTC coverage: 77.986%. Remained the same
23715729675

push

github

paulmthompson
Merge branch 'main' of https://github.com/paulmthompson/Whisker-Analysis

284 of 310 new or added lines in 5 files covered. (91.61%)

100 existing lines in 3 files now uncovered.

1495 of 1917 relevant lines covered (77.99%)

59536782.71 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

40.56
/src/WhiskerTracking/whiskertracker.cpp
1
#include "whiskertracker.hpp"
2

3
#include "JaneliaWhiskerTracker/io.hpp"
4
#include "Geometry/lines.hpp"
5

6
#include <omp.h>
7

8
#include <algorithm>
9
#include <chrono>
10
#include <cmath>
11
#include <iostream>
12
#include <numeric>
13

14
thread_local janelia::JaneliaTracker whisker::WhiskerTracker::_janelia;
151✔
15
thread_local bool whisker::WhiskerTracker::_janelia_init;
16

17
namespace whisker {
18

19
janelia::Image<uint8_t> bg = janelia::Image<uint8_t>(640, 480, std::vector<uint8_t>(640 * 480, 0));
20

21
WhiskerTracker::WhiskerTracker()
5✔
22
{
23
    _janelia = janelia::JaneliaTracker();
5✔
24
    _janelia_init = false;
5✔
25
}
5✔
26

27
std::vector<std::vector<Line2D>> WhiskerTracker::trace_multiple_images(const std::vector<std::vector<uint8_t>> & images, const int image_height, const int image_width) {
3✔
28

29
    std::vector<std::vector<Line2D>> whiskers(images.size());
3✔
30

31
    //#ifndef _MSC_VER
32
    #pragma omp parallel
3✔
33
    {
34
        _reinitializeJanelia();
35
    }
36
    //#endif
37

38
    //#ifndef _MSC_VER
39
    #pragma omp parallel for
3✔
40
    //#endif
41
    for (int i = 0; i < static_cast<int>(images.size()); i++) {
42
        whiskers[i] = trace(images[i], image_height, image_width);
43
    }
44

45
    return whiskers;
3✔
46
}
47

NEW
48
std::vector<std::vector<Line2D>> WhiskerTracker::trace_multiple_images_with_masks(const std::vector<std::vector<uint8_t>> & images,
×
49
                                                                                  const std::vector<std::vector<uint8_t>> & masks,
50
                                                                                  const int image_height,
51
                                                                                  const int image_width) {
52

NEW
53
    if (images.size() != masks.size()) {
×
NEW
54
        throw std::runtime_error("Number of images and masks must be the same");
×
55
    }
56

NEW
57
    std::vector<std::vector<Line2D>> whiskers(images.size());
×
58

59
    //#ifndef _MSC_VER
NEW
60
    #pragma omp parallel
×
61
    {
62
        _reinitializeJanelia();
63
    }
64
   // #endif
65

66
    //#ifndef _MSC_VER
NEW
67
    #pragma omp parallel for
×
68
   // #endif
69
    for (int i = 0; i < static_cast<int>(images.size()); i++) {
70
        whiskers[i] = trace_with_mask(images[i], masks[i], image_height, image_width);
71
    }
72

NEW
73
    return whiskers;
×
74
}
75

76
std::vector<Line2D> WhiskerTracker::trace(const std::vector<uint8_t> & image, const int image_height, const int image_width) {
113✔
77

78
    _reinitializeJanelia();
113✔
79

80
    std::vector<Line2D> whiskers{};
113✔
81

82
    auto t0 = std::chrono::high_resolution_clock::now();
113✔
83

84
    auto img = janelia::Image<uint8_t>(image_width, image_height, image);
113✔
85

86
    auto t1 = std::chrono::high_resolution_clock::now();
113✔
87

88
    auto j_segs = _janelia.find_segments(1, img, bg);
113✔
89

90
    auto t2 = std::chrono::high_resolution_clock::now();
113✔
91

92
    for (auto &w_seg: j_segs) {
3,065✔
93
        auto whisker = create_line(w_seg.x, w_seg.y);
2,952✔
94
        if (length(whisker) > _whisker_length_threshold) {
2,952✔
95
            whiskers.push_back(std::move(whisker));
2,952✔
96
        }
97
    }
2,952✔
98

99
    auto t3 = std::chrono::high_resolution_clock::now();
113✔
100

101
    remove_duplicates(whiskers);
113✔
102
    std::ranges::for_each(whiskers, [wp=_whisker_pad](Line2D & w)
226✔
103
    {align_whisker_to_follicle(w, wp);});
787✔
104

105
    auto t4 = std::chrono::high_resolution_clock::now();
113✔
106

107
    _connectToFaceMask(whiskers);
113✔
108

109
    auto t5 = std::chrono::high_resolution_clock::now();
113✔
110

111
    remove_whiskers_outside_radius(whiskers, _whisker_pad, _whisker_pad_radius);
113✔
112

113
    auto t6 = std::chrono::high_resolution_clock::now();
113✔
114

115
    order_whiskers(whiskers, _head_direction_vector);
113✔
116

117
    auto t7 = std::chrono::high_resolution_clock::now();
113✔
118

119
    if (_verbose) {
113✔
UNCOV
120
        std::cout << "Image conversion: " << std::chrono::duration_cast<std::chrono::milliseconds>(t1 - t0).count() << "ms" << std::endl;
×
UNCOV
121
        std::cout << "Janelia find segments: " << std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count() << "ms" << std::endl;
×
UNCOV
122
        std::cout << "Create whiskers: " << std::chrono::duration_cast<std::chrono::milliseconds>(t3 - t2).count() << "ms" << std::endl;
×
UNCOV
123
        std::cout << "Remove duplicates: " << std::chrono::duration_cast<std::chrono::milliseconds>(t4 - t3).count() << "ms" << std::endl;
×
UNCOV
124
        std::cout << "Connect to face mask: " << std::chrono::duration_cast<std::chrono::milliseconds>(t5 - t4).count() << "ms" << std::endl;
×
UNCOV
125
        std::cout << "Remove whiskers by whisker pad radius: " << std::chrono::duration_cast<std::chrono::milliseconds>(t6 - t5).count() << "ms" << std::endl;
×
UNCOV
126
        std::cout << "Order whiskers: " << std::chrono::duration_cast<std::chrono::milliseconds>(t7 - t6).count() << "ms" << std::endl;
×
127
    }
128

129
    return whiskers;
226✔
130
}
113✔
131

132
std::vector<Line2D> WhiskerTracker::trace_with_mask(const std::vector<uint8_t> & image, const std::vector<uint8_t> & mask, const int image_height, const int image_width) {
1✔
133

134
    _reinitializeJanelia();
1✔
135

136
    std::vector<Line2D> whiskers{};
1✔
137

138
    auto t0 = std::chrono::high_resolution_clock::now();
1✔
139

140
    auto img = janelia::Image<uint8_t>(image_width, image_height, image);
1✔
141
    auto mask_img = janelia::Image<uint8_t>(image_width, image_height, mask);
1✔
142

143
    auto t1 = std::chrono::high_resolution_clock::now();
1✔
144

145
    auto j_segs = _janelia.find_segments_from_mask(1, img, mask_img);
1✔
146

147
    auto t2 = std::chrono::high_resolution_clock::now();
1✔
148

149
    for (auto &w_seg: j_segs) {
6✔
150
        auto whisker = create_line(w_seg.x, w_seg.y);
5✔
151
        if (length(whisker) > _whisker_length_threshold) {
5✔
152
            whiskers.push_back(std::move(whisker));
5✔
153
        }
154
    }
5✔
155

156
    auto t3 = std::chrono::high_resolution_clock::now();
1✔
157

158
    remove_duplicates(whiskers);
1✔
159
    std::ranges::for_each(whiskers, [wp=_whisker_pad](Line2D & w)
2✔
160
    {align_whisker_to_follicle(w, wp);});
5✔
161

162
    auto t4 = std::chrono::high_resolution_clock::now();
1✔
163

164
    _connectToFaceMask(whiskers);
1✔
165

166
    auto t5 = std::chrono::high_resolution_clock::now();
1✔
167

168
    remove_whiskers_outside_radius(whiskers, _whisker_pad, _whisker_pad_radius);
1✔
169

170
    auto t6 = std::chrono::high_resolution_clock::now();
1✔
171

172
    order_whiskers(whiskers, _head_direction_vector);
1✔
173

174
    auto t7 = std::chrono::high_resolution_clock::now();
1✔
175

176
    if (_verbose) {
1✔
NEW
177
        std::cout << "Image conversion: " << std::chrono::duration_cast<std::chrono::milliseconds>(t1 - t0).count() << "ms" << std::endl;
×
NEW
178
        std::cout << "Janelia find segments from mask: " << std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count() << "ms" << std::endl;
×
NEW
179
        std::cout << "Create whiskers: " << std::chrono::duration_cast<std::chrono::milliseconds>(t3 - t2).count() << "ms" << std::endl;
×
NEW
180
        std::cout << "Remove duplicates: " << std::chrono::duration_cast<std::chrono::milliseconds>(t4 - t3).count() << "ms" << std::endl;
×
NEW
181
        std::cout << "Connect to face mask: " << std::chrono::duration_cast<std::chrono::milliseconds>(t5 - t4).count() << "ms" << std::endl;
×
NEW
182
        std::cout << "Remove whiskers by whisker pad radius: " << std::chrono::duration_cast<std::chrono::milliseconds>(t6 - t5).count() << "ms" << std::endl;
×
NEW
183
        std::cout << "Order whiskers: " << std::chrono::duration_cast<std::chrono::milliseconds>(t7 - t6).count() << "ms" << std::endl;
×
184
    }
185

186
    return whiskers;
2✔
187
}
1✔
188

UNCOV
189
std::map<int, std::vector<Line2D>> load_janelia_whiskers(std::string const & filename) {
×
UNCOV
190
    auto j_segs = janelia::load_binary_data(filename);
×
191

UNCOV
192
    auto output_whiskers = std::map<int, std::vector<Line2D>>();
×
193

UNCOV
194
    for (auto const & w_seg: j_segs) {
×
195

UNCOV
196
        if (!output_whiskers.contains(w_seg.time)) { // Key doesn't exist
×
UNCOV
197
            output_whiskers[w_seg.time] = std::vector<Line2D>();
×
198
        }
199

UNCOV
200
        output_whiskers[w_seg.time].push_back(create_line(w_seg.x, w_seg.y));
×
201

202
    }
203

UNCOV
204
    return output_whiskers;
×
205
}
×
206

207
void WhiskerTracker::setHeadDirection(float x, float y)
×
208
{
209
    _head_direction_vector = GeomVector{x,y};
×
210
    _head_direction_vector = normalize(_head_direction_vector);
×
211
}
×
212

UNCOV
213
void WhiskerTracker::changeJaneliaParameter(JaneliaParameter parameter, float value) {
×
UNCOV
214
    switch (parameter) {
×
UNCOV
215
        case SEED_ON_GRID_LATTICE_SPACING: {
×
UNCOV
216
            _janelia.config._lattice_spacing = static_cast<int>(value);
×
UNCOV
217
            break;
×
218
        }
UNCOV
219
        case SEED_SIZE_PX: {
×
UNCOV
220
            _janelia.config._maxr = static_cast<int>(value);
×
UNCOV
221
            break;
×
222
        }
UNCOV
223
        case SEED_ITERATIONS: {
×
UNCOV
224
            _janelia.config._maxiter = static_cast<int>(value);
×
UNCOV
225
            break;
×
226
        }
UNCOV
227
        case SEED_ITERATION_THRESH: {
×
UNCOV
228
            _janelia.config._iteration_thres = value;
×
UNCOV
229
            break;
×
230
        }
UNCOV
231
        case SEED_ACCUM_THRESH: {
×
UNCOV
232
            _janelia.config._accum_thres = value;
×
UNCOV
233
            break;
×
234
        }
UNCOV
235
        case SEED_THRESH: {
×
UNCOV
236
            _janelia.config._accum_thres = value;
×
UNCOV
237
            break;
×
238
        }
UNCOV
239
        case HAT_RADIUS: {
×
240

241
        }
UNCOV
242
        case MIN_LEVEL: {
×
243

244
        }
UNCOV
245
        case MIN_SIZE: {
×
246

247
        }
UNCOV
248
        case TLEN: {
×
UNCOV
249
            _janelia.config._tlen = value;
×
UNCOV
250
            _reinitializeJanelia();
×
UNCOV
251
            break;
×
252
        }
UNCOV
253
        case OFFSET_STEP: {
×
UNCOV
254
            _janelia.config._offset_step = value;
×
UNCOV
255
            _reinitializeJanelia();
×
UNCOV
256
            break;
×
257
        }
UNCOV
258
        case ANGLE_STEP: {
×
UNCOV
259
            _janelia.config._angle_step = value;
×
UNCOV
260
            _reinitializeJanelia();
×
UNCOV
261
            break;
×
262
        }
263
        case WIDTH_STEP: {
×
264
            _janelia.config._width_step = value;
×
265
            _reinitializeJanelia();
×
266
        }
267
        case WIDTH_MIN: {
×
268
            // Must be multiple of width step
UNCOV
269
            _janelia.config._width_min = value;
×
UNCOV
270
            _reinitializeJanelia();
×
UNCOV
271
            break;
×
272
        }
UNCOV
273
        case WIDTH_MAX: {
×
274
            _janelia.config._width_max = value;
×
275
            _reinitializeJanelia();
×
UNCOV
276
            break;
×
277
        }
UNCOV
278
        case MIN_SIGNAL: {
×
279
            _janelia.config._min_signal = value;
×
UNCOV
280
            _reinitializeJanelia();
×
281
            break;
×
282
        }
UNCOV
283
        case MAX_DELTA_ANGLE: {
×
UNCOV
284
            _janelia.config._max_delta_angle = value;
×
285
            _reinitializeJanelia();
×
UNCOV
286
            break;
×
287
        }
UNCOV
288
        case MAX_DELTA_WIDTH: {
×
289
            _janelia.config._max_delta_width = value;
×
290
            _reinitializeJanelia();
×
UNCOV
291
            break;
×
292
        }
UNCOV
293
        case MAX_DELTA_OFFSET: {
×
294
            _janelia.config._max_delta_offset = value;
×
295
            _reinitializeJanelia();
×
296
            break;
×
297
        }
298
        case HALF_SPACE_ASSYMETRY_THRESH: {
×
299
            _janelia.config._half_space_assymetry = value;
×
300
            _reinitializeJanelia();
×
301
            break;
×
302
        }
UNCOV
303
        case HALF_SPACE_TUNNELING_MAX_MOVES: {
×
304
            _janelia.config._half_space_tunneling_max_moves = value;
×
305
            _reinitializeJanelia();
×
306
            break;
×
307
        }
308
    }
309
}
×
310

311
void WhiskerTracker::_reinitializeJanelia() {
126✔
312
    if (_janelia_init == false) {
126✔
313
        _janelia.bank = janelia::LineDetector(_janelia.config);
8✔
314
        _janelia.half_space_bank = janelia::HalfSpaceDetector(_janelia.config);
8✔
315
        _janelia_init = true;
8✔
316
    }
317
}
126✔
318

319
void WhiskerTracker::_connectToFaceMask(std::vector<Line2D> & whiskers)
114✔
320
{
321
    if (_face_mask.empty()) {
114✔
322
        return;
323
    }
324

UNCOV
325
    for (auto &w: whiskers) {
×
326

327
        whisker::extend_line_to_mask(w, _face_mask_set, _image_width, _image_height);
×
328
    }
329
}
330

331
} // namespace whisker
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc