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,7 +69,6 @@
69 $ cd bin 69 $ cd bin
70 $ export LD_LIBRARY_PATH=../lib:${LD_LIBRARY_PATH} 70 $ export LD_LIBRARY_PATH=../lib:${LD_LIBRARY_PATH}
71 $ sudo ldconfig 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 \endverbatim 72 \endverbatim
74 * \par OS X 73 * \par OS X
75 \verbatim 74 \verbatim
@@ -80,10 +79,6 @@ $ export DYLD_FRAMEWORK_PATH=../lib:${DYLD_FRAMEWORK_PATH} @@ -80,10 +79,6 @@ $ export DYLD_FRAMEWORK_PATH=../lib:${DYLD_FRAMEWORK_PATH}
80 * \par Windows 79 * \par Windows
81 * No configuration is necessary! 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 * \section installation_done Start Working 82 * \section installation_done Start Working
88 * To test for successful installation: 83 * To test for successful installation:
89 \verbatim 84 \verbatim
openbr/plugins/stream.cpp
@@ -31,8 +31,10 @@ public: @@ -31,8 +31,10 @@ public:
31 virtual ~SharedBuffer() {} 31 virtual ~SharedBuffer() {}
32 32
33 virtual void addItem(FrameData * input)=0; 33 virtual void addItem(FrameData * input)=0;
  34 + virtual void reset()=0;
34 35
35 virtual FrameData * tryGetItem()=0; 36 virtual FrameData * tryGetItem()=0;
  37 + virtual int size()=0;
