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

BlueBrain / MorphIO / 11254817533

09 Oct 2024 12:20PM UTC coverage: 77.697% (+0.08%) from 77.615%
11254817533

Pull #476

github

mgeplf
Merge remote-tracking branch 'origin/master' into efficient-swc-build
Pull Request #476: Efficient swc build

286 of 321 new or added lines in 10 files covered. (89.1%)

9 existing lines in 4 files now uncovered.

2125 of 2735 relevant lines covered (77.7%)

901.01 hits per line

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

80.95
/src/mut/morphology.cpp
1
/* Copyright (c) 2013-2023, EPFL/Blue Brain Project
2
 *
3
 * SPDX-License-Identifier: Apache-2.0
4
 */
5
#include <algorithm>  // std::find_if
6
#include <cctype>     // std::tolower
7
#include <memory>
8
#include <string>
9

10
#include <morphio/endoplasmic_reticulum.h>
11
#include <morphio/mitochondria.h>
12
#include <morphio/mut/endoplasmic_reticulum.h>
13
#include <morphio/mut/morphology.h>
14
#include <morphio/mut/section.h>
15
#include <morphio/mut/writers.h>
16
#include <morphio/soma.h>
17

18
#include "../error_message_generation.h"
19
#include "../shared_utils.hpp"
20

21
namespace {
22
using SectionP = std::shared_ptr<morphio::mut::Section>;
23

24
void insertSectionsBeforeSection(std::vector<SectionP>& sections_to_update,
888✔
25
                                 const std::vector<SectionP>& sections,
26
                                 const SectionP& target_section) {
27
    auto is_equal = [&target_section](const SectionP& section) {
3,144✔
28
        return (section->id() == target_section->id()) && section->hasSameShape(*target_section);
1,128✔
29
    };
888✔
30

31
    const auto it = std::find_if(sections_to_update.begin(), sections_to_update.end(), is_equal);
888✔
32

33
    // Note that the section with section_id is not removed at this step
34
    sections_to_update.insert(it, sections.begin(), sections.end());
888✔
35
}
888✔
36

37
void appendProperties(morphio::Property::PointLevel& to,
766✔
38
                      const morphio::Property::PointLevel& from,
39
                      int offset = 0) {
40
    morphio::_appendVector(to._points, from._points, offset);
766✔
41
    morphio::_appendVector(to._diameters, from._diameters, offset);
766✔
42

43
    if (!from._perimeters.empty()) {
766✔
44
        morphio::_appendVector(to._perimeters, from._perimeters, offset);
2✔
45
    }
46
}
766✔
47
}  // namespace
48

