Commit 193d0298023031e0ca6c285f1e8e3f7b2ef0fa9c

Authored by Austin Van Blanton
2 parents 8aada808 9fa45efb

Merge branch 'master' of https://github.com/biometrics/openbr

openbr/openbr_export.cpp
... ... @@ -69,7 +69,6 @@
69 69 $ cd bin
70 70 $ export LD_LIBRARY_PATH=../lib:${LD_LIBRARY_PATH}
71 71 $ sudo ldconfig
72   -$ sudo cp ../share/openbr/70-yubikey.rules /etc/udev/rules.d # Only needed if you were given a license dongle.
73 72 \endverbatim
74 73 * \par OS X
75 74 \verbatim
... ... @@ -80,10 +79,6 @@ $ export DYLD_FRAMEWORK_PATH=../lib:${DYLD_FRAMEWORK_PATH}
80 79 * \par Windows
81 80 * No configuration is necessary!
82 81 *
83   - * \section installation_license_dongle License Dongle
84   - * In the unlikely event that you were given a USB License Dongle, then dongle must be in the computer in order to use the SDK.
85   - * No configuration of the dongle is needed.
86   - *
87 82 * \section installation_done Start Working
88 83 * To test for successful installation:
89 84 \verbatim
... ...
openbr/plugins/stream.cpp
... ... @@ -31,8 +31,10 @@ public:
31 31 virtual ~SharedBuffer() {}
32 32  
33 33 virtual void addItem(FrameData * input)=0;
  34 + virtual void reset()=0;
34 35  
35 36 virtual FrameData * tryGetItem()=0;
  37 + virtual int size()=0;
36 38 };
37 39  
38 40 // for n - 1 boundaries, multiple threads call addItem, the frames are
... ... @@ -74,6 +76,21 @@ public:
74 76 return output;
75 77 }
76 78  
  79 + virtual int size()
  80 + {
  81 + QMutexLocker lock(&bufferGuard);
  82 + return buffer.size();
  83 + }
  84 + virtual void reset()
  85 + {
  86 + if (size() != 0)
  87 + qDebug("Sequencing buffer has non-zero size during reset!");
  88 +
  89 + QMutexLocker lock(&bufferGuard);
  90 + next_target = 0;
  91 + }
  92 +
  93 +
77 94 private:
78 95 QMutex bufferGuard;
79 96 int next_target;
... ... @@ -95,6 +112,11 @@ public:
95 112 outputBuffer = &buffer2;
96 113 }
97 114  
  115 + int size()
  116 + {
  117 + QReadLocker readLock(&bufferGuard);
  118 + return inputBuffer->size() + outputBuffer->size();
  119 + }
98 120  
99 121 // called from the producer thread
100 122 void addItem(FrameData * input)
... ... @@ -133,6 +155,13 @@ public:
133 155 return output;
134 156 }
135 157  
  158 + virtual void reset()
  159 + {
  160 + if (this->size() != 0)
  161 + qDebug("Shared buffer has non-zero size during reset!");
  162 + }
  163 +
  164 +
136 165 private:
137 166 // The read-write lock. The thread adding to this buffer can add
138 167 // to the current input buffer if it has a read lock. The thread
... ... @@ -194,14 +223,13 @@ public:
194 223 // Try to get a FrameData from the pool, if we can't it means too many
195 224 // frames are already out, and we will return NULL to indicate failure
196 225 FrameData * aFrame = allFrames.tryGetItem();
197   - if (aFrame == NULL) {
  226 + if (aFrame == NULL)
198 227 return NULL;
199   - }
200 228  
201 229 aFrame->data.clear();
202 230 aFrame->sequenceNumber = -1;
203 231  
204   - // Try to read a frame, if this returns false the data source is broken
  232 + // Try to actually read a frame, if this returns false the data source is broken
205 233 bool res = getNext(*aFrame);
206 234  
207 235 // The datasource broke, update final_frame
... ... @@ -211,12 +239,15 @@ public:
211 239 final_frame = lookAhead.back()->sequenceNumber;
212 240 allFrames.addItem(aFrame);
213 241 }
214   - else lookAhead.push_back(aFrame);
  242 + else {
  243 + lookAhead.push_back(aFrame);
  244 + }
215 245  
  246 + // we will return the first frame on the lookAhead buffer