36 }; 38 };
37 39
38 // for n - 1 boundaries, multiple threads call addItem, the frames are 40 // for n - 1 boundaries, multiple threads call addItem, the frames are
@@ -74,6 +76,21 @@ public: @@ -74,6 +76,21 @@ public:
74 return output; 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 private: 94 private:
78 QMutex bufferGuard; 95 QMutex bufferGuard;
79 int next_target; 96 int next_target;
@@ -95,6 +112,11 @@ public: @@ -95,6 +112,11 @@ public:
95 outputBuffer = &buffer2; 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 // called from the producer thread 121 // called from the producer thread
100 void addItem(FrameData * input) 122 void addItem(FrameData * input)
@@ -133,6 +155,13 @@ public: @@ -133,6 +155,13 @@ public:
133 return output; 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 private: 165 private:
137 // The read-write lock. The thread adding to this buffer can add 166 // The read-write lock. The thread adding to this buffer can add
138 // to the current input buffer if it has a read lock. The thread 167 // to the current input buffer if it has a read lock. The thread
@@ -194,14 +223,13 @@ public: @@ -194,14 +223,13 @@ public:
194 // Try to get a FrameData from the pool, if we can't it means too many 223 // Try to get a FrameData from the pool, if we can't it means too many
195 // frames are already out, and we will return NULL to indicate failure 224 // frames are already out, and we will return NULL to indicate failure
196 FrameData * aFrame = allFrames.tryGetItem(); 225 FrameData * aFrame = allFrames.tryGetItem();
197 - if (aFrame == NULL) { 226 + if (aFrame == NULL)
198 return NULL; 227 return NULL;
199 - }  
200 228
201 aFrame->data.clear(); 229 aFrame->data.clear();
202 aFrame->sequenceNumber = -1; 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 bool res = getNext(*aFrame); 233 bool res = getNext(*aFrame);
206 234
207 // The datasource broke, update final_frame 235 // The datasource broke, update final_frame
@@ -211,12 +239,15 @@ public: @@ -211,12 +239,15 @@ public:
211 final_frame = lookAhead.back()->sequenceNumber; 239 final_frame = lookAhead.back()->sequenceNumber;
212 allFrames.addItem(aFrame); 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 FrameData * rVal = lookAhead.first(); 247 FrameData * rVal = lookAhead.first();
217 lookAhead.pop_front(); 248 lookAhead.pop_front();
218 249
219 - 250 + // If this is the last frame, say so
220 if (rVal->sequenceNumber == final_frame) { 251 if (rVal->sequenceNumber == final_frame) {
221 last_frame = true; 252 last_frame = true;
222 is_broken = true; 253 is_broken = true;
@@ -239,6 +270,7 @@ public: @@ -239,6 +270,7 @@ public:
239 270
240 if (frameNumber == final_frame) { 271 if (frameNumber == final_frame) {
241 // We just received the last frame, better pulse 272 // We just received the last frame, better pulse
  273 + allReturned = true;
242 lastReturned.wakeAll(); 274 lastReturned.wakeAll();
243 rval = true; 275 rval = true;
244 } 276 }
@@ -246,15 +278,24 @@ public: @@ -246,15 +278,24 @@ public:
246 return rval; 278 return rval;
247 } 279 }
248 280
249 - void waitLast() 281 + bool waitLast()
250 { 282 {
251 QMutexLocker lock(&last_frame_update); 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 bool open(Template & output, int start_index = 0) 294 bool open(Template & output, int start_index = 0)
256 { 295 {
257 is_broken = false; 296 is_broken = false;
  297 + allReturned = false;
  298 +
258 // The last frame isn't initialized yet 299 // The last frame isn't initialized yet
259 final_frame = -1; 300 final_frame = -1;
260 // Start our sequence numbers from the input index 301 // Start our sequence numbers from the input index
@@ -282,19 +323,34 @@ public: @@ -282,19 +323,34 @@ public:
282 bool res = getNext(*firstFrame); 323 bool res = getNext(*firstFrame);
283 324
284 // the data source broke already, we couldn't even get one frame 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 if (!res) { 327 if (!res) {
287 is_broken = true; 328 is_broken = true;
288 return false; 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 lookAhead.append(firstFrame); 334 lookAhead.append(firstFrame);
292 return true; 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 virtual bool isOpen()=0; 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 virtual bool concreteOpen(Template & output) = 0; 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 virtual bool getNext(FrameData & input) = 0; 352 virtual bool getNext(FrameData & input) = 0;
  353 + // close the currently open data source.
298 virtual void close() = 0; 354 virtual void close() = 0;
299 355
300 int next_sequence_number; 356 int next_sequence_number;
@@ -302,6 +358,7 @@ protected: @@ -302,6 +358,7 @@ protected:
302 DoubleBuffer allFrames; 358 DoubleBuffer allFrames;
303 int final_frame; 359 int final_frame;
304 bool is_broken; 360 bool is_broken;
  361 + bool allReturned;
305 QList<FrameData *> lookAhead; 362 QList<FrameData *> lookAhead;
306 363
307 QWaitCondition lastReturned; 364 QWaitCondition lastReturned;
@@ -317,6 +374,11 @@ public: @@ -317,6 +374,11 @@ public:
317 bool concreteOpen(Template &input) 374 bool concreteOpen(Template &input)
318 { 375 {
319 basis = input; 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 bool is_int = false; 382 bool is_int = false;
321 int anInt = input.file.name.toInt(&is_int); 383 int anInt = input.file.name.toInt(&is_int);
322 if (is_int) 384 if (is_int)
@@ -349,8 +411,10 @@ public: @@ -349,8 +411,10 @@ public:
349 private: 411 private:
350 bool getNext(FrameData & output) 412 bool getNext(FrameData & output)
351 { 413 {
352 - if (!isOpen()) 414 + if (!isOpen()) {
  415 + qDebug("video source is not open");
353 return false; 416 return false;
  417 + }
354 418
355 output.data.append(Template(basis.file)); 419 output.data.append(Template(basis.file));
356 output.data.last().m() = cv::Mat(); 420 output.data.last().m() = cv::Mat();
@@ -362,6 +426,7 @@ private: @@ -362,6 +426,7 @@ private:
362 bool res = video.read(temp); 426 bool res = video.read(temp);
363 427
364 if (!res) { 428 if (!res) {
  429 + // The video capture broke, return false.
365 output.data.last().m() = cv::Mat(); 430 output.data.last().m() = cv::Mat();
366 close(); 431 close();
367 return false; 432 return false;
@@ -391,6 +456,8 @@ public: @@ -391,6 +456,8 @@ public:
391 data_ok = false; 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 bool concreteOpen(Template &input) 461 bool concreteOpen(Template &input)
395 { 462 {
396 basis = input; 463 basis = input;
@@ -440,7 +507,7 @@ private: @@ -440,7 +507,7 @@ private:
440 class DataSourceManager : public DataSource 507 class DataSourceManager : public DataSource
441 { 508 {
442 public: 509 public:
443 - DataSourceManager() : DataSource(500) 510 + DataSourceManager(int activeFrames=100) : DataSource(activeFrames)
444 { 511 {
445 actualSource = NULL; 512 actualSource = NULL;
446 } 513 }
@@ -450,6 +517,11 @@ public: @@ -450,6 +517,11 @@ public:
450 close(); 517 close();
451 } 518 }
452 519
  520 + int size()
  521 + {
  522 + return this->allFrames.size();
  523 + }
  524 +
453 void close() 525 void close()
454 { 526 {
455 if (actualSource) { 527 if (actualSource) {
@@ -459,29 +531,40 @@ public: @@ -459,29 +531,40 @@ public:
459 } 531 }
460 } 532 }
461 533
  534 + // We are used through a call to open(TemplateList)
462 bool open(TemplateList & input) 535 bool open(TemplateList & input)
463 { 536 {
  537 + // Set up variables specific to us
464 current_template_idx = 0; 538 current_template_idx = 0;
465 templates = input; 539 templates = input;
466 540
  541 + // Call datasourece::open on the first template to set up
  542 + // state variables
467 return DataSource::open(templates[current_template_idx]); 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 bool concreteOpen(Template & input) 549 bool concreteOpen(Template & input)
471 { 550 {
472 close(); 551 close();
473 552
  553 + bool open_res = false;
474 // Input has no matrices? Its probably a video that hasn't been loaded yet 554 // Input has no matrices? Its probably a video that hasn't been loaded yet
475 if (input.empty()) { 555 if (input.empty()) {
476 actualSource = new VideoDataSource(0); 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 else { 561 else {
480 - // create frame dealer  
481 actualSource = new TemplateDataSource(0); 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 delete actualSource; 568 delete actualSource;
486 actualSource = NULL; 569 actualSource = NULL;
487 return false; 570 return false;
@@ -497,12 +580,16 @@ protected: @@ -497,12 +580,16 @@ protected:
497 580
498 TemplateList templates; 581 TemplateList templates;
499 DataSource * actualSource; 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 bool getNext(FrameData & output) 585 bool getNext(FrameData & output)
501 { 586 {
502 bool res = actualSource->getNext(output); 587 bool res = actualSource->getNext(output);
503 output.sequenceNumber = next_sequence_number; 588 output.sequenceNumber = next_sequence_number;
504 589
  590 + // OK we got a frame
505 if (res) { 591 if (res) {
  592 + // Override the sequence number set by actualSource
506 output.data.last().file.set("FrameNumber", output.sequenceNumber); 593 output.data.last().file.set("FrameNumber", output.sequenceNumber);
507 next_sequence_number++; 594 next_sequence_number++;
508 if (output.data.last().last().empty()) 595 if (output.data.last().last().empty())
@@ -510,7 +597,7 @@ protected: @@ -510,7 +597,7 @@ protected:
510 return true; 597 return true;
511 } 598 }
512 599
513 - 600 + // We didn't get a frame, try to move on to the next template.
514 while(!res) { 601 while(!res) {
515 output.data.clear(); 602 output.data.clear();
516 current_template_idx++; 603 current_template_idx++;
@@ -521,12 +608,16 @@ protected: @@ -521,12 +608,16 @@ protected:
521 608
522 // open the next data source 609 // open the next data source
523 bool open_res = concreteOpen(templates[current_template_idx]); 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 if (!open_res) 613 if (!open_res)
525 return false; 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 res = actualSource->getNext(output); 618 res = actualSource->getNext(output);
529 } 619 }
  620 + // Finally, set the sequence number for the frame we actually return.
530 output.sequenceNumber = next_sequence_number++; 621 output.sequenceNumber = next_sequence_number++;
531 output.data.last().file.set("FrameNumber", output.sequenceNumber); 622 output.data.last().file.set("FrameNumber", output.sequenceNumber);
532 623
@@ -573,6 +664,9 @@ public: @@ -573,6 +664,9 @@ public:
573 int stage_id; 664 int stage_id;
574 665
575 virtual void reset()=0; 666 virtual void reset()=0;
  667 +
  668 + virtual void status()=0;
  669 +
576 protected: 670 protected:
577 int thread_count; 671 int thread_count;
578 672
@@ -606,7 +700,8 @@ class MultiThreadStage : public ProcessingStage @@ -606,7 +700,8 @@ class MultiThreadStage : public ProcessingStage
606 public: 700 public:
607 MultiThreadStage(int _input) : ProcessingStage(_input) {} 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 FrameData * run(FrameData * input, bool & should_continue) 705 FrameData * run(FrameData * input, bool & should_continue)
611 { 706 {
612 if (input == NULL) { 707 if (input == NULL) {
@@ -620,7 +715,8 @@ public: @@ -620,7 +715,8 @@ public:
620 return input; 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 virtual bool tryAcquireNextStage(FrameData *& input) 720 virtual bool tryAcquireNextStage(FrameData *& input)
625 { 721 {
626 (void) input; 722 (void) input;
@@ -631,6 +727,9 @@ public: @@ -631,6 +727,9 @@ public:
631 { 727 {
632 // nothing to do. 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 class SingleThreadStage : public ProcessingStage 735 class SingleThreadStage : public ProcessingStage
@@ -640,13 +739,18 @@ public: @@ -640,13 +739,18 @@ public:
640 { 739 {
641 currentStatus = STOPPING; 740 currentStatus = STOPPING;
642 next_target = 0; 741 next_target = 0;
  742 + // If the previous stage is single-threaded, queued inputs
  743 + // are stored in a double buffer
643 if (input_variance) { 744 if (input_variance) {
644 this->inputBuffer = new DoubleBuffer(); 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 else { 749 else {
647 this->inputBuffer = new SequencingBuffer(); 750 this->inputBuffer = new SequencingBuffer();
648 } 751 }
649 } 752 }
  753 +
650 ~SingleThreadStage() 754 ~SingleThreadStage()
651 { 755 {
652 delete inputBuffer; 756 delete inputBuffer;
@@ -657,6 +761,7 @@ public: @@ -657,6 +761,7 @@ public:
657 QWriteLocker writeLock(&statusLock); 761 QWriteLocker writeLock(&statusLock);
658 currentStatus = STOPPING; 762 currentStatus = STOPPING;
659 next_target = 0; 763 next_target = 0;
  764 + inputBuffer->reset();
660 } 765 }
661 766
662 767
@@ -706,7 +811,13 @@ public: @@ -706,7 +811,13 @@ public:
706 next->stages = stages; 811 next->stages = stages;
707 next->start_idx = this->stage_id; 812 next->start_idx = this->stage_id;
708 next->startItem = newItem; 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,45 +852,50 @@ public:
741 852
742 return true; 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 class FirstStage : public SingleThreadStage 863 class FirstStage : public SingleThreadStage
749 { 864 {
750 public: 865 public:
751 - FirstStage() : SingleThreadStage(true) {} 866 + FirstStage(int activeFrames = 100) : SingleThreadStage(true), dataSource(activeFrames){ }
752 867
753 DataSourceManager dataSource; 868 DataSourceManager dataSource;
754 869
  870 + void reset()
  871 + {
  872 + dataSource.close();
  873 + SingleThreadStage::reset();
  874 + }
  875 +
755 FrameData * run(FrameData * input, bool & should_continue) 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 QWriteLocker lock(&statusLock); 887 QWriteLocker lock(&statusLock);
759 bool last_frame = false; 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 currentStatus = STOPPING; 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 return input; 900 return input;
785 } 901 }
@@ -797,31 +913,48 @@ public: @@ -797,31 +913,48 @@ public:
797 } 913 }
798 914
799 QReadLocker lock(&statusLock); 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 if (currentStatus == STARTING) 917 if (currentStatus == STARTING)
803 { 918 {
804 return false; 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 lock.unlock(); 925 lock.unlock();
808 926
809 QWriteLocker writeLock(&statusLock); 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 if (currentStatus == STARTING) 930 if (currentStatus == STARTING)
812 { 931 {
813 return false; 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 currentStatus = STARTING; 944 currentStatus = STARTING;
817 945
818 - // We always start a readstage thread with null input, so nothing to do here  
819 return true; 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 class LastStage : public SingleThreadStage 958 class LastStage : public SingleThreadStage
826 { 959 {
827 public: 960 public:
@@ -834,6 +967,7 @@ public: @@ -834,6 +967,7 @@ public:
834 private: 967 private:
835 TemplateList collectedOutput; 968 TemplateList collectedOutput;
836 public: 969 public:
  970 +
837 void reset() 971 void reset()
838 { 972 {
839 collectedOutput.clear(); 973 collectedOutput.clear();
@@ -873,6 +1007,11 @@ public: @@ -873,6 +1007,11 @@ public:
873 1007
874 return input; 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,6 +1019,8 @@ class StreamTransform : public CompositeTransform
880 { 1019 {
881 Q_OBJECT 1020 Q_OBJECT
882 public: 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 void train(const TemplateList & data) 1025 void train(const TemplateList & data)
885 { 1026 {
@@ -902,7 +1043,8 @@ public: @@ -902,7 +1043,8 @@ public:
902 qFatal("whatever"); 1043 qFatal("whatever");
903 } 1044 }
904 1045
905 - // start processing 1046 + // start processing, consider all templates in src a continuous
  1047 + // 'video'
906 void projectUpdate(const TemplateList & src, TemplateList & dst) 1048 void projectUpdate(const TemplateList & src, TemplateList & dst)
907 { 1049 {
908 dst = src; 1050 dst = src;
@@ -911,12 +1053,21 @@ public: @@ -911,12 +1053,21 @@ public:
911 if (!res) return; 1053 if (!res) return;
912 1054
913 // Start the first thread in the stream. 1055 // Start the first thread in the stream.
  1056 + QWriteLocker lock(&readStage->statusLock);
914 readStage->currentStatus = SingleThreadStage::STARTING; 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 // the data source. 1068 // the data source.
919 - readStage->dataSource.waitLast(); 1069 + bool wait_res = false;
  1070 + wait_res = readStage->dataSource.waitLast();
920 1071
921 // Now that there are no more incoming frames, call finalize 1072 // Now that there are no more incoming frames, call finalize
922 // on each transform in turn to collect any last templates 1073 // on each transform in turn to collect any last templates
@@ -958,6 +1109,8 @@ public: @@ -958,6 +1109,8 @@ public:
958 { 1109 {
959 if (transforms.isEmpty()) return; 1110 if (transforms.isEmpty()) return;
960 1111
  1112 + // call CompositeTransform::init so that trainable is set
  1113 + // correctly.
961 CompositeTransform::init(); 1114 CompositeTransform::init();
962 1115
963 // We share a thread pool across streams attached to the same 1116 // We share a thread pool across streams attached to the same
@@ -973,24 +1126,31 @@ public: @@ -973,24 +1126,31 @@ public:
973 threads = it.value(); 1126 threads = it.value();
974 poolLock.unlock(); 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 stage_variance.reserve(transforms.size()); 1131 stage_variance.reserve(transforms.size());
977 foreach (const br::Transform *transform, transforms) { 1132 foreach (const br::Transform *transform, transforms) {
978 stage_variance.append(transform->timeVarying()); 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 processingStages.push_back(readStage); 1140 processingStages.push_back(readStage);
984 readStage->stage_id = 0; 1141 readStage->stage_id = 0;
985 readStage->stages = &this->processingStages; 1142 readStage->stages = &this->processingStages;
986 readStage->threads = this->threads; 1143 readStage->threads = this->threads;
987 1144
  1145 + // Initialize and link a processing stage for each of our child
  1146 + // transforms.
988 int next_stage_id = 1; 1147 int next_stage_id = 1;
989 -  
990 bool prev_stage_variance = true; 1148 bool prev_stage_variance = true;
991 for (int i =0; i < transforms.size(); i++) 1149 for (int i =0; i < transforms.size(); i++)
992 { 1150 {
993 if (stage_variance[i]) 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 processingStages.append(new SingleThreadStage(prev_stage_variance)); 1154 processingStages.append(new SingleThreadStage(prev_stage_variance));
995 else 1155 else
996 processingStages.append(new MultiThreadStage(Globals->parallelism)); 1156 processingStages.append(new MultiThreadStage(Globals->parallelism));
@@ -1008,20 +1168,25 @@ public: @@ -1008,20 +1168,25 @@ public:
1008 prev_stage_variance = stage_variance[i]; 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 collectionStage = new LastStage(prev_stage_variance); 1173 collectionStage = new LastStage(prev_stage_variance);
1012 processingStages.append(collectionStage); 1174 processingStages.append(collectionStage);
1013 collectionStage->stage_id = next_stage_id; 1175 collectionStage->stage_id = next_stage_id;
1014 collectionStage->stages = &this->processingStages; 1176 collectionStage->stages = &this->processingStages;
1015 collectionStage->threads = this->threads; 1177 collectionStage->threads = this->threads;
1016 1178
  1179 + // the last transform stage points to collection stage
1017 processingStages[processingStages.size() - 2]->nextStage = collectionStage; 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 collectionStage->nextStage = readStage; 1184 collectionStage->nextStage = readStage;
1021 } 1185 }
1022 1186
1023 ~StreamTransform() 1187 ~StreamTransform()
1024 { 1188 {
  1189 + // Delete all the stages
1025 for (int i = 0; i < processingStages.size(); i++) { 1190 for (int i = 0; i < processingStages.size(); i++) {
1026 delete processingStages[i]; 1191 delete processingStages[i];
1027 } 1192 }