49
namespace morphio {
50
namespace mut {
51

52
Morphology::Morphology(const std::string& uri,
62✔
53
                       unsigned int options,
54
                       std::shared_ptr<WarningHandler> warning_handler)
62✔
55
    : Morphology(morphio::Morphology(uri, options, warning_handler)) {}
62✔
56

57
Morphology::Morphology(const HighFive::Group& group,
10✔
58
                       unsigned int options,
59
                       std::shared_ptr<WarningHandler> warning_handler)
10✔
60
    : Morphology(morphio::Morphology(group, options, warning_handler)) {}
10✔
61

62
Morphology::Morphology(const morphio::mut::Morphology& morphology,
×
63
                       unsigned int options,
64
                       std::shared_ptr<WarningHandler> warning_handler)
×
65
    : _soma(std::make_shared<Soma>(*morphology.soma()))
×
66
    , _cellProperties(std::make_shared<morphio::Property::CellLevel>(*morphology._cellProperties))
×
67
    , _endoplasmicReticulum(morphology.endoplasmicReticulum())
68
    , _dendriticSpineLevel(morphology._dendriticSpineLevel)
×
69
    , _handler(warning_handler ? warning_handler : morphio::getWarningHandler()) {
×
70
    for (const std::shared_ptr<Section>& root : morphology.rootSections()) {
×
71
        appendRootSection(root, true);
×
72
    }
73

74
    for (const std::shared_ptr<MitoSection>& root : morphology.mitochondria().rootSections()) {
×
75
        mitochondria().appendRootSection(root, true);
×
76
    }
77

78
    applyModifiers(options);
×
79
}
×
80

81
Morphology::Morphology(const morphio::Morphology& morphology,
76✔
82
                       unsigned int options,
83
                       std::shared_ptr<WarningHandler> warning_handler)
76✔
84
    : _soma(std::make_shared<Soma>(morphology.soma()))
76✔
85
    , _cellProperties(
86
          std::make_shared<morphio::Property::CellLevel>(morphology.properties_->_cellLevel))
76✔
87
    , _endoplasmicReticulum(morphology.endoplasmicReticulum())
76✔
88
    , _dendriticSpineLevel(morphology.properties_->_dendriticSpineLevel)
152✔
89
    , _handler(warning_handler != nullptr ? warning_handler : morphio::getWarningHandler()) {
228✔
90
    for (const morphio::Section& root : morphology.rootSections()) {
196✔
91
        appendRootSection(root, true);
120✔
92
    }
93

94
    for (const morphio::MitoSection& root : morphology.mitochondria().rootSections()) {
120✔
95
        mitochondria().appendRootSection(root, true);
44✔
96
    }
97

98
    applyModifiers(options);
76✔
99
}
76✔
100

101
/**
102
   Return false if there is no duplicate point
103
 **/
104
bool _checkDuplicatePoint(const SectionP& parent, const SectionP& current) {
526✔
105
    // Weird edge case where parent is empty: skipping it
106
    if (parent->points().empty()) {
526✔
107
        return true;
×
108
    } else if (current->points().empty()) {
526✔
109
        return false;
×
110
    } else if (parent->points().back() != current->points().front()) {
526✔
111
        return false;
8✔
112
    }
113

114
    // // As perimeter is optional, it must either be defined for parent and
115
    // current
116
    // // or not be defined at all
117
    // if(parent->perimeters().empty() != current->perimeters().empty())
118
    //     return false;
119

120
    // if(!parent->perimeters().empty() &&
121
    //    parent->perimeters()[parent->perimeters().size()-1] !=
122
    //    current->perimeters()[0])
123
    //     return false;
124

125
    return true;
518✔
126
}
127

128
std::shared_ptr<Section> Morphology::appendRootSection(const morphio::Section& section_,
120✔
129
                                                       bool recursive) {
130
    std::shared_ptr<Section> ptr(new Section(this, _counter, section_));
120✔
131
    _register(ptr);
120✔
132
    _rootSections.push_back(ptr);
120✔
133

134
    const bool emptySection = ptr->points().empty();
120✔
135
    if (emptySection) {
120✔
136
        getWarningHandler()->emit(std::make_shared<AppendingEmptySection>(_uri, ptr->id()));
×
137
    }
138

139
    if (recursive) {
120✔
140
        for (const auto& child : section_.children()) {
288✔
141
            ptr->appendSection(child, true);
168✔
142
        }
143
    }
144

145
    return ptr;
120✔
146
}
147

148
std::shared_ptr<Section> Morphology::appendRootSection(const std::shared_ptr<Section>& section_,
×
149
                                                       bool recursive) {
150
    std::shared_ptr<Section> section_copy(new Section(this, _counter, *section_));
×
151
    _register(section_copy);
×
152
    _rootSections.push_back(section_copy);
×
153
    const bool emptySection = section_copy->points().empty();
×
154
    if (emptySection) {
×
155
        getWarningHandler()->emit(
×
156
            std::make_shared<AppendingEmptySection>(_uri, section_copy->id()));
×
157
    }
158

159
    if (recursive) {
×
160
        for (const auto& child : section_->children()) {
×
161
            section_copy->appendSection(child, true);
×
162
        }
163
    }
164

165
    return section_copy;
×
166
}
167

168
std::shared_ptr<Section> Morphology::appendRootSection(const Property::PointLevel& pointProperties,
250✔
169
                                                       SectionType type) {
170
    std::shared_ptr<Section> ptr(new Section(this, _counter, type, pointProperties));
250✔
171
    _register(ptr);
250✔
172
    _rootSections.push_back(ptr);
250✔
173

174
    bool emptySection = ptr->points().empty();
250✔
175
    if (emptySection) {
250✔
UNCOV
176
        getWarningHandler()->emit(std::make_shared<AppendingEmptySection>(_uri, ptr->id()));
×
177
    }
178

179
    return ptr;
250✔
180
}
181

182
uint32_t Morphology::_register(const std::shared_ptr<Section>& section_) {
888✔
183
    if (_sections.count(section_->id()) > 0) {
888✔
184
        throw SectionBuilderError("Section already exists");
×
185
    }
186
    _counter = std::max(_counter, section_->id()) + 1;
888✔
187
    _sections[section_->id()] = section_;
888✔
188
    return section_->id();
888✔
189
}
190

191
Morphology::~Morphology() {
230✔
192
    auto roots = _rootSections;  // Need to iterate on a copy
460✔
193
    for (const auto& root : roots) {
602✔
194
        deleteSection(root, true);
372✔
195
    }
196
}
230✔
197

198
void Morphology::eraseByValue(std::vector<std::shared_ptr<Section>>& vec,
888✔
199
                              const std::shared_ptr<Section>& section) {
200
    if (section->morphology_ == this) {
888✔
201
        section->morphology_ = nullptr;
888✔
202
        section->id_ = 0xffffffff;
888✔
203
    }
204
    vec.erase(std::remove(vec.begin(), vec.end(), section), vec.end());
888✔
205
}
888✔
206

207

208
void Morphology::deleteSection(const std::shared_ptr<Section> section_, bool recursive) {
1,260✔
209
    if (!section_) {
1,260✔
210
        return;
×
211
    }
212

213
    if (recursive) {
1,260✔
214
        // The deletion must start by the furthest leaves, otherwise you may cut
215
        // the topology and forget to remove some sections
216
        std::vector<std::shared_ptr<Section>> ids;
744✔
217
        std::copy(section_->breadth_begin(), breadth_end(), std::back_inserter(ids));
372✔
218

219
        for (auto it = ids.rbegin(); it != ids.rend(); ++it) {
1,250✔
220
            deleteSection(*it, false);
878✔
221
        }
222
    } else {
223
        const auto section_id = section_->id();
888✔
224

225
        // Careful not to use a reference here or you will face reference invalidation problem
226
        // with vector resize
227
        const auto children = section_->children();
1,776✔
228

229
        if (section_->isRoot()) {
888✔
230
            // put root section's children in its place
231
            insertSectionsBeforeSection(_rootSections, children, section_);
374✔
232
            eraseByValue(_rootSections, section_);
374✔
233
        } else {
234
            // set grandparent as section children's parent
235
            for (const auto& child : children) {
518✔
236
                _parent[child->id()] = _parent[section_id];
4✔
237
            }
238

239
            auto& children_of_parent = _children[_parent[section_id]];
514✔
240

241
            // put grandchildren at the position of the deleted section
242
            insertSectionsBeforeSection(children_of_parent, children, section_);
514✔
243
            eraseByValue(children_of_parent, section_);
514✔
244
        }
245

246
        // remove section id from connectivity and section lists
247
        _children.erase(section_id);
888✔
248
        _parent.erase(section_id);
888✔
249
        _sections.erase(section_id);
888✔
250
    }
251
}
252

253
void Morphology::removeUnifurcations() {
4✔
254
    morphio::details::ErrorMessages err;
8✔
255

256
    auto it = depth_begin();
8✔
257
    while (it != depth_end()) {
16✔
258
        std::shared_ptr<Section> section_ = *it;
12✔
259

260
        // Incrementing iterator here before we potentially delete the section
261
        ++it;
12✔
262
        unsigned int sectionId = section_->id();
12✔
263

264
        if (section_->isRoot()) {
12✔
265
            continue;
4✔
266
        }
267

268
        const auto& parent = section_->parent();
8✔
269
        bool duplicate = _checkDuplicatePoint(parent, section_);
8✔
270

271
        if (!duplicate) {
8✔
272
            getWarningHandler()->emit(std::make_shared<WrongDuplicate>(_uri, section_, parent));
×
273
        }
274

275
        bool isUnifurcation = parent->children().size() == 1;
8✔
276

277
        // This "if" condition ensures that "unifurcations" (ie. successive
278
        // sections with only 1 child) get merged together into a bigger section
279
        if (isUnifurcation) {
8✔
280
            getWarningHandler()->emit(std::make_shared<OnlyChild>(_uri, parent->id(), sectionId));
8✔
281

282
            addAnnotation(Property::Annotation(AnnotationType::SINGLE_CHILD,
16✔
283
                                               sectionId,
284
                                               section_->properties(),
8✔
285
                                               "SingleChild",
286
                                               -1));
287

288
            _appendVector(parent->points(), section_->points(), duplicate ? 1 : 0);
8✔
289

290
            _appendVector(parent->diameters(), section_->diameters(), duplicate ? 1 : 0);
8✔
291

292
            if (!parent->perimeters().empty()) {
8✔
293
                _appendVector(parent->perimeters(), section_->perimeters(), duplicate ? 1 : 0);
×
294
            }
295

296
            deleteSection(section_, false);
8✔
297
        }
298
    }
299
}
4✔
300

301
Property::Properties Morphology::buildReadOnly() const {
130✔
302
    int sectionIdOnDisk = 0;
130✔
303
    std::map<uint32_t, int32_t> newIds;
260✔
304
    Property::Properties properties{};
130✔
305

306
    properties._cellLevel = *_cellProperties;
130✔
307
    properties._cellLevel._somaType = _soma->type();
130✔
308
    appendProperties(properties._somaLevel, _soma->point_properties_);
130✔
309

310
    for (auto it = depth_begin(); it != depth_end(); ++it) {
766✔
311
        const std::shared_ptr<Section>& section = *it;
1,272✔
312
        unsigned int sectionId = section->id();
636✔
313
        int parentOnDisk = (section->isRoot() ? -1 : newIds[section->parent()->id()]);
636✔
314

315
        auto start = static_cast<int>(properties._pointLevel._points.size());
636✔
316
        properties._sectionLevel._sections.push_back({start, parentOnDisk});
636✔
317
        properties._sectionLevel._sectionTypes.push_back(section->type());
636✔
318
        newIds[sectionId] = sectionIdOnDisk++;
636✔
319
        appendProperties(properties._pointLevel, section->point_properties_);
636✔
320
    }
321

322
    mitochondria()._buildMitochondria(properties);
130✔
323
    properties._endoplasmicReticulumLevel = endoplasmicReticulum().buildReadOnly();
130✔
324
    properties._dendriticSpineLevel = _dendriticSpineLevel;
130✔
325

326
    return properties;
260✔
327
}
328

329
depth_iterator Morphology::depth_begin() const {
148✔
330
    return depth_iterator(*this);
148✔
331
}
332

333
depth_iterator Morphology::depth_end() const {
838✔
334
    return depth_iterator();
838✔
335
}
336

337
breadth_iterator Morphology::breadth_begin() const {
×
338
    return breadth_iterator(*this);
×
339
}
340

341
breadth_iterator Morphology::breadth_end() const {
372✔
342
    return breadth_iterator();
372✔
343
}
344

345
void Morphology::applyModifiers(unsigned int modifierFlags) {
198✔
346
    if (modifierFlags & NO_DUPLICATES & TWO_POINTS_SECTIONS) {
347
        const auto err = details::ErrorMessages(_uri);
348
        throw SectionBuilderError(err.ERROR_UNCOMPATIBLE_FLAGS(NO_DUPLICATES, TWO_POINTS_SECTIONS));
349
    }
350

351
    if (modifierFlags & SOMA_SPHERE) {
198✔
352
        modifiers::soma_sphere(*this);
×
353
    }
354

355
    if (modifierFlags & NO_DUPLICATES) {
198✔
356
        modifiers::no_duplicate_point(*this);
×
357
    }
358

359
    if (modifierFlags & TWO_POINTS_SECTIONS) {
198✔
360
        modifiers::two_points_sections(*this);
×
361
    }
362

363
    if (modifierFlags & NRN_ORDER) {
198✔
364
        modifiers::nrn_order(*this);
6✔
365
    }
366
}
198✔
367

368
std::unordered_map<int, std::vector<unsigned int>> Morphology::connectivity() {
2✔
369
    std::unordered_map<int, std::vector<unsigned int>> connectivity;
2✔
370

371
    const auto& roots = rootSections();
2✔
372
    connectivity[-1].reserve(roots.size());
2✔
373
    std::transform(roots.begin(),
374
                   roots.end(),
375
                   std::back_inserter(connectivity[-1]),
2✔
376
                   [](const std::shared_ptr<Section>& section) { return section->id(); });
6✔
377

378
    for (const auto& kv : _children) {
6✔
379
        auto& nodeEdges = connectivity[static_cast<int>(kv.first)];
4✔
380
        nodeEdges.reserve(kv.second.size());
4✔
381
        std::transform(kv.second.begin(),
382
                       kv.second.end(),
383
                       std::back_inserter(nodeEdges),
384
                       [](const std::shared_ptr<Section>& section) { return section->id(); });
12✔
385
    }
386

387
    return connectivity;
2✔
388
}
389

390
void Morphology::write(const std::string& filename) const {
24✔
391
    const size_t pos = filename.find_last_of('.');
24✔
392
    if (pos == std::string::npos) {
24✔
393
        throw UnknownFileType("Missing file extension.");
×
394
    }
395

396
    std::string extension;
48✔
397
    for (char c : filename.substr(pos)) {
118✔
398
        extension += static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
94✔
399
    }
400

401
    if (extension == ".h5") {
24✔
402
        writer::h5(*this, filename, _handler);
2✔
403
    } else if (extension == ".asc") {
22✔
404
        writer::asc(*this, filename, _handler);
2✔
405
    } else if (extension == ".swc") {
20✔
406
        writer::swc(*this, filename, _handler);
28✔
407
    } else {
408
        const auto err = details::ErrorMessages(_uri);
×
409
        throw UnknownFileType(err.ERROR_WRONG_EXTENSION(filename));
×
410
    }
411
}
16✔
412

413
}  // end namespace mut
414
}  // end namespace morphio
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