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

BlueBrain / MorphIO / 6533620342

16 Oct 2023 12:32PM UTC coverage: 76.051% (-0.05%) from 76.104%
6533620342

Pull #476

github

mgeplf
should apt-get update first
Pull Request #476: Efficient swc build

278 of 278 new or added lines in 4 files covered. (100.0%)

1972 of 2593 relevant lines covered (76.05%)

903.23 hits per line

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

81.43
/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 <cassert>
7
#include <cctype>    // std::tolower
8
#include <iterator>  // std::back_inserter
9
#include <sstream>
10
#include <string>
11

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

20
#include "../shared_utils.hpp"
21

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

25
void insertSectionsBeforeSection(std::vector<SectionP>& sections_to_update,
770✔
26
                                 const std::vector<SectionP>& sections,
27
                                 const SectionP& target_section) {
28
    auto is_equal = [&target_section](const SectionP& section) {
2,734✔
29
        return (section->id() == target_section->id()) && section->hasSameShape(*target_section);
982✔
30
    };
770✔
31

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

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

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

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

50

51
namespace morphio {
52
namespace mut {
53

54
using morphio::readers::ErrorMessages;
55
Morphology::Morphology(const std::string& uri, unsigned int options)
56✔
56
    : Morphology(morphio::Morphology(uri, options)) {}
56✔
57

58
Morphology::Morphology(const HighFive::Group& group, unsigned int options)
10✔
59
    : Morphology(morphio::Morphology(group, options)) {}
10✔
60

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

70
    for (const std::shared_ptr<MitoSection>& root : morphology.mitochondria().rootSections()) {
×
71
        mitochondria().appendRootSection(root, true);
×
72
    }
73

74
    applyModifiers(options);
×
75
}
×
76

77
Morphology::Morphology(const morphio::Morphology& morphology, unsigned int options)
70✔
78
    : _soma(std::make_shared<Soma>(morphology.soma()))
70✔
79
    , _cellProperties(
80
          std::make_shared<morphio::Property::CellLevel>(morphology.properties_->_cellLevel))
70✔
81
    , _endoplasmicReticulum(morphology.endoplasmicReticulum())
70✔
82
    , _dendriticSpineLevel(morphology.properties_->_dendriticSpineLevel) {
210✔
83
    for (const morphio::Section& root : morphology.rootSections()) {
174✔
84
        appendRootSection(root, true);
104✔
85
    }
86

87
    for (const morphio::MitoSection& root : morphology.mitochondria().rootSections()) {
114✔
88
        mitochondria().appendRootSection(root, true);
44✔
89
    }
90

91
    applyModifiers(options);
70✔
92
}
70✔
93

94
/**
95
   Return false if there is no duplicate point
96
 **/
97
bool _checkDuplicatePoint(const SectionP& parent, const SectionP& current) {
476✔
98
    // Weird edge case where parent is empty: skipping it
99
    if (parent->points().empty()) {
476✔
100
        return true;
×
101
    } else if (current->points().empty()) {
476✔
102
        return false;
×
103
    } else if (parent->points().back() != current->points().front()) {
476✔
104
        return false;
8✔
105
    }
106

107
    // // As perimeter is optional, it must either be defined for parent and
108
    // current
109
    // // or not be defined at all
110
    // if(parent->perimeters().empty() != current->perimeters().empty())
111
    //     return false;
112

113
    // if(!parent->perimeters().empty() &&
114
    //    parent->perimeters()[parent->perimeters().size()-1] !=
115
    //    current->perimeters()[0])
116
    //     return false;
117

118
    return true;
468✔
119
}
120

121
std::shared_ptr<Section> Morphology::appendRootSection(const morphio::Section& section_,
104✔
122
                                                       bool recursive) {
123
    std::shared_ptr<Section> ptr(new Section(this, _counter, section_));
104✔
124
    _register(ptr);
104✔
125
    _rootSections.push_back(ptr);
104✔
126

127
    const bool emptySection = ptr->points().empty();
104✔
128
    if (emptySection) {
104✔
129
        printError(Warning::APPENDING_EMPTY_SECTION, _err.WARNING_APPENDING_EMPTY_SECTION(ptr));
×
130
    }
131

132
    if (recursive) {
104✔
133
        for (const auto& child : section_.children()) {
256✔
134
            ptr->appendSection(child, true);
152✔
135
        }
136
    }
137

138
    return ptr;
104✔
139
}
140

141
std::shared_ptr<Section> Morphology::appendRootSection(const std::shared_ptr<Section>& section_,
×
142
                                                       bool recursive) {
143
    std::shared_ptr<Section> section_copy(new Section(this, _counter, *section_));
×
144
    _register(section_copy);
×
145
    _rootSections.push_back(section_copy);
×
146
    const bool emptySection = section_copy->points().empty();
×
147
    if (emptySection) {
×
148
        printError(Warning::APPENDING_EMPTY_SECTION,
×
149
                   _err.WARNING_APPENDING_EMPTY_SECTION(section_copy));
×
150
    }
151

152
    if (recursive) {
×
153
        for (const auto& child : section_->children()) {
×
154
            section_copy->appendSection(child, true);
×
155
        }
156
    }
157

158
    return section_copy;
×
159
}
160

161
std::shared_ptr<Section> Morphology::appendRootSection(const Property::PointLevel& pointProperties,
206✔
162
                                                       SectionType type) {
163
    std::shared_ptr<Section> ptr(new Section(this, _counter, type, pointProperties));
206✔
164
    _register(ptr);
206✔
165
    _rootSections.push_back(ptr);
206✔
166

167
    bool emptySection = ptr->points().empty();
206✔
168
    if (emptySection) {
206✔
169
        printError(Warning::APPENDING_EMPTY_SECTION, _err.WARNING_APPENDING_EMPTY_SECTION(ptr));
×
170
    }
171

172
    return ptr;
206✔
173
}
174

175
uint32_t Morphology::_register(const std::shared_ptr<Section>& section_) {
770✔
176
    if (_sections.count(section_->id()) > 0) {
770✔
177
        throw SectionBuilderError("Section already exists");
×
178
    }
179
    _counter = std::max(_counter, section_->id()) + 1;
770✔
180
    _sections[section_->id()] = section_;
770✔
181
    return section_->id();
770✔
182
}
183

184
Morphology::~Morphology() {
186✔
185
    auto roots = _rootSections;  // Need to iterate on a copy
372✔
186
    for (const auto& root : roots) {
498✔
187
        deleteSection(root, true);
312✔
188
    }
189
}
186✔
190

191
void Morphology::eraseByValue(std::vector<std::shared_ptr<Section>>& vec,
770✔
192
                              const std::shared_ptr<Section> section) {
193
    if (section->morphology_ == this) {
770✔
194
        section->morphology_ = nullptr;
770✔
195
        section->id_ = 0xffffffff;
770✔
196
    }
197
    vec.erase(std::remove(vec.begin(), vec.end(), section), vec.end());
770✔
198
}
770✔
199

200

201
void Morphology::deleteSection(const std::shared_ptr<Section> section_, bool recursive) {
1,082✔
202
    if (!section_) {
1,082✔
203
        return;
×
204
    }
205

206
    if (recursive) {
1,082✔
207
        // The deletion must start by the furthest leaves, otherwise you may cut
208
        // the topology and forget to remove some sections
209
        std::vector<std::shared_ptr<Section>> ids;
624✔
210
        std::copy(section_->breadth_begin(), breadth_end(), std::back_inserter(ids));
312✔
211

212
        for (auto it = ids.rbegin(); it != ids.rend(); ++it) {
1,072✔
213
            deleteSection(*it, false);
760✔
214
        }
215
    } else {
216
        const auto section_id = section_->id();
770✔
217

218
        // Careful not to use a reference here or you will face reference invalidation problem
219
        // with vector resize
220
        const auto children = section_->children();
1,540✔
221

222
        if (section_->isRoot()) {
770✔
223
            // put root section's children in its place
224
            insertSectionsBeforeSection(_rootSections, children, section_);
314✔
225
            eraseByValue(_rootSections, section_);
314✔
226
        } else {
227
            // set grandparent as section children's parent
228
            for (const auto& child : children) {
460✔
229
                _parent[child->id()] = _parent[section_id];
4✔
230
            }
231

232
            auto& children_of_parent = _children[_parent[section_id]];
456✔
233

234
            // put grandchildren at the position of the deleted section
235
            insertSectionsBeforeSection(children_of_parent, children, section_);
456✔
236
            eraseByValue(children_of_parent, section_);
456✔
237
        }
238

239
        // remove section id from connectivity and section lists
240
        _children.erase(section_id);
770✔
241
        _parent.erase(section_id);
770✔
242
        _sections.erase(section_id);
770✔
243
    }
244
}
245

246
void Morphology::removeUnifurcations() {
4✔
247
    removeUnifurcations(morphio::readers::DebugInfo());
4✔
248
}
4✔
249

250
void Morphology::removeUnifurcations(const morphio::readers::DebugInfo& debugInfo) {
4✔
251
    morphio::readers::ErrorMessages err(debugInfo._filename);
8✔
252

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

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

261
        if (section_->isRoot()) {
12✔
262
            continue;
4✔
263
        }
264

265
        unsigned int parentId = section_->parent()->id();
8✔
266

267
        if (!ErrorMessages::isIgnored(Warning::WRONG_DUPLICATE) &&
16✔
268
            !_checkDuplicatePoint(section_->parent(), section_)) {
8✔
269
            printError(Warning::WRONG_DUPLICATE,
×
270
                       err.WARNING_WRONG_DUPLICATE(section_, section_->parent()));
×
271
        }
272

273
        auto parent = section_->parent();
16✔
274
        bool isUnifurcation = parent->children().size() == 1;
8✔
275

276
        // This "if" condition ensures that "unifurcations" (ie. successive
277
        // sections with only 1 child) get merged together into a bigger section
278
        if (isUnifurcation) {
8✔
279
            printError(Warning::ONLY_CHILD, err.WARNING_ONLY_CHILD(debugInfo, parentId, sectionId));
8✔
280
            bool duplicate = _checkDuplicatePoint(section_->parent(), section_);
8✔
281

282
            addAnnotation(morphio::Property::Annotation(morphio::AnnotationType::SINGLE_CHILD,
16✔
283
                                                        sectionId,
284
                                                        section_->properties(),
8✔
285
                                                        "",
286
                                                        debugInfo.getLineNumber(parentId)));
287

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

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

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

298
            deleteSection(section_, false);
8✔
299
        }
300
    }
301
}
4✔
302

303
Property::Properties Morphology::buildReadOnly() const {
110✔
304
    int sectionIdOnDisk = 0;
110✔
305
    std::map<uint32_t, int32_t> newIds;
220✔
306
    Property::Properties properties{};
110✔
307

308
    properties._cellLevel = *_cellProperties;
110✔
309
    properties._cellLevel._somaType = _soma->type();
110✔
310
    appendProperties(properties._somaLevel, _soma->point_properties_);
110✔
311

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

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

324
    mitochondria()._buildMitochondria(properties);
110✔
325
    properties._endoplasmicReticulumLevel = endoplasmicReticulum().buildReadOnly();
110✔
326
    properties._dendriticSpineLevel = _dendriticSpineLevel;
110✔
327

328
    return properties;
220✔
329
}
330

331
depth_iterator Morphology::depth_begin() const {
120✔
332
    return depth_iterator(*this);
120✔
333
}
334

335
depth_iterator Morphology::depth_end() const {
720✔
336
    return depth_iterator();
720✔
337
}
338

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

343
breadth_iterator Morphology::breadth_end() const {
312✔
344
    return breadth_iterator();
312✔
345
}
346

347
void Morphology::applyModifiers(unsigned int modifierFlags) {
172✔
348
    if (modifierFlags & NO_DUPLICATES & TWO_POINTS_SECTIONS) {
349
        throw SectionBuilderError(
350
            _err.ERROR_UNCOMPATIBLE_FLAGS(NO_DUPLICATES, TWO_POINTS_SECTIONS));
351
    }
352

353
    if (modifierFlags & SOMA_SPHERE) {
172✔
354
        modifiers::soma_sphere(*this);
×
355
    }
356

357
    if (modifierFlags & NO_DUPLICATES) {
172✔
358
        modifiers::no_duplicate_point(*this);
×
359
    }
360

361
    if (modifierFlags & TWO_POINTS_SECTIONS) {
172✔
362
        modifiers::two_points_sections(*this);
×
363
    }
364

365
    if (modifierFlags & NRN_ORDER) {
172✔
366
        modifiers::nrn_order(*this);
6✔
367
    }
368
}
172✔
369

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

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

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

389
    return connectivity;
2✔
390
}
391

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

398
    std::string extension;
12✔
399
    for (char c : filename.substr(pos)) {
28✔
400
        extension += static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
22✔
401
    }
402

403
    if (extension == ".h5") {
6✔
404
        writer::h5(*this, filename);
2✔
405
    } else if (extension == ".asc") {
4✔
406
        writer::asc(*this, filename);
2✔
407
    } else if (extension == ".swc") {
2✔
408
        writer::swc(*this, filename);
2✔
409
    } else {
410
        throw UnknownFileType(_err.ERROR_WRONG_EXTENSION(filename));
×
411
    }
412
}
6✔
413

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