216 247 FrameData * rVal = lookAhead.first();
217 248 lookAhead.pop_front();
218 249  
219   -
  250 + // If this is the last frame, say so
220 251 if (rVal->sequenceNumber == final_frame) {
221 252 last_frame = true;
222 253 is_broken = true;
... ... @@ -239,6 +270,7 @@ public:
239 270  
240 271 if (frameNumber == final_frame) {
241 272 // We just received the last frame, better pulse
  273 + allReturned = true;
242 274 lastReturned.wakeAll();
243 275 rval = true;
244 276 }
... ... @@ -246,15 +278,24 @@ public:
246 278 return rval;
247 279 }
248 280  
249   - void waitLast()
  281 + bool waitLast()
250 282 {
251 283 QMutexLocker lock(&last_frame_update);
252   - lastReturned.wait(&last_frame_update);
  284 +
  285 + while (!allReturned)
  286 + {
  287 + // This would be a safer wait if we used a timeout, but
  288 + // theoretically that should never matter.
  289 + lastReturned.wait(&last_frame_update);
  290 + }
  291 + return true;
253 292 }
254 293  
255 294 bool open(Template & output, int start_index = 0)
256 295 {
257 296 is_broken = false;
  297 + allReturned = false;
  298 +
258 299 // The last frame isn't initialized yet
259 300 final_frame = -1;
260 301 // Start our sequence numbers from the input index
... ... @@ -282,19 +323,34 @@ public:
282 323 bool res = getNext(*firstFrame);
283 324  
284 325 // the data source broke already, we couldn't even get one frame
285   - // from it.
  326 + // from it even though it claimed to have opened successfully.
286 327 if (!res) {
287 328 is_broken = true;
288 329 return false;
289 330 }
290 331  
  332 + // We read one frame ahead of the last one returned, this allows
  333 + // us to know which frame is the final frame when we return it.
291 334 lookAhead.append(firstFrame);
292 335 return true;
293 336 }
294 337  
  338 + /*
  339 + * Pure virtual methods
  340 + */
  341 +
  342 + // isOpen doesn't appear to particularly work when used on opencv
  343 + // VideoCaptures, so we don't use it for anything important.
295 344 virtual bool isOpen()=0;
  345 + // Called from open, open the data source specified by the input
  346 + // template, don't worry about setting any of the state variables
  347 + // set in open.
296 348 virtual bool concreteOpen(Template & output) = 0;
  349 + // Get the next frame from the data source, store the results in
  350 + // FrameData (including the actual frame and appropriate sequence
  351 + // number).
297 352 virtual bool getNext(FrameData & input) = 0;
  353 + // close the currently open data source.
298 354 virtual void close() = 0;
299 355  
300 356 int next_sequence_number;
... ... @@ -302,6 +358,7 @@ protected:
302 358 DoubleBuffer allFrames;
303 359 int final_frame;
304 360 bool is_broken;
  361 + bool allReturned;
