comparison transform/FeatureExtractionModelTransformer.cpp @ 1365:3382d914e110

Merge from branch 3.0-integration
author Chris Cannam
date Fri, 13 Jan 2017 10:29:44 +0000
parents a99641535e02
children d163b04c3ec4
comparison
equal deleted inserted replaced
1272:6a7ea3bd0e10 1365:3382d914e110
14 */ 14 */
15 15
16 #include "FeatureExtractionModelTransformer.h" 16 #include "FeatureExtractionModelTransformer.h"
17 17
18 #include "plugin/FeatureExtractionPluginFactory.h" 18 #include "plugin/FeatureExtractionPluginFactory.h"
19
19 #include "plugin/PluginXml.h" 20 #include "plugin/PluginXml.h"
20 #include <vamp-hostsdk/Plugin.h> 21 #include <vamp-hostsdk/Plugin.h>
21 22
22 #include "data/model/Model.h" 23 #include "data/model/Model.h"
23 #include "base/Window.h" 24 #include "base/Window.h"
40 #include <QSettings> 41 #include <QSettings>
41 42
42 FeatureExtractionModelTransformer::FeatureExtractionModelTransformer(Input in, 43 FeatureExtractionModelTransformer::FeatureExtractionModelTransformer(Input in,
43 const Transform &transform) : 44 const Transform &transform) :
44 ModelTransformer(in, transform), 45 ModelTransformer(in, transform),
45 m_plugin(0) 46 m_plugin(0),
47 m_haveOutputs(false)
46 { 48 {
47 SVDEBUG << "FeatureExtractionModelTransformer::FeatureExtractionModelTransformer: plugin " << m_transforms.begin()->getPluginIdentifier() << ", outputName " << m_transforms.begin()->getOutput() << endl; 49 SVDEBUG << "FeatureExtractionModelTransformer::FeatureExtractionModelTransformer: plugin " << m_transforms.begin()->getPluginIdentifier() << ", outputName " << m_transforms.begin()->getOutput() << endl;
48
49 initialise();
50 } 50 }
51 51
52 FeatureExtractionModelTransformer::FeatureExtractionModelTransformer(Input in, 52 FeatureExtractionModelTransformer::FeatureExtractionModelTransformer(Input in,
53 const Transforms &transforms) : 53 const Transforms &transforms) :
54 ModelTransformer(in, transforms), 54 ModelTransformer(in, transforms),
55 m_plugin(0) 55 m_plugin(0),
56 m_haveOutputs(false)
56 { 57 {
57 if (m_transforms.empty()) { 58 if (m_transforms.empty()) {
58 SVDEBUG << "FeatureExtractionModelTransformer::FeatureExtractionModelTransformer: " << transforms.size() << " transform(s)" << endl; 59 SVDEBUG << "FeatureExtractionModelTransformer::FeatureExtractionModelTransformer: " << transforms.size() << " transform(s)" << endl;
59 } else { 60 } else {
60 SVDEBUG << "FeatureExtractionModelTransformer::FeatureExtractionModelTransformer: " << transforms.size() << " transform(s), first has plugin " << m_transforms.begin()->getPluginIdentifier() << ", outputName " << m_transforms.begin()->getOutput() << endl; 61 SVDEBUG << "FeatureExtractionModelTransformer::FeatureExtractionModelTransformer: " << transforms.size() << " transform(s), first has plugin " << m_transforms.begin()->getPluginIdentifier() << ", outputName " << m_transforms.begin()->getOutput() << endl;
61 } 62 }
62
63 initialise();
64 } 63 }
65 64
66 static bool 65 static bool
67 areTransformsSimilar(const Transform &t1, const Transform &t2) 66 areTransformsSimilar(const Transform &t1, const Transform &t2)
68 { 67 {
72 } 71 }
73 72
74 bool 73 bool
75 FeatureExtractionModelTransformer::initialise() 74 FeatureExtractionModelTransformer::initialise()
76 { 75 {
76 // This is (now) called from the run thread. The plugin is
77 // constructed, initialised, used, and destroyed all from a single
78 // thread.
79
77 // All transforms must use the same plugin, parameters, and 80 // All transforms must use the same plugin, parameters, and
78 // inputs: they can differ only in choice of plugin output. So we 81 // inputs: they can differ only in choice of plugin output. So we
79 // initialise based purely on the first transform in the list (but 82 // initialise based purely on the first transform in the list (but
80 // first check that they are actually similar as promised) 83 // first check that they are actually similar as promised)
81 84
89 Transform primaryTransform = m_transforms[0]; 92 Transform primaryTransform = m_transforms[0];
90 93
91 QString pluginId = primaryTransform.getPluginIdentifier(); 94 QString pluginId = primaryTransform.getPluginIdentifier();
92 95
93 FeatureExtractionPluginFactory *factory = 96 FeatureExtractionPluginFactory *factory =
94 FeatureExtractionPluginFactory::instanceFor(pluginId); 97 FeatureExtractionPluginFactory::instance();
95 98
96 if (!factory) { 99 if (!factory) {
97 m_message = tr("No factory available for feature extraction plugin id \"%1\" (unknown plugin type, or internal error?)").arg(pluginId); 100 m_message = tr("No factory available for feature extraction plugin id \"%1\" (unknown plugin type, or internal error?)").arg(pluginId);
98 return false; 101 return false;
99 } 102 }
102 if (!input) { 105 if (!input) {
103 m_message = tr("Input model for feature extraction plugin \"%1\" is of wrong type (internal error?)").arg(pluginId); 106 m_message = tr("Input model for feature extraction plugin \"%1\" is of wrong type (internal error?)").arg(pluginId);
104 return false; 107 return false;
105 } 108 }
106 109
110 SVDEBUG << "FeatureExtractionModelTransformer: Instantiating plugin for transform in thread "
111 << QThread::currentThreadId() << endl;
112
107 m_plugin = factory->instantiatePlugin(pluginId, input->getSampleRate()); 113 m_plugin = factory->instantiatePlugin(pluginId, input->getSampleRate());
108 if (!m_plugin) { 114 if (!m_plugin) {
109 m_message = tr("Failed to instantiate plugin \"%1\"").arg(pluginId); 115 m_message = tr("Failed to instantiate plugin \"%1\"").arg(pluginId);
110 return false; 116 return false;
111 } 117 }
128 .arg(input->getChannelCount()); 134 .arg(input->getChannelCount());
129 return false; 135 return false;
130 } 136 }
131 137
132 SVDEBUG << "Initialising feature extraction plugin with channels = " 138 SVDEBUG << "Initialising feature extraction plugin with channels = "
133 << channelCount << ", step = " << primaryTransform.getStepSize() 139 << channelCount << ", step = " << primaryTransform.getStepSize()
134 << ", block = " << primaryTransform.getBlockSize() << endl; 140 << ", block = " << primaryTransform.getBlockSize() << endl;
135 141
136 if (!m_plugin->initialise(channelCount, 142 if (!m_plugin->initialise(channelCount,
137 primaryTransform.getStepSize(), 143 primaryTransform.getStepSize(),
138 primaryTransform.getBlockSize())) { 144 primaryTransform.getBlockSize())) {
139 145
140 int pstep = primaryTransform.getStepSize(); 146 int pstep = primaryTransform.getStepSize();
141 int pblock = primaryTransform.getBlockSize(); 147 int pblock = primaryTransform.getBlockSize();
142 148
143 ///!!! hang on, this isn't right -- we're modifying a copy 149 ///!!! hang on, this isn't right -- we're modifying a copy
144 primaryTransform.setStepSize(0); 150 primaryTransform.setStepSize(0);
146 TransformFactory::getInstance()->makeContextConsistentWithPlugin 152 TransformFactory::getInstance()->makeContextConsistentWithPlugin
147 (primaryTransform, m_plugin); 153 (primaryTransform, m_plugin);
148 154
149 if (primaryTransform.getStepSize() != pstep || 155 if (primaryTransform.getStepSize() != pstep ||
150 primaryTransform.getBlockSize() != pblock) { 156 primaryTransform.getBlockSize() != pblock) {
157
158 SVDEBUG << "Initialisation failed, trying again with default step = "
159 << primaryTransform.getStepSize()
160 << ", block = " << primaryTransform.getBlockSize() << endl;
151 161
152 if (!m_plugin->initialise(channelCount, 162 if (!m_plugin->initialise(channelCount,
153 primaryTransform.getStepSize(), 163 primaryTransform.getStepSize(),
154 primaryTransform.getBlockSize())) { 164 primaryTransform.getBlockSize())) {
155 165
166 SVDEBUG << "Initialisation failed again" << endl;
167
156 m_message = tr("Failed to initialise feature extraction plugin \"%1\"").arg(pluginId); 168 m_message = tr("Failed to initialise feature extraction plugin \"%1\"").arg(pluginId);
157 return false; 169 return false;
158 170
159 } else { 171 } else {
160 172
173 SVDEBUG << "Initialisation succeeded this time" << endl;
174
161 m_message = tr("Feature extraction plugin \"%1\" rejected the given step and block sizes (%2 and %3); using plugin defaults (%4 and %5) instead") 175 m_message = tr("Feature extraction plugin \"%1\" rejected the given step and block sizes (%2 and %3); using plugin defaults (%4 and %5) instead")
162 .arg(pluginId) 176 .arg(pluginId)
163 .arg(pstep) 177 .arg(pstep)
164 .arg(pblock) 178 .arg(pblock)
165 .arg(primaryTransform.getStepSize()) 179 .arg(primaryTransform.getStepSize())
166 .arg(primaryTransform.getBlockSize()); 180 .arg(primaryTransform.getBlockSize());
167 } 181 }
168 182
169 } else { 183 } else {
170 184
185 SVDEBUG << "Initialisation failed" << endl;
186
171 m_message = tr("Failed to initialise feature extraction plugin \"%1\"").arg(pluginId); 187 m_message = tr("Failed to initialise feature extraction plugin \"%1\"").arg(pluginId);
172 return false; 188 return false;
173 } 189 }
190 } else {
191 SVDEBUG << "Initialisation succeeded" << endl;
174 } 192 }
175 193
176 if (primaryTransform.getPluginVersion() != "") { 194 if (primaryTransform.getPluginVersion() != "") {
177 QString pv = QString("%1").arg(m_plugin->getPluginVersion()); 195 QString pv = QString("%1").arg(m_plugin->getPluginVersion());
178 if (pv != primaryTransform.getPluginVersion()) { 196 if (pv != primaryTransform.getPluginVersion()) {
218 236
219 for (int j = 0; j < (int)m_transforms.size(); ++j) { 237 for (int j = 0; j < (int)m_transforms.size(); ++j) {
220 createOutputModels(j); 238 createOutputModels(j);
221 } 239 }
222 240
241 m_outputMutex.lock();
242 m_haveOutputs = true;
243 m_outputsCondition.wakeAll();
244 m_outputMutex.unlock();
245
223 return true; 246 return true;
247 }
248
249 void
250 FeatureExtractionModelTransformer::deinitialise()
251 {
252 SVDEBUG << "FeatureExtractionModelTransformer: deleting plugin for transform in thread "
253 << QThread::currentThreadId() << endl;
254
255 delete m_plugin;
256 for (int j = 0; j < (int)m_descriptors.size(); ++j) {
257 delete m_descriptors[j];
258 }
224 } 259 }
225 260
226 void 261 void
227 FeatureExtractionModelTransformer::createOutputModels(int n) 262 FeatureExtractionModelTransformer::createOutputModels(int n)
228 { 263 {
229 DenseTimeValueModel *input = getConformingInput(); 264 DenseTimeValueModel *input = getConformingInput();
230
231 // cerr << "FeatureExtractionModelTransformer::createOutputModel: sample type " << m_descriptor->sampleType << ", rate " << m_descriptor->sampleRate << endl;
232 265
233 PluginRDFDescription description(m_transforms[n].getPluginIdentifier()); 266 PluginRDFDescription description(m_transforms[n].getPluginIdentifier());
234 QString outputId = m_transforms[n].getOutput(); 267 QString outputId = m_transforms[n].getOutput();
235 268
236 int binCount = 1; 269 int binCount = 1;
252 maxValue = m_descriptors[n]->maxValue; 285 maxValue = m_descriptors[n]->maxValue;
253 haveExtents = true; 286 haveExtents = true;
254 } 287 }
255 288
256 sv_samplerate_t modelRate = input->getSampleRate(); 289 sv_samplerate_t modelRate = input->getSampleRate();
290 sv_samplerate_t outputRate = modelRate;
257 int modelResolution = 1; 291 int modelResolution = 1;
258 292
259 if (m_descriptors[n]->sampleType != 293 if (m_descriptors[n]->sampleType !=
260 Vamp::Plugin::OutputDescriptor::OneSamplePerStep) { 294 Vamp::Plugin::OutputDescriptor::OneSamplePerStep) {
261 if (m_descriptors[n]->sampleRate > input->getSampleRate()) { 295
262 cerr << "WARNING: plugin reports output sample rate as " 296 outputRate = m_descriptors[n]->sampleRate;
263 << m_descriptors[n]->sampleRate << " (can't display features with finer resolution than the input rate of " << input->getSampleRate() << ")" << endl; 297
264 }
265 }
266
267 switch (m_descriptors[n]->sampleType) {
268
269 case Vamp::Plugin::OutputDescriptor::VariableSampleRate:
270 if (m_descriptors[n]->sampleRate != 0.0) {
271 modelResolution = int(round(modelRate / m_descriptors[n]->sampleRate));
272 }
273 break;
274
275 case Vamp::Plugin::OutputDescriptor::OneSamplePerStep:
276 modelResolution = m_transforms[n].getStepSize();
277 break;
278
279 case Vamp::Plugin::OutputDescriptor::FixedSampleRate:
280 //!!! SV doesn't actually support display of models that have 298 //!!! SV doesn't actually support display of models that have
281 //!!! different underlying rates together -- so we always set 299 //!!! different underlying rates together -- so we always set
282 //!!! the model rate to be the input model's rate, and adjust 300 //!!! the model rate to be the input model's rate, and adjust
283 //!!! the resolution appropriately. We can't properly display 301 //!!! the resolution appropriately. We can't properly display
284 //!!! data with a higher resolution than the base model at all 302 //!!! data with a higher resolution than the base model at all
285 if (m_descriptors[n]->sampleRate > input->getSampleRate()) { 303 if (outputRate > input->getSampleRate()) {
286 modelResolution = 1; 304 SVDEBUG << "WARNING: plugin reports output sample rate as "
287 } else if (m_descriptors[n]->sampleRate <= 0.0) { 305 << outputRate
288 cerr << "WARNING: Fixed sample-rate plugin reports invalid sample rate " << m_descriptors[n]->sampleRate << "; defaulting to input rate of " << input->getSampleRate() << endl; 306 << " (can't display features with finer resolution than the input rate of "
307 << modelRate << ")" << endl;
308 outputRate = modelRate;
309 }
310 }
311
312 switch (m_descriptors[n]->sampleType) {
313
314 case Vamp::Plugin::OutputDescriptor::VariableSampleRate:
315 if (outputRate != 0.0) {
316 modelResolution = int(round(modelRate / outputRate));
317 }
318 break;
319
320 case Vamp::Plugin::OutputDescriptor::OneSamplePerStep:
321 modelResolution = m_transforms[n].getStepSize();
322 break;
323
324 case Vamp::Plugin::OutputDescriptor::FixedSampleRate:
325 if (outputRate <= 0.0) {
326 SVDEBUG << "WARNING: Fixed sample-rate plugin reports invalid sample rate " << m_descriptors[n]->sampleRate << "; defaulting to input rate of " << input->getSampleRate() << endl;
289 modelResolution = 1; 327 modelResolution = 1;
290 } else { 328 } else {
291 modelResolution = int(round(modelRate / m_descriptors[n]->sampleRate)); 329 modelResolution = int(round(modelRate / outputRate));
330 // cerr << "modelRate = " << modelRate << ", descriptor rate = " << outputRate << ", modelResolution = " << modelResolution << endl;
292 } 331 }
293 break; 332 break;
294 } 333 }
295 334
296 bool preDurationPlugin = (m_plugin->getVampApiVersion() < 2); 335 bool preDurationPlugin = (m_plugin->getVampApiVersion() < 2);
477 out->setSourceModel(input); 516 out->setSourceModel(input);
478 m_outputs.push_back(out); 517 m_outputs.push_back(out);
479 } 518 }
480 } 519 }
481 520
521 void
522 FeatureExtractionModelTransformer::awaitOutputModels()
523 {
524 m_outputMutex.lock();
525 while (!m_haveOutputs) {
526 m_outputsCondition.wait(&m_outputMutex);
527 }
528 m_outputMutex.unlock();
529 }
530
482 FeatureExtractionModelTransformer::~FeatureExtractionModelTransformer() 531 FeatureExtractionModelTransformer::~FeatureExtractionModelTransformer()
483 { 532 {
484 // SVDEBUG << "FeatureExtractionModelTransformer::~FeatureExtractionModelTransformer()" << endl; 533 // Parent class dtor set the abandoned flag and waited for the run
485 delete m_plugin; 534 // thread to exit; the run thread owns the plugin, and should have
486 for (int j = 0; j < (int)m_descriptors.size(); ++j) { 535 // destroyed it before exiting (via a call to deinitialise)
487 delete m_descriptors[j];
488 }
489 } 536 }
490 537
491 FeatureExtractionModelTransformer::Models 538 FeatureExtractionModelTransformer::Models
492 FeatureExtractionModelTransformer::getAdditionalOutputModels() 539 FeatureExtractionModelTransformer::getAdditionalOutputModels()
493 { 540 {
564 } 611 }
565 612
566 void 613 void
567 FeatureExtractionModelTransformer::run() 614 FeatureExtractionModelTransformer::run()
568 { 615 {
616 initialise();
617
569 DenseTimeValueModel *input = getConformingInput(); 618 DenseTimeValueModel *input = getConformingInput();
570 if (!input) return; 619 if (!input) return;
571 620
572 if (m_outputs.empty()) return; 621 if (m_outputs.empty()) return;
573 622
604 (getConformingInput(), 653 (getConformingInput(),
605 channelCount == 1 ? m_input.getChannel() : ch, 654 channelCount == 1 ? m_input.getChannel() : ch,
606 primaryTransform.getWindowType(), 655 primaryTransform.getWindowType(),
607 blockSize, 656 blockSize,
608 stepSize, 657 stepSize,
609 blockSize, 658 blockSize);
610 false,
611 StorageAdviser::PrecisionCritical);
612 if (!model->isOK() || model->getError() != "") { 659 if (!model->isOK() || model->getError() != "") {
613 QString err = model->getError(); 660 QString err = model->getError();
614 delete model; 661 delete model;
615 for (int j = 0; j < (int)m_outputNos.size(); ++j) { 662 for (int j = 0; j < (int)m_outputNos.size(); ++j) {
616 setCompletion(j, 100); 663 setCompletion(j, 100);
617 } 664 }
618 //!!! need a better way to handle this -- previously we were using a QMessageBox but that isn't an appropriate thing to do here either 665 //!!! need a better way to handle this -- previously we were using a QMessageBox but that isn't an appropriate thing to do here either
619 throw AllocationFailed("Failed to create the FFT model for this feature extraction model transformer: error is: " + err); 666 throw AllocationFailed("Failed to create the FFT model for this feature extraction model transformer: error is: " + err);
620 } 667 }
621 model->resume();
622 fftModels.push_back(model); 668 fftModels.push_back(model);
623 cerr << "created model for channel " << ch << endl; 669 cerr << "created model for channel " << ch << endl;
624 } 670 }
625 } 671 }
626 672
698 buffers[ch][i*2+1] = 0.f; 744 buffers[ch][i*2+1] = 0.f;
699 } 745 }
700 } 746 }
701 error = fftModels[ch]->getError(); 747 error = fftModels[ch]->getError();
702 if (error != "") { 748 if (error != "") {
703 cerr << "FeatureExtractionModelTransformer::run: Abandoning, error is " << error << endl; 749 SVDEBUG << "FeatureExtractionModelTransformer::run: Abandoning, error is " << error << endl;
704 m_abandoned = true; 750 m_abandoned = true;
705 m_message = error; 751 m_message = error;
706 break; 752 break;
707 } 753 }
708 } 754 }
759 805
760 for (int ch = 0; ch < channelCount; ++ch) { 806 for (int ch = 0; ch < channelCount; ++ch) {
761 delete[] buffers[ch]; 807 delete[] buffers[ch];
762 } 808 }
763 delete[] buffers; 809 delete[] buffers;
810
811 deinitialise();
764 } 812 }
765 813
766 void 814 void
767 FeatureExtractionModelTransformer::getFrames(int channelCount, 815 FeatureExtractionModelTransformer::getFrames(int channelCount,
768 sv_frame_t startFrame, 816 sv_frame_t startFrame,
788 836
789 sv_frame_t got = 0; 837 sv_frame_t got = 0;
790 838
791 if (channelCount == 1) { 839 if (channelCount == 1) {
792 840
793 got = input->getData(m_input.getChannel(), startFrame, size, 841 auto data = input->getData(m_input.getChannel(), startFrame, size);
794 buffers[0] + offset); 842 got = data.size();
843
844 copy(data.begin(), data.end(), buffers[0] + offset);
795 845
796 if (m_input.getChannel() == -1 && input->getChannelCount() > 1) { 846 if (m_input.getChannel() == -1 && input->getChannelCount() > 1) {
797 // use mean instead of sum, as plugin input 847 // use mean instead of sum, as plugin input
798 float cc = float(input->getChannelCount()); 848 float cc = float(input->getChannelCount());
799 for (sv_frame_t i = 0; i < size; ++i) { 849 for (sv_frame_t i = 0; i < got; ++i) {
800 buffers[0][i + offset] /= cc; 850 buffers[0][i + offset] /= cc;
801 } 851 }
802 } 852 }
803 853
804 } else { 854 } else {
805 855
806 float **writebuf = buffers; 856 auto data = input->getMultiChannelData(0, channelCount-1, startFrame, size);
807 if (offset > 0) { 857 if (!data.empty()) {
808 writebuf = new float *[channelCount]; 858 got = data[0].size();
809 for (int i = 0; i < channelCount; ++i) { 859 for (int c = 0; in_range_for(data, c); ++c) {
810 writebuf[i] = buffers[i] + offset; 860 copy(data[c].begin(), data[c].end(), buffers[c] + offset);
811 } 861 }
812 } 862 }
813
814 got = input->getMultiChannelData
815 (0, channelCount-1, startFrame, size, writebuf);
816
817 if (writebuf != buffers) delete[] writebuf;
818 } 863 }
819 864
820 while (got < size) { 865 while (got < size) {
821 for (int c = 0; c < channelCount; ++c) { 866 for (int c = 0; c < channelCount; ++c) {
822 buffers[c][got + offset] = 0.0; 867 buffers[c][got + offset] = 0.0;
842 887
843 if (m_descriptors[n]->sampleType == 888 if (m_descriptors[n]->sampleType ==
844 Vamp::Plugin::OutputDescriptor::VariableSampleRate) { 889 Vamp::Plugin::OutputDescriptor::VariableSampleRate) {
845 890
846 if (!feature.hasTimestamp) { 891 if (!feature.hasTimestamp) {
847 cerr 892 SVDEBUG
848 << "WARNING: FeatureExtractionModelTransformer::addFeature: " 893 << "WARNING: FeatureExtractionModelTransformer::addFeature: "
849 << "Feature has variable sample rate but no timestamp!" 894 << "Feature has variable sample rate but no timestamp!"
850 << endl; 895 << endl;
851 return; 896 return;
852 } else { 897 } else {
878 frame = lrint((double(m_fixedRateFeatureNos[n]) / rate) * inputRate); 923 frame = lrint((double(m_fixedRateFeatureNos[n]) / rate) * inputRate);
879 // cerr << frame << endl; 924 // cerr << frame << endl;
880 } 925 }
881 926
882 if (frame < 0) { 927 if (frame < 0) {
883 cerr 928 SVDEBUG
884 << "WARNING: FeatureExtractionModelTransformer::addFeature: " 929 << "WARNING: FeatureExtractionModelTransformer::addFeature: "
885 << "Negative frame counts are not supported (frame = " << frame 930 << "Negative frame counts are not supported (frame = " << frame
886 << " from timestamp " << feature.timestamp 931 << " from timestamp " << feature.timestamp
887 << "), dropping feature" 932 << "), dropping feature"
888 << endl; 933 << endl;
1011 } 1056 }
1012 } 1057 }
1013 1058
1014 } else if (isOutput<EditableDenseThreeDimensionalModel>(n)) { 1059 } else if (isOutput<EditableDenseThreeDimensionalModel>(n)) {
1015 1060
1016 DenseThreeDimensionalModel::Column values = 1061 DenseThreeDimensionalModel::Column values = feature.values;
1017 DenseThreeDimensionalModel::Column::fromStdVector(feature.values);
1018 1062
1019 EditableDenseThreeDimensionalModel *model = 1063 EditableDenseThreeDimensionalModel *model =
1020 getConformingOutput<EditableDenseThreeDimensionalModel>(n); 1064 getConformingOutput<EditableDenseThreeDimensionalModel>(n);
1021 if (!model) return; 1065 if (!model) return;
1022 1066