305 362 QList<FrameData *> lookAhead;
306 363  
307 364 QWaitCondition lastReturned;
... ... @@ -317,6 +374,11 @@ public:
317 374 bool concreteOpen(Template &input)
318 375 {
319 376 basis = input;
  377 +
  378 + // We can open either files (well actually this includes addresses of ip cameras
  379 + // through ffmpeg), or webcams. Webcam VideoCaptures are created through a separate
  380 + // overload of open that takes an integer, not a string.
  381 + // So, does this look like an integer?
320 382 bool is_int = false;
321 383 int anInt = input.file.name.toInt(&is_int);
322 384 if (is_int)
... ... @@ -349,8 +411,10 @@ public:
349 411 private:
350 412 bool getNext(FrameData & output)
351 413 {
352   - if (!isOpen())
  414 + if (!isOpen()) {
  415 + qDebug("video source is not open");
353 416 return false;
  417 + }
354 418  
355 419 output.data.append(Template(basis.file));
356 420 output.data.last().m() = cv::Mat();
... ... @@ -362,6 +426,7 @@ private:
362 426 bool res = video.read(temp);
363 427  
364 428 if (!res) {
  429 + // The video capture broke, return false.
365 430 output.data.last().m() = cv::Mat();
366 431 close();
367 432 return false;
... ... @@ -391,6 +456,8 @@ public:
391 456 data_ok = false;
392 457 }
393 458  
  459 + // To "open" it we just set appropriate indices, we assume that if this
  460 + // is an image, it is already loaded into memory.
394 461 bool concreteOpen(Template &input)
395 462 {
396 463 basis = input;
... ... @@ -440,7 +507,7 @@ private:
440 507 class DataSourceManager : public DataSource
441 508 {
442 509 public:
443   - DataSourceManager() : DataSource(500)
  510 + DataSourceManager(int activeFrames=100) : DataSource(activeFrames)
444 511 {
445 512 actualSource = NULL;
446 513 }
... ... @@ -450,6 +517,11 @@ public:
450 517 close();
451 518 }
452 519  
  520 + int size()
  521 + {
  522 + return this->allFrames.size();
  523 + }
  524 +
453 525 void close()
454 526 {
455 527 if (actualSource) {
... ... @@ -459,29 +531,40 @@ public:
459 531 }
460 532 }
461 533  
  534 + // We are used through a call to open(TemplateList)
462 535 bool open(TemplateList & input)
463 536 {
  537 + // Set up variables specific to us
464 538 current_template_idx = 0;
465 539 templates = input;
466 540  
  541 + // Call datasourece::open on the first template to set up
  542 + // state variables
467 543 return DataSource::open(templates[current_template_idx]);
468 544 }
469 545  
  546 + // Create an actual data source of appropriate type for this template
  547 + // (initially called via the call to DataSource::open, called later
  548 + // as we run out of frames on our templates).
470 549 bool concreteOpen(Template & input)
471 550 {
472 551 close();
473 552  
  553 + bool open_res = false;
474 554 // Input has no matrices? Its probably a video that hasn't been loaded yet
475 555 if (input.empty()) {
476 556 actualSource = new VideoDataSource(0);
477   - actualSource->concreteOpen(input);
  557 + open_res = actualSource->concreteOpen(input);
478 558 }
  559 + // If the input is not empty, we assume it is a set of frames already
  560 + // in memory.
479 561 else {
480   - // create frame dealer
481 562 actualSource = new TemplateDataSource(0);
482   - actualSource->concreteOpen(input);
  563 + open_res = actualSource->concreteOpen(input);
483 564 }
484   - if (!isOpen()) {
  565 +
  566 + // The data source failed to open
  567 + if (!open_res) {
485 568 delete actualSource;
486 569 actualSource = NULL;
487 570 return false;
... ... @@ -497,12 +580,16 @@ protected:
497 580  
498 581 TemplateList templates;
499 582 DataSource * actualSource;
  583 + // Get the next frame, if we run out of frames on the current template
  584 + // move on to the next one.
500 585 bool getNext(FrameData & output)
501 586 {
502 587 bool res = actualSource->getNext(output);
503 588 output.sequenceNumber = next_sequence_number;
504 589  
  590 + // OK we got a frame
505 591 if (res) {
  592 + // Override the sequence number set by actualSource
506 593 output.data.last().file.set("FrameNumber", output.sequenceNumber);
507 594 next_sequence_number++;
508 595 if (output.data.last().last().empty())
... ... @@ -510,7 +597,7 @@ protected:
510 597 return true;
511 598 }
512 599  
513   -
  600 + // We didn't get a frame, try to move on to the next template.
514 601 while(!res) {
515 602 output.data.clear();
516 603 current_template_idx++;
... ... @@ -521,12 +608,16 @@ protected:
521 608  
522 609 // open the next data source
523 610 bool open_res = concreteOpen(templates[current_template_idx]);
  611 + // We couldn't open it, give up? We could maybe continue here
  612 + // but don't currently.
524 613 if (!open_res)
525 614 return false;
526 615  
527   - // get a frame from it
  616 + // get a frame from the newly opened data source, if that fails
  617 + // we continue to open the next one.
528 618 res = actualSource->getNext(output);
529 619 }
  620 + // Finally, set the sequence number for the frame we actually return.
530 621 output.sequenceNumber = next_sequence_number++;
531 622 output.data.last().file.set("FrameNumber", output.sequenceNumber);
532 623  
... ... @@ -573,6 +664,9 @@ public:
573 664 int stage_id;
574 665  
575 666 virtual void reset()=0;
  667 +
  668 + virtual void status()=0;
  669 +
576 670 protected:
577 671 int thread_count;
578 672  
... ... @@ -606,7 +700,8 @@ class MultiThreadStage : public ProcessingStage
606 700 public:
607 701 MultiThreadStage(int _input) : ProcessingStage(_input) {}
608 702  
609   -
  703 + // Not much to worry about here, we will project the input
  704 + // and try to continue to the next stage.
610 705 FrameData * run(FrameData * input, bool & should_continue)
611 706 {
612 707 if (input == NULL) {
... ... @@ -620,7 +715,8 @@ public:
620 715 return input;
621 716 }
622 717  
623   - // Called from a different thread than run
  718 + // Called from a different thread than run. Nothing to worry about
  719 + // we offer no restrictions on when loops may enter this stage.
624 720 virtual bool tryAcquireNextStage(FrameData *& input)
625 721 {
626 722 (void) input;
... ... @@ -631,6 +727,9 @@ public:
631 727 {
632 728 // nothing to do.
633 729 }
  730 + void status(){
  731 + qDebug("multi thread stage %d, nothing to worry about", this->stage_id);
  732 + }
634 733 };
635 734  
636 735 class SingleThreadStage : public ProcessingStage
... ... @@ -640,13 +739,18 @@ public:
640 739 {
641 740 currentStatus = STOPPING;
642 741 next_target = 0;
  742 + // If the previous stage is single-threaded, queued inputs
  743 + // are stored in a double buffer
643 744 if (input_variance) {
644 745 this->inputBuffer = new DoubleBuffer();
645 746 }
  747 + // If it's multi-threaded we need to put the inputs back in order
  748 + // before we can use them, so we use a sequencing buffer.
646 749 else {
647 750 this->inputBuffer = new SequencingBuffer();
648 751 }
649 752 }
  753 +
650 754 ~SingleThreadStage()
651 755 {
652 756 delete inputBuffer;
... ... @@ -657,6 +761,7 @@ public:
657 761 QWriteLocker writeLock(&statusLock);
658 762 currentStatus = STOPPING;
659 763 next_target = 0;
  764 + inputBuffer->reset();
660 765 }
661 766  
662 767  
... ... @@ -706,7 +811,13 @@ public:
706 811 next->stages = stages;
707 812 next->start_idx = this->stage_id;
708 813 next->startItem = newItem;
709   - this->threads->start(next, stages->size() - stage_id);
  814 +
  815 + // We start threads with priority equal to their stage id
  816 + // This is intended to ensure progression, we do queued late stage
  817 + // jobs before queued early stage jobs, and so tend to finish frames
  818 + // rather than go stage by stage. In Qt 5.1, priorities are priorities
  819 + // so we use the stage_id directly.
  820 + this->threads->start(next, stage_id);
710 821 }
711 822  
712 823  
... ... @@ -741,45 +852,50 @@ public:
741 852  
742 853 return true;
743 854 }
  855 +
  856 + void status(){
  857 + qDebug("single thread stage %d, status starting? %d, next %d buffer size %d", this->stage_id, this->currentStatus == SingleThreadStage::STARTING, this->next_target, this->inputBuffer->size());
  858 + }
  859 +
744 860 };
745 861  
746   -// No input buffer, instead we draw templates from some data source
747   -// Will be operated by the main thread for the stream. starts threads
  862 +// This stage reads new frames from the data source.
748 863 class FirstStage : public SingleThreadStage
749 864 {
750 865 public:
751   - FirstStage() : SingleThreadStage(true) {}
  866 + FirstStage(int activeFrames = 100) : SingleThreadStage(true), dataSource(activeFrames){ }
752 867  
753 868 DataSourceManager dataSource;
754 869  
  870 + void reset()
  871 + {
  872 + dataSource.close();
  873 + SingleThreadStage::reset();
  874 + }
  875 +
755 876 FrameData * run(FrameData * input, bool & should_continue)
756 877 {
757   - // Try to get a frame from the datasource
  878 + if (input == NULL)
  879 + qFatal("NULL frame in input stage");
  880 +
  881 + // Can we enter the next stage?
  882 + should_continue = nextStage->tryAcquireNextStage(input);
  883 +
  884 + // Try to get a frame from the datasource, we keep working on
  885 + // the frame we have, but we will queue another job for the next
  886 + // frame if a frame is currently available.
758 887 QWriteLocker lock(&statusLock);
759 888 bool last_frame = false;
760   - input = dataSource.tryGetFrame(last_frame);
  889 + FrameData * newFrame = dataSource.tryGetFrame(last_frame);
761 890  
762   - // Datasource broke, or is currently out of frames?
763   - if (!input || last_frame)
764   - {
765   - // We will just stop and not continue.
  891 + // Were we able to get a frame?
  892 + if (newFrame) startThread(newFrame);
  893 + // If not this stage will enter a stopped state.
  894 + else {
766 895 currentStatus = STOPPING;
767   - if (!input) {
768   - should_continue = false;
769   - return NULL;
770   - }
771 896 }
772   - lock.unlock();
773   - // Can we enter the next stage?
774   - should_continue = nextStage->tryAcquireNextStage(input);
775 897  
776   - // We are exiting leaving this stage, should we start another
777   - // thread here? Normally we will always re-queue a thread on
778   - // the first stage, but if we received the last frame there is
779   - // no need to.
780   - if (!last_frame) {
781   - startThread(NULL);
782   - }
  898 + lock.unlock();
783 899  
784 900 return input;
785 901 }
... ... @@ -797,31 +913,48 @@ public:
797 913 }
798 914  
799 915 QReadLocker lock(&statusLock);
800   - // A thread is already in the first stage,
801   - // we should just return
  916 + // If the first stage is already active we will just end.
802 917 if (currentStatus == STARTING)
803 918 {
804 919 return false;
805 920 }
806   - // Have to change to a write lock to modify currentStatus
  921 +
  922 + // Otherwise we will try to continue, but to do so we have to
  923 + // escalate the lock, and sadly there is no way to do so without
  924 + // releasing the read-mode lock, and getting a new write-mode lock.
807 925 lock.unlock();
808 926  
809 927 QWriteLocker writeLock(&statusLock);
810   - // But someone else might have started a thread in the meantime
  928 + // currentStatus might have changed in the gap between releasing the read
  929 + // lock and getting the write lock.
811 930 if (currentStatus == STARTING)
812 931 {
813 932 return false;
814 933 }
815   - // Ok we'll start a thread
  934 +
  935 + bool last_frame = false;
  936 + // Try to get a frame from the data source, if we get one we will
  937 + // continue to the first stage.
  938 + input = dataSource.tryGetFrame(last_frame);
  939 +
  940 + if (!input) {
  941 + return false;
  942 + }
  943 +
816 944 currentStatus = STARTING;
817 945  
818   - // We always start a readstage thread with null input, so nothing to do here
819 946 return true;
820 947 }
821 948  
  949 + void status(){
  950 + qDebug("Read stage %d, status starting? %d, next frame %d buffer size %d", this->stage_id, this->currentStatus == SingleThreadStage::STARTING, this->next_target, this->dataSource.size());
  951 + }
  952 +
  953 +
822 954 };
823 955  
824   -// starts threads
  956 +// Appened to the end of a Stream's transform sequence. Collects the output
  957 +// from each frame on a single templatelist
825 958 class LastStage : public SingleThreadStage
826 959 {
827 960 public:
... ... @@ -834,6 +967,7 @@ public:
834 967 private:
835 968 TemplateList collectedOutput;
836 969 public:
  970 +
837 971 void reset()
838 972 {
839 973 collectedOutput.clear();
... ... @@ -873,6 +1007,11 @@ public:
873 1007  
874 1008 return input;
875 1009 }
  1010 +
  1011 + void status(){
  1012 + qDebug("Collection stage %d, status starting? %d, next %d buffer size %d", this->stage_id, this->currentStatus == SingleThreadStage::STARTING, this->next_target, this->inputBuffer->size());
  1013 + }
  1014 +
876 1015 };
877 1016  
878 1017  
... ... @@ -880,6 +1019,8 @@ class StreamTransform : public CompositeTransform
880 1019 {
881 1020 Q_OBJECT
882 1021 public:
  1022 + Q_PROPERTY(int activeFrames READ get_activeFrames WRITE set_activeFrames RESET reset_activeFrames)
  1023 + BR_PROPERTY(int, activeFrames, 100)
883 1024  
884 1025 void train(const TemplateList & data)
885 1026 {
... ... @@ -902,7 +1043,8 @@ public:
902 1043 qFatal("whatever");
903 1044 }
904 1045  
905   - // start processing
  1046 + // start processing, consider all templates in src a continuous
  1047 + // 'video'
906 1048 void projectUpdate(const TemplateList & src, TemplateList & dst)
907 1049 {
908 1050 dst = src;
... ... @@ -911,12 +1053,21 @@ public:
911 1053 if (!res) return;
912 1054  
913 1055 // Start the first thread in the stream.
  1056 + QWriteLocker lock(&readStage->statusLock);
914 1057 readStage->currentStatus = SingleThreadStage::STARTING;
915   - readStage->startThread(NULL);
916 1058  
917   - // Wait for the stream to reach the last frame available from
  1059 + // We have to get a frame before starting the thread
  1060 + bool last_frame = false;
  1061 + FrameData * firstFrame = readStage->dataSource.tryGetFrame(last_frame);
  1062 + if (firstFrame == NULL)
  1063 + qFatal("Failed to read first frame of video");
  1064 + readStage->startThread(firstFrame);
  1065 + lock.unlock();
  1066 +
  1067 + // Wait for the stream to process the last frame available from
918 1068 // the data source.
919   - readStage->dataSource.waitLast();
  1069 + bool wait_res = false;
  1070 + wait_res = readStage->dataSource.waitLast();
920 1071  
921 1072 // Now that there are no more incoming frames, call finalize
922 1073 // on each transform in turn to collect any last templates
... ... @@ -958,6 +1109,8 @@ public:
958 1109 {
959 1110 if (transforms.isEmpty()) return;
960 1111  
  1112 + // call CompositeTransform::init so that trainable is set
  1113 + // correctly.
961 1114 CompositeTransform::init();
962 1115  
963 1116 // We share a thread pool across streams attached to the same
... ... @@ -973,24 +1126,31 @@ public:
973 1126 threads = it.value();
974 1127 poolLock.unlock();
975 1128  
  1129 + // Are our children time varying or not? This decides whether
  1130 + // we run them in single threaded or multi threaded stages
976 1131 stage_variance.reserve(transforms.size());
977 1132 foreach (const br::Transform *transform, transforms) {
978 1133 stage_variance.append(transform->timeVarying());
979 1134 }
980 1135  
981   - readStage = new FirstStage();
  1136 + // Additionally, we have a separate stage responsible for reading
  1137 + // frames from the data source
  1138 + readStage = new FirstStage(activeFrames);
982 1139  
983 1140 processingStages.push_back(readStage);
984 1141 readStage->stage_id = 0;
985 1142 readStage->stages = &this->processingStages;
986 1143 readStage->threads = this->threads;
987 1144  
  1145 + // Initialize and link a processing stage for each of our child
  1146 + // transforms.
988 1147 int next_stage_id = 1;
989   -
990 1148 bool prev_stage_variance = true;
991 1149 for (int i =0; i < transforms.size(); i++)
992 1150 {
993 1151 if (stage_variance[i])
  1152 + // Whether or not the previous stage is multi-threaded controls
  1153 + // the type of input buffer we need in a single threaded stage.
994 1154 processingStages.append(new SingleThreadStage(prev_stage_variance));
995 1155 else
996 1156 processingStages.append(new MultiThreadStage(Globals->parallelism));
... ... @@ -1008,20 +1168,25 @@ public:
1008 1168 prev_stage_variance = stage_variance[i];
1009 1169 }
1010 1170  
  1171 + // We also have the last stage, which just puts the output of the
  1172 + // previous stages on a template list.
1011 1173 collectionStage = new LastStage(prev_stage_variance);
1012 1174 processingStages.append(collectionStage);
1013 1175 collectionStage->stage_id = next_stage_id;
1014 1176 collectionStage->stages = &this->processingStages;
1015 1177 collectionStage->threads = this->threads;
1016 1178  
  1179 + // the last transform stage points to collection stage
1017 1180 processingStages[processingStages.size() - 2]->nextStage = collectionStage;
1018 1181  
1019   - // It's a ring buffer, get it?
  1182 + // And the collection stage points to the read stage, because this is
  1183 + // a ring buffer.
1020 1184 collectionStage->nextStage = readStage;
1021 1185 }
1022 1186  
1023 1187 ~StreamTransform()
1024 1188 {
  1189 + // Delete all the stages
1025 1190 for (int i = 0; i < processingStages.size(); i++) {
1026 1191 delete processingStages[i];
1027 1192 }
... ...