Commit e3778811bb6ecd169ff3fc8bb677bf0a3fe6335b

Authored by jklontz
2 parents 27f6b386 54fc6ac9

Merge pull request #117 from biometrics/streamlining

Leverage stream to do enrollment more efficiently
openbr/core/core.cpp
... ... @@ -19,6 +19,7 @@
19 19 #include "bee.h"
20 20 #include "common.h"
21 21 #include "qtutils.h"
  22 +#include "../plugins/openbr_internal.h"
22 23  
23 24 using namespace br;
24 25  
... ... @@ -44,6 +45,14 @@ struct AlgorithmCore
44 45 qDebug("Training on %s%s", qPrintable(input.flat()),
45 46 model.isEmpty() ? "" : qPrintable(" to " + model));
46 47  
  48 + QScopedPointer<Transform> trainingWrapper(Transform::make("DirectStream([Identity])", NULL));
  49 + CompositeTransform * downcast = dynamic_cast<CompositeTransform *>(trainingWrapper.data());
  50 + if (downcast == NULL)
  51 + qFatal("downcast failed?");
  52 + downcast->transforms[0] = this->transform.data();
  53 +
  54 + downcast->init();
  55 +
47 56 TemplateList data(TemplateList::fromGallery(input));
48 57  
49 58 // set the Train bool metadata, in case a Transform's project
... ... @@ -56,7 +65,7 @@ struct AlgorithmCore
56 65  
57 66 QTime time; time.start();
58 67 qDebug("Training Enrollment");
59   - transform->train(data);
  68 + downcast->train(data);
60 69  
61 70 if (!distance.isNull()) {
62 71 qDebug("Projecting Enrollment");
... ... @@ -114,74 +123,64 @@ struct AlgorithmCore
114 123  
115 124 FileList enroll(File input, File gallery = File())
116 125 {
  126 + FileList files;
  127 +
117 128 qDebug("Enrolling %s%s", qPrintable(input.flat()),
118 129 gallery.isNull() ? "" : qPrintable(" to " + gallery.flat()));
119 130  
120   - FileList fileList;
121 131 if (gallery.name.isEmpty()) {
122 132 if (input.name.isEmpty()) return FileList();
123 133 else gallery = getMemoryGallery(input);
124 134 }
  135 + TemplateList data(TemplateList::fromGallery(input));
125 136  
126   - QScopedPointer<Gallery> g(Gallery::make(gallery));
127   - if (g.isNull()) qFatal("Null gallery!");
128   -
129   - do {
130   - fileList.clear();
131   -
132   - if (gallery.contains("read") || gallery.contains("cache"))
133   - fileList = g->files();
134   -
135   - if (!fileList.isEmpty() && gallery.contains("cache"))
136   - return fileList;
137   -
138   - const TemplateList i(TemplateList::fromGallery(input));
139   - if (i.isEmpty()) return fileList; // Nothing to enroll
140   -
141   - if (transform.isNull()) qFatal("Null transform.");
142   - const int blocks = Globals->blocks(i.size());
143   - Globals->currentStep = 0;
144   - Globals->totalSteps = i.size();
145   - Globals->startTime.start();
146   -
147   - const bool noDuplicates = gallery.contains("noDuplicates");
148   - QStringList fileNames = noDuplicates ? fileList.names() : QStringList();
149   - const int subBlockSize = 4*std::max(1, Globals->parallelism);
150   - const int numSubBlocks = ceil(1.0*Globals->blockSize/subBlockSize);
151   - int totalCount = 0, failureCount = 0;
152   - double totalBytes = 0;
153   - for (int block=0; block<blocks; block++) {
154   - for (int subBlock = 0; subBlock<numSubBlocks; subBlock++) {
155   - TemplateList data = i.mid(block*Globals->blockSize + subBlock*subBlockSize, subBlockSize);
156   - if (data.isEmpty()) break;
157   - if (noDuplicates)
158   - for (int i=data.size()-1; i>=0; i--)
159   - if (fileNames.contains(data[i].file.name))
160   - data.removeAt(i);
161   - const int numFiles = data.size();
162   -
163   - data >> *transform;
164   -
165   - g->writeBlock(data);
166   - const FileList newFiles = data.files();
167   - fileList.append(newFiles);
168   -
169   - totalCount += newFiles.size();
170   - failureCount += newFiles.failures();
171   - totalBytes += data.bytes<double>();
172   - Globals->currentStep += numFiles;
173   - Globals->printStatus();
  137 + if (gallery.contains("append"))
  138 + {
  139 + // Remove any templates which are already in the gallery
  140 + QScopedPointer<Gallery> g(Gallery::make(gallery));
  141 + files = g->files();
  142 + QSet<QString> nameSet = QSet<QString>::fromList(files.names());
  143 + for (int i = data.size() - 1; i>=0; i--) {
  144 + if (nameSet.contains(data[i].file.name))
  145 + {
  146 + data.removeAt(i);
174 147 }
175 148 }
  149 + }
  150 +
  151 + if (data.empty())
  152 + return files;
  153 +
  154 + // Trust me, this makes complete sense.
  155 + // We're just going to make a pipe with a placeholder first transform
  156 + QString pipeDesc = "Identity+GalleryOutput("+gallery.flat()+")+ProgressCounter("+QString::number(data.length())+")+Discard";
  157 + QScopedPointer<Transform> basePipe(Transform::make(pipeDesc,NULL));
  158 +
  159 + CompositeTransform * downcast = dynamic_cast<CompositeTransform *>(basePipe.data());
  160 + if (downcast == NULL)
  161 + qFatal("downcast failed?");
  162 +
  163 + // replace that placeholder with the current algorithm
  164 + downcast->transforms[0] = this->transform.data();
  165 +
  166 + // call init on the pipe to collapse the algorithm (if its top level is a pipe)
  167 + downcast->init();
  168 +
  169 + // Next, we make a Stream (with placeholder transform)
  170 + QString streamDesc = "Stream(Identity, readMode=DistributeFrames)";
  171 + QScopedPointer<Transform> baseStream(Transform::make(streamDesc, NULL));
  172 + WrapperTransform * wrapper = dynamic_cast<WrapperTransform *> (baseStream.data());
  173 +
  174 + // replace that placeholder with the pipe we built
  175 + wrapper->transform = downcast;
  176 +
  177 + // and get the final stream's stages by reinterpreting the pipe. Perfectly straightforward.
  178 + wrapper->init();
176 179  
177   - const float speed = 1000 * Globals->totalSteps / Globals->startTime.elapsed() / std::max(1, abs(Globals->parallelism));
178   - if (!Globals->quiet && (Globals->totalSteps > 1))
179   - fprintf(stderr, "\rTIME ELAPSED (MINS) %f SPEED=%.1e SIZE=%.4g FAILURES=%d/%d \n",
180   - Globals->startTime.elapsed()/1000./60.,speed, totalBytes/totalCount, failureCount, totalCount);
181   - Globals->totalSteps = 0;
182   - } while (input.getBool("infinite"));
  180 + wrapper->projectUpdate(data,data);
183 181  
184   - return fileList;
  182 + files.append(data.files());
  183 + return files;
185 184 }
186 185  
187 186 void enroll(TemplateList &data)
... ... @@ -312,8 +311,6 @@ private:
312 311 if ((words.size() < 1) || (words.size() > 2)) qFatal("Invalid algorithm format.");
313 312 //! [Parsing the algorithm description]
314 313  
315   - if (description.getBool("distribute", true))
316   - words[0] = "DistributeTemplate(" + words[0] + ")";
317 314  
318 315 //! [Creating the template generation and comparison methods]
319 316 transform = QSharedPointer<Transform>(Transform::make(words[0], NULL));
... ...
openbr/core/qtutils.cpp
... ... @@ -427,6 +427,17 @@ QString toString(const QVariantList &amp;variantList)
427 427 return QString();
428 428 }
429 429  
  430 +QString toTime(int s)
  431 +{
  432 + int h = s / (60*60);
  433 + int m = (s - h*60*60) / 60;
  434 + s = (s - h*60*60 - m*60);
  435 +
  436 + const QChar fillChar = QLatin1Char('0');
  437 +
  438 + return QString("%1:%2:%3").arg(h,2,10,fillChar).arg(m,2,10,fillChar).arg(s,2,10,fillChar);
  439 +}
  440 +
430 441 float euclideanLength(const QPointF &point)
431 442 {
432 443 return sqrt(pow(point.x(), 2) + pow(point.y(), 2));
... ...
openbr/core/qtutils.h
... ... @@ -65,6 +65,7 @@ namespace QtUtils
65 65 QPointF toPoint(const QString &string, bool *ok = NULL);
66 66 QRectF toRect(const QString &string, bool *ok = NULL);
67 67 QStringList naturalSort(const QStringList &strings);
  68 + QString toTime(int s);
68 69  
69 70 /**** Process Utilities ****/
70 71 bool runRScript(const QString &file);
... ...
openbr/openbr_plugin.cpp
... ... @@ -839,10 +839,7 @@ void br::Context::printStatus()
839 839 const float p = progress();
840 840 if (p < 1) {
841 841 int s = timeRemaining();
842   - int h = s / (60*60);
843   - int m = (s - h*60*60) / 60;
844   - s = (s - h*60*60 - m*60);
845   - fprintf(stderr, "%05.2f%% REMAINING=%02d:%02d:%02d COUNT=%g \r", 100 * p, h, m, s, totalSteps);
  842 + fprintf(stderr, "%05.2f%% REMAINING=%s COUNT=%g \r", 100 * p, QtUtils::toTime(s/1000.0f).toStdString().c_str(), totalSteps);
846 843 }
847 844 }
848 845  
... ... @@ -1156,6 +1153,17 @@ Gallery *Gallery::make(const File &amp;file)
1156 1153 return gallery;
1157 1154 }
1158 1155  
  1156 +// Default init -- if the file contains "append", read the existing
  1157 +// data and immediately write it
  1158 +void Gallery::init()
  1159 +{
  1160 + if (file.exists() && file.contains("append"))
  1161 + {
  1162 + TemplateList data = this->read();
  1163 + this->writeBlock(data);
  1164 + }
  1165 +}
  1166 +
1159 1167 /* Transform - public methods */
1160 1168 Transform::Transform(bool _independent, bool _trainable)
1161 1169 {
... ...
openbr/openbr_plugin.h
... ... @@ -1060,6 +1060,7 @@ public:
1060 1060 void writeBlock(const TemplateList &templates); /*!< \brief Serialize a template list. */
1061 1061 virtual void write(const Template &t) = 0; /*!< \brief Serialize a template. */
1062 1062 static Gallery *make(const File &file); /*!< \brief Make a gallery to/from a file on disk. */
  1063 + void init();
1063 1064  
1064 1065 private:
1065 1066 QSharedPointer<Gallery> next;
... ...
openbr/plugins/algorithms.cpp
... ... @@ -38,7 +38,7 @@ class AlgorithmsInitializer : public Initializer
38 38 Globals->abbreviations.insert("MedianFace", "Open!Cascade(FrontalFace)+ASEFEyes+Affine(256,256,0.37,0.45)+Center(Median)");
39 39 Globals->abbreviations.insert("BlurredFaceDetection", "Open+LimitSize(1024)+SkinMask/(Cvt(Gray)+GradientMask)+And+Morph(Erode,16)+LargestConvexArea");
40 40 Globals->abbreviations.insert("DrawFaceDetection", "Open+Cascade(FrontalFace)!ASEFEyes+Draw");
41   - Globals->abbreviations.insert("ShowFaceDetection", "DrawFaceDetection!Show[distribute=false]");
  41 + Globals->abbreviations.insert("ShowFaceDetection", "DrawFaceDetection!Show");
42 42 Globals->abbreviations.insert("OpenBR", "FaceRecognition");
43 43 Globals->abbreviations.insert("GenderEstimation", "GenderClassification");
44 44 Globals->abbreviations.insert("AgeEstimation", "AgeRegression");
... ...
openbr/plugins/gallery.cpp
... ... @@ -73,6 +73,11 @@ class arffGallery : public Gallery
73 73 arffFile.write(qPrintable(OpenCVUtils::matrixToStringList(t).join(',')));
74 74 arffFile.write(qPrintable(",'" + t.file.get<QString>("Label") + "'\n"));
75 75 }
  76 +
  77 + void init()
  78 + {
  79 + //
  80 + }
76 81 };
77 82  
78 83 BR_REGISTER(Gallery, arffGallery)
... ... @@ -94,7 +99,12 @@ class galGallery : public Gallery
94 99 if (file.get<bool>("remove", false))
95 100 gallery.remove();
96 101 QtUtils::touchDir(gallery);
97   - if (!gallery.open(QFile::ReadWrite | QFile::Append))
  102 + QFile::OpenMode mode = QFile::ReadWrite;
  103 +
  104 + if (file.contains("append"))
  105 + mode |= QFile::Append;
  106 +
  107 + if (!gallery.open(mode))
98 108 qFatal("Can't open gallery: %s", qPrintable(gallery.fileName()));
99 109 stream.setDevice(&gallery);
100 110 }
... ... @@ -579,6 +589,11 @@ class templateGallery : public Gallery
579 589 (void) t;
580 590 qFatal("No supported.");
581 591 }
  592 +
  593 + void init()
  594 + {
  595 + //
  596 + }
582 597 };
583 598  
584 599 BR_REGISTER(Gallery, templateGallery)
... ... @@ -737,6 +752,11 @@ class dbGallery : public Gallery
737 752 (void) t;
738 753 qFatal("Not supported.");
739 754 }
  755 +
  756 + void init()
  757 + {
  758 + //
  759 + }
740 760 };
741 761  
742 762 BR_REGISTER(Gallery, dbGallery)
... ... @@ -790,6 +810,11 @@ class googleGallery : public Gallery
790 810 (void) t;
791 811 qFatal("Not supported.");
792 812 }
  813 +
  814 + void init()
  815 + {
  816 + //
  817 + }
793 818 };
794 819  
795 820 BR_REGISTER(Gallery, googleGallery)
... ... @@ -883,6 +908,11 @@ class FDDBGallery : public Gallery
883 908 (void) t;
884 909 qFatal("Not implemented.");
885 910 }
  911 +
  912 + void init()
  913 + {
  914 + //
  915 + }
886 916 };
887 917  
888 918 BR_REGISTER(Gallery, FDDBGallery)
... ... @@ -927,6 +957,11 @@ class landmarksGallery : public Gallery
927 957 (void) t;
928 958 qFatal("Not implemented.");
929 959 }
  960 +
  961 + void init()
  962 + {
  963 + //
  964 + }
930 965 };
931 966  
932 967 BR_REGISTER(Gallery, landmarksGallery)
... ...
openbr/plugins/misc.cpp
... ... @@ -14,10 +14,12 @@
14 14 * limitations under the License. *
15 15 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
16 16  
  17 +#include <QElapsedTimer>
17 18 #include <QRegularExpression>
18 19 #include <opencv2/highgui/highgui.hpp>
19 20 #include "openbr_internal.h"
20 21 #include "openbr/core/opencvutils.h"
  22 +#include "openbr/core/qtutils.h"
21 23  
22 24 using namespace cv;
23 25  
... ... @@ -517,6 +519,106 @@ class EventTransform : public UntrainableMetaTransform
517 519 };
518 520 BR_REGISTER(Transform, EventTransform)
519 521  
  522 +
  523 +class GalleryOutputTransform : public TimeVaryingTransform
  524 +{
  525 + Q_OBJECT
  526 +
  527 + Q_PROPERTY(QString outputString READ get_outputString WRITE set_outputString RESET reset_outputString STORED false)
  528 + BR_PROPERTY(QString, outputString, "")
  529 +
  530 + void projectUpdate(const TemplateList &src, TemplateList &dst)
  531 + {
  532 + if (src.empty())
  533 + return;
  534 + dst = src;
  535 + writer->writeBlock(dst);
  536 + }
  537 +
  538 + void train(const TemplateList& data)
  539 + {
  540 + (void) data;
  541 + }
  542 + ;
  543 + void init()
  544 + {
  545 + writer = QSharedPointer<Gallery>(Gallery::make(outputString));
  546 + }
  547 +
  548 + QSharedPointer<Gallery> writer;
  549 +public:
  550 + GalleryOutputTransform() : TimeVaryingTransform(false,false) {}
  551 +};
  552 +
  553 +BR_REGISTER(Transform, GalleryOutputTransform)
  554 +
  555 +class ProgressCounterTransform : public TimeVaryingTransform
  556 +{
  557 + Q_OBJECT
  558 +
  559 + Q_PROPERTY(int totalTemplates READ get_totalTemplates WRITE set_totalTemplates RESET reset_totalTemplates STORED false)
  560 + BR_PROPERTY(int, totalTemplates, 1)
  561 +
  562 + void projectUpdate(const TemplateList &src, TemplateList &dst)
  563 + {
  564 + dst = src;
  565 + qint64 elapsed = timer.elapsed();
  566 + calls++;
  567 + set_calls++;
  568 + // updated every 10 seconds
  569 + if (elapsed > 5 * 1000) {
  570 + float f_elapsed = elapsed / 1000.0f;
  571 + // remaining calls (according to our input variable)
  572 + int remaining = totalTemplates - calls;
  573 + // calls / second
  574 + float speed = set_calls / f_elapsed;
  575 +
  576 + float p = 100 * float(calls) / totalTemplates;
  577 +
  578 + // seconds remaining
  579 + int s = float(remaining) / speed;
  580 +
  581 + fprintf(stderr, "%05.2f%% ELAPSED=%s REMAINING=%s COUNT=%g \r", p, QtUtils::toTime(Globals->startTime.elapsed()/1000.0f).toStdString().c_str(), QtUtils::toTime(s).toStdString().c_str(), float(calls));
  582 +
  583 + timer.start();
  584 + set_calls = 0;
  585 + }
  586 +
  587 +
  588 + return;
  589 + }
  590 +
  591 + void train(const TemplateList& data)
  592 + {
  593 + (void) data;
  594 + }
  595 +
  596 + void finalize(TemplateList & data)
  597 + {
  598 + (void) data;
  599 + float p = 100 * float(calls) / totalTemplates;
  600 + qDebug("%05.2f%% ELAPSED=%s REMAINING=%s COUNT=%g \r", p, QtUtils::toTime(Globals->startTime.elapsed()/1000.0f).toStdString().c_str(), QtUtils::toTime(0).toStdString().c_str(), float(calls));
  601 + }
  602 +
  603 + void init()
  604 + {
  605 + calls = 0;
  606 + set_calls = 0;
  607 + timer.start();
  608 + Globals->startTime.start();
  609 + }
  610 +
  611 +public:
  612 + ProgressCounterTransform() : TimeVaryingTransform(false,false) {}
  613 + bool initialized;
  614 + QElapsedTimer timer;
  615 + qint64 calls;
  616 + qint64 set_calls;
  617 +
  618 +};
  619 +
  620 +BR_REGISTER(Transform, ProgressCounterTransform)
  621 +
520 622 }
521 623  
522 624 #include "misc.moc"
... ...
openbr/plugins/openbr_internal.h
... ... @@ -154,6 +154,53 @@ protected:
154 154 }
155 155 };
156 156  
  157 +/*!
  158 + * \brief Interface for transforms that act as decorators of another transform
  159 + */
  160 +class BR_EXPORT WrapperTransform : public TimeVaryingTransform
  161 +{
  162 + Q_OBJECT
  163 +public:
  164 + WrapperTransform(bool independent = true) : TimeVaryingTransform(independent)
  165 + {
  166 + }
  167 +
  168 + Q_PROPERTY(br::Transform *transform READ get_transform WRITE set_transform RESET reset_transform STORED false)
  169 + BR_PROPERTY(br::Transform *, transform, NULL)
  170 +
  171 + bool timeVarying() const { return transform->timeVarying(); }
  172 +
  173 + void project(const Template &src, Template &dst) const
  174 + {
  175 + transform->project(src,dst);
  176 + }
  177 +
  178 + void projectUpdate(const Template &src, Template &dst)
  179 + {
  180 + transform->projectUpdate(src,dst);
  181 + }
  182 + void projectUpdate(const TemplateList & src, TemplateList & dst)
  183 + {
  184 + transform->projectUpdate(src,dst);
  185 + }
  186 +
  187 + void train(const QList<TemplateList> & data)
  188 + {
  189 + transform->train(data);
  190 + }
  191 +
  192 + virtual void finalize(TemplateList & output)
  193 + {
  194 + transform->finalize(output);
  195 + }
  196 +
  197 + void init()
  198 + {
  199 + if (transform)
  200 + this->trainable = transform->trainable;
  201 + }
  202 +
  203 +};
157 204  
158 205 /*!
159 206 * \brief A MetaTransform that aggregates some sub-transforms
... ...
openbr/plugins/stream.cpp
... ... @@ -16,6 +16,17 @@ using namespace cv;
16 16 namespace br
17 17 {
18 18  
  19 +class Idiocy : public QObject
  20 +{
  21 + Q_OBJECT
  22 +public:
  23 + enum StreamModes { StreamVideo,
  24 + DistributeFrames,
  25 + Auto};
  26 +
  27 + Q_ENUMS(StreamModes)
  28 +};
  29 +
19 30 class FrameData
20 31 {
21 32 public:
... ... @@ -180,198 +191,29 @@ private:
180 191 QList<FrameData *> buffer2;
181 192 };
182 193  
183   -
184   -// Interface for sequentially getting data from some data source.
185   -// Initialized off of a template, can represent a video file (stored in the template's filename)
186   -// or a set of images already loaded into memory stored as multiple matrices in an input template.
187   -class DataSource
  194 +// Given a template as input, return N templates as output, one at a time on subsequent
  195 +// calls to getNext
  196 +class TemplateProcessor
188 197 {
189 198 public:
190   - DataSource(int maxFrames=500)
191   - {
192   - // The sequence number of the last frame
193   - final_frame = -1;
194   - for (int i=0; i < maxFrames;i++)
195   - {
196   - allFrames.addItem(new FrameData());
197   - }
198   - }
199   -
200   - virtual ~DataSource()
201   - {
202   - while (true)
203   - {
204   - FrameData * frame = allFrames.tryGetItem();
205   - if (frame == NULL)
206   - break;
207   - delete frame;
208   - }
209   - }
210   -
211   - // non-blocking version of getFrame
212   - // Returns a NULL FrameData if too many frames are out, or the
213   - // data source is broken. Sets last_frame to true iff the FrameData
214   - // returned is the last valid frame, and the data source is now broken.
215   - FrameData * tryGetFrame(bool & last_frame)
216   - {
217   - last_frame = false;
218   -
219   - if (is_broken) {
220   - return NULL;
221   - }
222   -
223   - // Try to get a FrameData from the pool, if we can't it means too many
224   - // frames are already out, and we will return NULL to indicate failure
225   - FrameData * aFrame = allFrames.tryGetItem();
226   - if (aFrame == NULL)
227   - return NULL;
228   -
229   - // Try to actually read a frame, if this returns false the data source is broken
230   - bool res = getNext(*aFrame);
231   -
232   - // The datasource broke, update final_frame
233   - if (!res)
234   - {
235   - QMutexLocker lock(&last_frame_update);
236   - final_frame = lookAhead.back()->sequenceNumber;
237   - allFrames.addItem(aFrame);
238   - }
239   - else {
240   - lookAhead.push_back(aFrame);
241   - }
242   -
243   - // we will return the first frame on the lookAhead buffer
244   - FrameData * rVal = lookAhead.first();
245   - lookAhead.pop_front();
246   -
247   - // If this is the last frame, say so
248   - if (rVal->sequenceNumber == final_frame) {
249   - last_frame = true;
250   - is_broken = true;
251   - }
252   -
253   - return rVal;
254   - }
255   -
256   - // Return a frame to the pool, returns true if the frame returned was the last
257   - // frame issued, false otherwise
258   - bool returnFrame(FrameData * inputFrame)
259   - {
260   - int frameNumber = inputFrame->sequenceNumber;
261   -
262   - inputFrame->data.clear();
263   - inputFrame->sequenceNumber = -1;
264   - allFrames.addItem(inputFrame);
265   -
266   - bool rval = false;
267   -
268   - QMutexLocker lock(&last_frame_update);
269   -
270   - if (frameNumber == final_frame) {
271   - // We just received the last frame, better pulse
272   - allReturned = true;
273   - lastReturned.wakeAll();
274   - rval = true;
275   - }
276   -
277   - return rval;
278   - }
279   -
280   - bool waitLast()
281   - {
282   - QMutexLocker lock(&last_frame_update);
283   -
284   - while (!allReturned)
285   - {
286   - // This would be a safer wait if we used a timeout, but
287   - // theoretically that should never matter.
288   - lastReturned.wait(&last_frame_update);
289   - }
290   - return true;
291   - }
292   -
293   - bool open(Template & output, int start_index = 0)
294   - {
295   - is_broken = false;
296   - allReturned = false;
297   -
298   - // The last frame isn't initialized yet
299   - final_frame = -1;
300   - // Start our sequence numbers from the input index
301   - next_sequence_number = start_index;
302   -
303   - // Actually open the data source
304   - bool open_res = concreteOpen(output);
305   -
306   - // We couldn't open the data source
307   - if (!open_res) {
308   - is_broken = true;
309   - return false;
310   - }
311   -
312   - // Try to get a frame from the global pool
313   - FrameData * firstFrame = allFrames.tryGetItem();
314   -
315   - // If this fails, things have gone pretty badly.
316   - if (firstFrame == NULL) {
317   - is_broken = true;
318   - return false;
319   - }
320   -
321   - // Read a frame from the video source
322   - bool res = getNext(*firstFrame);
323   -
324   - // the data source broke already, we couldn't even get one frame
325   - // from it even though it claimed to have opened successfully.
326   - if (!res) {
327   - is_broken = true;
328   - return false;
329   - }
330   -
331   - // We read one frame ahead of the last one returned, this allows
332   - // us to know which frame is the final frame when we return it.
333   - lookAhead.append(firstFrame);
334   - return true;
335   - }
336   -
337   - /*
338   - * Pure virtual methods
339   - */
340   -
341   - // isOpen doesn't appear to particularly work when used on opencv
342   - // VideoCaptures, so we don't use it for anything important.
  199 + virtual ~TemplateProcessor() {}
  200 + virtual bool open(Template & input)=0;
343 201 virtual bool isOpen()=0;
344   - // Called from open, open the data source specified by the input
345   - // template, don't worry about setting any of the state variables
346   - // set in open.
347   - virtual bool concreteOpen(Template & output) = 0;
348   - // Get the next frame from the data source, store the results in
349   - // FrameData (including the actual frame and appropriate sequence
350   - // number).
351   - virtual bool getNext(FrameData & input) = 0;
352   - // close the currently open data source.
353   - virtual void close() = 0;
354   -
355   - int next_sequence_number;
  202 + virtual void close()=0;
  203 + virtual bool getNextTemplate(Template & output)=0;
356 204 protected:
357   - DoubleBuffer allFrames;
358   - int final_frame;
359   - bool is_broken;
360   - bool allReturned;
361   - QList<FrameData *> lookAhead;
362   -
363   - QWaitCondition lastReturned;
364   - QMutex last_frame_update;
  205 + Template basis;
365 206 };
366 207  
367 208 static QMutex openLock;
  209 +
368 210 // Read a video frame by frame using cv::VideoCapture
369   -class VideoDataSource : public DataSource
  211 +class VideoReader : public TemplateProcessor
370 212 {
371 213 public:
372   - VideoDataSource(int maxFrames) : DataSource(maxFrames) {}
  214 + VideoReader() {}
373 215  
374   - bool concreteOpen(Template &input)
  216 + bool open(Template &input)
375 217 {
376 218 basis = input;
377 219  
... ... @@ -407,30 +249,23 @@ public:
407 249  
408 250 bool isOpen() { return video.isOpened(); }
409 251  
410   - void close() {
411   - video.release();
412   - }
  252 + void close() { video.release(); }
413 253  
414   -private:
415   - bool getNext(FrameData & output)
  254 + bool getNextTemplate(Template & output)
416 255 {
417 256 if (!isOpen()) {
418 257 qDebug("video source is not open");
419 258 return false;
420 259 }
421   -
422   - output.data.append(Template(basis.file));
423   - output.data.last().m() = cv::Mat();
424   -
425   - output.sequenceNumber = next_sequence_number;
426   - next_sequence_number++;
  260 + output.file = basis.file;
  261 + output.m() = cv::Mat();
427 262  
428 263 cv::Mat temp;
429 264 bool res = video.read(temp);
430 265  
431 266 if (!res) {
432 267 // The video capture broke, return false.
433   - output.data.last().m() = cv::Mat();
  268 + output.m() = cv::Mat();
434 269 close();
435 270 return false;
436 271 }
... ... @@ -438,198 +273,330 @@ private:
438 273 // This clone is critical, if we don't do it then the matrix will
439 274 // be an alias of an internal buffer of the video source, leading
440 275 // to various problems later.
441   - output.data.last().m() = temp.clone();
442   -
443   - output.data.last().file.set("FrameNumber", output.sequenceNumber);
  276 + output.m() = temp.clone();
444 277 return true;
445 278 }
446   -
  279 +protected:
447 280 cv::VideoCapture video;
448   - Template basis;
449 281 };
450 282  
451   -// Given a template as input, return its matrices one by one on subsequent calls
452   -// to getNext
453   -class TemplateDataSource : public DataSource
  283 +
  284 +class DirectReturn : public TemplateProcessor
454 285 {
455 286 public:
456   - TemplateDataSource(int maxFrames) : DataSource(maxFrames)
  287 + DirectReturn()
457 288 {
458   - current_matrix_idx = INT_MAX;
459 289 data_ok = false;
460 290 }
461 291  
462   - // To "open" it we just set appropriate indices, we assume that if this
463   - // is an image, it is already loaded into memory.
464   - bool concreteOpen(Template &input)
  292 + // We don't do anything, just prepare to return input when getNext is called.
  293 + bool open(Template &input)
465 294 {
466 295 basis = input;
467   - current_matrix_idx = 0;
468   -
469   - data_ok = current_matrix_idx < basis.size();
  296 + data_ok =true;
470 297 return data_ok;
471 298 }
472 299  
473   - bool isOpen() {
474   - return data_ok;
475   - }
  300 + bool isOpen() { return data_ok; }
476 301  
477 302 void close()
478 303 {
479   - current_matrix_idx = INT_MAX;
  304 + data_ok = false;
480 305 basis.clear();
481 306 }
482 307  
483   -private:
484   - bool getNext(FrameData & output)
  308 + bool getNextTemplate(Template & output)
485 309 {
486   - data_ok = current_matrix_idx < basis.size();
487 310 if (!data_ok)
488 311 return false;
489   -
490   - output.data.append(basis[current_matrix_idx]);
491   - current_matrix_idx++;
492   -
493   - output.sequenceNumber = next_sequence_number;
494   - next_sequence_number++;
495   -
496   - output.data.last().file.set("FrameNumber", output.sequenceNumber);
  312 + output = basis;
  313 + data_ok = false;
497 314 return true;
498 315 }
499 316  
500   - Template basis;
501   - // Index of the next matrix to output from the template
502   - int current_matrix_idx;
503   -
504   - // is current_matrix_idx in bounds?
  317 +protected:
  318 + // Have we sent our template yet?
505 319 bool data_ok;
506 320 };
507 321  
508   -// Given a templatelist as input, create appropriate data source for each
509   -// individual template
510   -class DataSourceManager : public DataSource
  322 +
  323 +// Interface for sequentially getting data from some data source.
  324 +// Given a TemplateList, return single template frames sequentially by applying a TemplateProcessor
  325 +// to each individual template.
  326 +class DataSource
511 327 {
512 328 public:
513   - DataSourceManager(int activeFrames=100) : DataSource(activeFrames)
  329 + DataSource(int maxFrames=500)
514 330 {
515   - actualSource = NULL;
  331 + // The sequence number of the last frame
  332 + final_frame = -1;
  333 + for (int i=0; i < maxFrames;i++)
  334 + {
  335 + allFrames.addItem(new FrameData());
  336 + }
  337 + frameSource = NULL;
516 338 }
517 339  
518   - ~DataSourceManager()
  340 + virtual ~DataSource()
519 341 {
520   - close();
  342 + while (true)
  343 + {
  344 + FrameData * frame = allFrames.tryGetItem();
  345 + if (frame == NULL)
  346 + break;
  347 + delete frame;
  348 + }
521 349 }
522 350  
523   - int size()
  351 + void close()
524 352 {
525   - return this->allFrames.size();
  353 + if (this->frameSource)
  354 + {
  355 + frameSource->close();
  356 + delete frameSource;
  357 + frameSource = NULL;
  358 + }
526 359 }
527 360  
528   - void close()
  361 + int size()
529 362 {
530   - if (actualSource) {
531   - actualSource->close();
532   - delete actualSource;
533   - actualSource = NULL;
534   - }
  363 + return this->templates.size();
535 364 }
536 365  
537   - // We are used through a call to open(TemplateList)
538   - bool open(TemplateList & input)
  366 + bool open(TemplateList & input, br::Idiocy::StreamModes _mode)
539 367 {
540 368 // Set up variables specific to us
541 369 current_template_idx = 0;
542 370 templates = input;
  371 + mode = _mode;
  372 +
  373 + is_broken = false;
  374 + allReturned = false;
543 375  
544   - // Call datasourece::open on the first template to set up
545   - // state variables
546   - return DataSource::open(templates[current_template_idx]);
  376 + // The last frame isn't initialized yet
  377 + final_frame = -1;
  378 + // Start our sequence numbers from the input index
  379 + next_sequence_number = 0;
  380 +
  381 + // Actually open the data source
  382 + bool open_res = openNextTemplate();
  383 +
  384 + // We couldn't open the data source
  385 + if (!open_res) {
  386 + is_broken = true;
  387 + return false;
  388 + }
  389 +
  390 + // Try to get a frame from the global pool
  391 + FrameData * firstFrame = allFrames.tryGetItem();
  392 +
  393 + // If this fails, things have gone pretty badly.
  394 + if (firstFrame == NULL) {
  395 + is_broken = true;
  396 + return false;
  397 + }
  398 +
  399 + // Read a frame from the video source
  400 + bool res = getNextFrame(*firstFrame);
  401 +
  402 + // the data source broke already, we couldn't even get one frame
  403 + // from it even though it claimed to have opened successfully.
  404 + if (!res) {
  405 + is_broken = true;
  406 + return false;
  407 + }
  408 +
  409 + // We read one frame ahead of the last one returned, this allows
  410 + // us to know which frame is the final frame when we return it.
  411 + lookAhead.append(firstFrame);
  412 + return true;
547 413 }
548 414  
549   - // Create an actual data source of appropriate type for this template
550   - // (initially called via the call to DataSource::open, called later
551   - // as we run out of frames on our templates).
552   - bool concreteOpen(Template & input)
  415 +
  416 + // non-blocking version of getFrame
  417 + // Returns a NULL FrameData if too many frames are out, or the
  418 + // data source is broken. Sets last_frame to true iff the FrameData
  419 + // returned is the last valid frame, and the data source is now broken.
  420 + FrameData * tryGetFrame(bool & last_frame)
553 421 {
554   - close();
  422 + last_frame = false;
555 423  
556   - bool open_res = false;
557   - // Input has no matrices? Its probably a video that hasn't been loaded yet
558   - if (input.empty()) {
559   - actualSource = new VideoDataSource(0);
560   - open_res = actualSource->concreteOpen(input);
  424 + if (is_broken) {
  425 + return NULL;
  426 + }
  427 +
  428 + // Try to get a FrameData from the pool, if we can't it means too many
  429 + // frames are already out, and we will return NULL to indicate failure
  430 + FrameData * aFrame = allFrames.tryGetItem();
  431 + if (aFrame == NULL)
  432 + return NULL;
  433 +
  434 + // Try to actually read a frame, if this returns false the data source is broken
  435 + bool res = getNextFrame(*aFrame);
  436 +
  437 + // The datasource broke, update final_frame
  438 + if (!res)
  439 + {
  440 + QMutexLocker lock(&last_frame_update);
  441 + final_frame = lookAhead.back()->sequenceNumber;
  442 + allFrames.addItem(aFrame);
561 443 }
562   - // If the input is not empty, we assume it is a set of frames already
563   - // in memory.
564 444 else {
565   - actualSource = new TemplateDataSource(0);
566   - open_res = actualSource->concreteOpen(input);
  445 + lookAhead.push_back(aFrame);
567 446 }
568 447  
569   - // The data source failed to open
570   - if (!open_res) {
571   - delete actualSource;
572   - actualSource = NULL;
573   - return false;
  448 + // we will return the first frame on the lookAhead buffer
  449 + FrameData * rVal = lookAhead.first();
  450 + lookAhead.pop_front();
  451 + if (rVal->data.empty())
  452 + qDebug("returning empty frame from look ahead!");
  453 +
  454 + // If this is the last frame, say so
  455 + if (rVal->sequenceNumber == final_frame) {
  456 + last_frame = true;
  457 + is_broken = true;
574 458 }
575   - return true;
  459 +
  460 + return rVal;
576 461 }
577 462  
578   - bool isOpen() { return !actualSource ? false : actualSource->isOpen(); }
  463 + // Return a frame to the pool, returns true if the frame returned was the last
  464 + // frame issued, false otherwise
  465 + bool returnFrame(FrameData * inputFrame)
  466 + {
  467 + int frameNumber = inputFrame->sequenceNumber;
579 468  
580   -protected:
581   - // Index of the template in the templatelist we are currently reading from
582   - int current_template_idx;
  469 + inputFrame->data.clear();
  470 + inputFrame->sequenceNumber = -1;
  471 + allFrames.addItem(inputFrame);
583 472  
584   - TemplateList templates;
585   - DataSource * actualSource;
586   - // Get the next frame, if we run out of frames on the current template
587   - // move on to the next one.
588   - bool getNext(FrameData & output)
  473 + bool rval = false;
  474 +
  475 + QMutexLocker lock(&last_frame_update);
  476 +
  477 + if (frameNumber == final_frame) {
  478 + // We just received the last frame, better pulse
  479 + allReturned = true;
  480 + lastReturned.wakeAll();
  481 + rval = true;
  482 + }
  483 +
  484 + return rval;
  485 + }
  486 +
  487 + bool waitLast()
589 488 {
590   - bool res = actualSource->getNext(output);
591   - output.sequenceNumber = next_sequence_number;
592   -
593   - // OK we got a frame
594   - if (res) {
595   - // Override the sequence number set by actualSource
596   - output.data.last().file.set("FrameNumber", output.sequenceNumber);
597   - next_sequence_number++;
598   - if (output.data.last().last().empty())
599   - qDebug("broken matrix");
600   - return true;
  489 + QMutexLocker lock(&last_frame_update);
  490 +
  491 + while (!allReturned)
  492 + {
  493 + // This would be a safer wait if we used a timeout, but
  494 + // theoretically that should never matter.
  495 + lastReturned.wait(&last_frame_update);
601 496 }
  497 + return true;
  498 + }
602 499  
603   - // We didn't get a frame, try to move on to the next template.
604   - while(!res) {
605   - output.data.clear();
606   - current_template_idx++;
  500 +protected:
607 501  
608   - // No more templates? We're done
609   - if (current_template_idx >= templates.size())
610   - return false;
  502 + bool openNextTemplate()
  503 + {
  504 + if (this->current_template_idx >= this->templates.size())
  505 + return false;
611 506  
612   - // open the next data source
613   - bool open_res = concreteOpen(templates[current_template_idx]);
614   - // We couldn't open it, give up? We could maybe continue here
615   - // but don't currently.
616   - if (!open_res)
617   - return false;
  507 + bool open_res = false;
  508 + while (!open_res)
  509 + {
  510 + if (frameSource)
  511 + frameSource->close();
618 512  
619   - // get a frame from the newly opened data source, if that fails
620   - // we continue to open the next one.
621   - res = actualSource->getNext(output);
  513 + if (mode == br::Idiocy::Auto)
  514 + {
  515 + delete frameSource;
  516 + if (this->templates[this->current_template_idx].empty())
  517 + frameSource = new VideoReader();
  518 + else
  519 + frameSource = new DirectReturn();
  520 + }
  521 + else if (mode == br::Idiocy::DistributeFrames)
  522 + {
  523 + if (!frameSource)
  524 + frameSource = new DirectReturn();
  525 + }
  526 + else if (mode == br::Idiocy::StreamVideo)
  527 + {
  528 + if (!frameSource)
  529 + frameSource = new VideoReader();
  530 + }
  531 +
  532 + open_res = frameSource->open(this->templates[current_template_idx]);
  533 + if (!open_res)
  534 + {
  535 + current_template_idx++;
  536 + if (current_template_idx >= this->templates.size())
  537 + return false;
  538 + }
622 539 }
623   - // Finally, set the sequence number for the frame we actually return.
624   - output.sequenceNumber = next_sequence_number++;
625   - output.data.last().file.set("FrameNumber", output.sequenceNumber);
  540 + return true;
  541 + }
626 542  
627   - if (output.data.last().last().empty())
628   - qDebug("broken matrix");
  543 + bool getNextFrame(FrameData & output)
  544 + {
  545 + bool got_frame = false;
629 546  
630   - return res;
  547 + Template aTemplate;
  548 +
  549 + while (!got_frame)
  550 + {
  551 + got_frame = frameSource->getNextTemplate(aTemplate);
  552 +
  553 + // OK we got a frame
  554 + if (got_frame) {
  555 + // set the sequence number and tempalte of this frame
  556 + output.sequenceNumber = next_sequence_number;
  557 + output.data.append(aTemplate);
  558 + // set the frame number in the template's metadata
  559 + output.data.last().file.set("FrameNumber", output.sequenceNumber);
  560 + next_sequence_number++;
  561 + return true;
  562 + }
  563 +
  564 + // advance to the next tempalte in our list
  565 + this->current_template_idx++;
  566 + bool open_res = this->openNextTemplate();
  567 +
  568 + // couldn't get the next template? nothing to do, otherwise we try to read
  569 + // a frame at the top of this loop.
  570 + if (!open_res) {
  571 + return false;
  572 + }
  573 + }
  574 +
  575 + return false;
631 576 }
632 577  
  578 + // Index of the template in the templatelist we are currently reading from
  579 + int current_template_idx;
  580 +
  581 + // What do we do to each template
  582 + br::Idiocy::StreamModes mode;
  583 +
  584 + // list of templates we are workign from
  585 + TemplateList templates;
  586 +
  587 + // processor for the current template
  588 + TemplateProcessor * frameSource;
  589 +
  590 + int next_sequence_number;
  591 + int final_frame;
  592 + bool is_broken;
  593 + bool allReturned;
  594 +
  595 + DoubleBuffer allFrames;
  596 + QList<FrameData *> lookAhead;
  597 +
  598 + QWaitCondition lastReturned;
  599 + QMutex last_frame_update;
633 600 };
634 601  
635 602 class ProcessingStage;
... ... @@ -710,6 +677,7 @@ public:
710 677 if (input == NULL) {
711 678 qFatal("null input to multi-thread stage");
712 679 }
  680 +
713 681 input->data >> *transform;
714 682  
715 683 should_continue = nextStage->tryAcquireNextStage(input);
... ... @@ -861,13 +829,35 @@ public:
861 829  
862 830 };
863 831  
  832 +// Semi-functional, doesn't do anything productive outside of stream::train
  833 +class CollectSets : public TimeVaryingTransform
  834 +{
  835 + Q_OBJECT
  836 +public:
  837 + CollectSets() : TimeVaryingTransform(false, false) {}
  838 +
  839 + QList<TemplateList> sets;
  840 +
  841 + void projectUpdate(const TemplateList &src, TemplateList &dst)
  842 + {
  843 + (void) dst;
  844 + sets.append(src);
  845 + }
  846 +
  847 + void train(const TemplateList & data)
  848 + {
  849 + (void) data;
  850 + }
  851 +
  852 +};
  853 +
864 854 // This stage reads new frames from the data source.
865   -class FirstStage : public SingleThreadStage
  855 +class ReadStage : public SingleThreadStage
866 856 {
867 857 public:
868   - FirstStage(int activeFrames = 100) : SingleThreadStage(true), dataSource(activeFrames){ }
  858 + ReadStage(int activeFrames = 100) : SingleThreadStage(true), dataSource(activeFrames){ }
869 859  
870   - DataSourceManager dataSource;
  860 + DataSource dataSource;
871 861  
872 862 void reset()
873 863 {
... ... @@ -951,99 +941,17 @@ public:
951 941 void status(){
952 942 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());
953 943 }
954   -
955   -
956   -};
957   -
958   -// Appened to the end of a Stream's transform sequence. Collects the output
959   -// from each frame on a single templatelist
960   -class LastStage : public SingleThreadStage
961   -{
962   -public:
963   - LastStage(bool _prev_stage_variance) : SingleThreadStage(_prev_stage_variance) {}
964   - TemplateList getOutput()
965   - {
966   - return collectedOutput;
967   - }
968   -
969   -private:
970   - TemplateList collectedOutput;
971   -public:
972   -
973   - void reset()
974   - {
975   - collectedOutput.clear();
976   - SingleThreadStage::reset();
977   - }
978   -
979   - FrameData * run(FrameData * input, bool & should_continue)
980   - {
981   - if (input == NULL) {
982   - qFatal("NULL input to stage %d", this->stage_id);
983   - }
984   -
985   - if (input->sequenceNumber != next_target)
986   - {
987   - qFatal("out of order frames for stage %d, got %d expected %d", this->stage_id, input->sequenceNumber, this->next_target);
988   - }
989   - next_target = input->sequenceNumber + 1;
990   -
991   - // add the item to our output buffer
992   - collectedOutput.append(input->data);
993   -
994   - // Can we enter the read stage?
995   - should_continue = nextStage->tryAcquireNextStage(input);
996   -
997   - // Is there anything on our input buffer? If so we should start a thread
998   - // in this stage to process that frame.
999   - QWriteLocker lock(&statusLock);
1000   - FrameData * newItem = inputBuffer->tryGetItem();
1001   - if (!newItem)
1002   - {
1003   - this->currentStatus = STOPPING;
1004   - }
1005   - lock.unlock();
1006   -
1007   - if (newItem)
1008   - startThread(newItem);
1009   -
1010   - return input;
1011   - }
1012   -
1013   - void status(){
1014   - 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());
1015   - }
1016   -
1017   -};
1018   -
1019   -// Semi-functional, doesn't do anything productive outside of stream::train
1020   -class CollectSets : public TimeVaryingTransform
1021   -{
1022   - Q_OBJECT
1023   -public:
1024   - CollectSets() : TimeVaryingTransform(false, false) {}
1025   -
1026   - QList<TemplateList> sets;
1027   -
1028   - void projectUpdate(const TemplateList &src, TemplateList &dst)
1029   - {
1030   - (void) dst;
1031   - sets.append(src);
1032   - }
1033   -
1034   - void train(const TemplateList & data)
1035   - {
1036   - (void) data;
1037   - }
1038   -
1039 944 };
1040 945  
1041 946 class DirectStreamTransform : public CompositeTransform
1042 947 {
1043 948 Q_OBJECT
1044 949 public:
  950 +
1045 951 Q_PROPERTY(int activeFrames READ get_activeFrames WRITE set_activeFrames RESET reset_activeFrames)
  952 + Q_PROPERTY(br::Idiocy::StreamModes readMode READ get_readMode WRITE set_readMode RESET reset_readMode)
1046 953 BR_PROPERTY(int, activeFrames, 100)
  954 + BR_PROPERTY(br::Idiocy::StreamModes, readMode, br::Idiocy::Auto)
1047 955  
1048 956 friend class StreamTransfrom;
1049 957  
... ... @@ -1109,13 +1017,21 @@ public:
1109 1017 qFatal("whatever");
1110 1018 }
1111 1019  
  1020 +
  1021 + virtual void finalize(TemplateList & output)
  1022 + {
  1023 + (void) output;
  1024 + // Nothing in particular to do here, stream calls finalize
  1025 + // on all child transforms as part of projectUpdate
  1026 + }
  1027 +
1112 1028 // start processing, consider all templates in src a continuous
1113 1029 // 'video'
1114 1030 void projectUpdate(const TemplateList & src, TemplateList & dst)
1115 1031 {
1116 1032 dst = src;
1117 1033  
1118   - bool res = readStage->dataSource.open(dst);
  1034 + bool res = readStage->dataSource.open(dst,readMode);
1119 1035 if (!res) {
1120 1036 qDebug("stream failed to open %s", qPrintable(dst[0].file.name));
1121 1037 return;
... ... @@ -1149,6 +1065,8 @@ public:
1149 1065 {
1150 1066 TemplateList output_set;
1151 1067 transforms[i]->finalize(output_set);
  1068 + if (output_set.empty())
  1069 + continue;
1152 1070  
1153 1071 for (int j=i+1; j < transforms.size();j++)
1154 1072 {
... ... @@ -1159,7 +1077,12 @@ public:
1159 1077  
1160 1078 // dst is set to all output received by the final stage, along
1161 1079 // with anything output via the calls to finalize.
1162   - dst = collectionStage->getOutput();
  1080 + //dst = collectionStage->getOutput();
  1081 + foreach(const TemplateList & list, collector->sets) {
  1082 + dst.append(list);
  1083 + }
  1084 + collector->sets.clear();
  1085 +
1163 1086 dst.append(final_output);
1164 1087  
1165 1088 foreach(ProcessingStage * stage, processingStages) {
... ... @@ -1167,12 +1090,6 @@ public:
1167 1090 }
1168 1091 }
1169 1092  
1170   - virtual void finalize(TemplateList & output)
1171   - {
1172   - (void) output;
1173   - // Nothing in particular to do here, stream calls finalize
1174   - // on all child transforms as part of projectUpdate
1175   - }
1176 1093  
1177 1094 // Create and link stages
1178 1095 void init()
... ... @@ -1193,7 +1110,7 @@ public:
1193 1110 QMutexLocker poolLock(&poolsAccess);
1194 1111 QHash<QObject *, QThreadPool *>::Iterator it;
1195 1112 if (!pools.contains(this->parent())) {
1196   - it = pools.insert(this->parent(), new QThreadPool(this));
  1113 + it = pools.insert(this->parent(), new QThreadPool(this->parent()));
1197 1114 it.value()->setMaxThreadCount(Globals->parallelism);
1198 1115 }
1199 1116 else it = pools.find(this->parent());
... ... @@ -1202,6 +1119,7 @@ public:
1202 1119  
1203 1120 // Are our children time varying or not? This decides whether
1204 1121 // we run them in single threaded or multi threaded stages
  1122 + stage_variance.clear();
1205 1123 stage_variance.reserve(transforms.size());
1206 1124 foreach (const br::Transform *transform, transforms) {
1207 1125 stage_variance.append(transform->timeVarying());
... ... @@ -1209,7 +1127,7 @@ public:
1209 1127  
1210 1128 // Additionally, we have a separate stage responsible for reading
1211 1129 // frames from the data source
1212   - readStage = new FirstStage(activeFrames);
  1130 + readStage = new ReadStage(activeFrames);
1213 1131  
1214 1132 processingStages.push_back(readStage);
1215 1133 readStage->stage_id = 0;
... ... @@ -1244,7 +1162,10 @@ public:
1244 1162  
1245 1163 // We also have the last stage, which just puts the output of the
1246 1164 // previous stages on a template list.
1247   - collectionStage = new LastStage(prev_stage_variance);
  1165 + collectionStage = new SingleThreadStage(prev_stage_variance);
  1166 + collectionStage->transform = this->collector.data();
  1167 +
  1168 +
1248 1169 processingStages.append(collectionStage);
1249 1170 collectionStage->stage_id = next_stage_id;
1250 1171 collectionStage->stages = &this->processingStages;
... ... @@ -1258,6 +1179,11 @@ public:
1258 1179 collectionStage->nextStage = readStage;
1259 1180 }
1260 1181  
  1182 + DirectStreamTransform()
  1183 + {
  1184 + this->collector = QSharedPointer<CollectSets>(new CollectSets());
  1185 + }
  1186 +
1261 1187 ~DirectStreamTransform()
1262 1188 {
1263 1189 // Delete all the stages
... ... @@ -1270,8 +1196,9 @@ public:
1270 1196 protected:
1271 1197 QList<bool> stage_variance;
1272 1198  
1273   - FirstStage * readStage;
1274   - LastStage * collectionStage;
  1199 + ReadStage * readStage;
  1200 + SingleThreadStage * collectionStage;
  1201 + QSharedPointer<CollectSets> collector;
1275 1202  
1276 1203 QList<ProcessingStage *> processingStages;
1277 1204  
... ... @@ -1311,21 +1238,20 @@ QMutex DirectStreamTransform::poolsAccess;
1311 1238  
1312 1239 BR_REGISTER(Transform, DirectStreamTransform)
1313 1240  
1314   -;
1315   -
1316   -class StreamTransform : public TimeVaryingTransform
  1241 +class StreamTransform : public WrapperTransform
1317 1242 {
1318 1243 Q_OBJECT
1319 1244  
1320 1245 public:
1321   - StreamTransform() : TimeVaryingTransform(false)
  1246 + StreamTransform() : WrapperTransform(false)
1322 1247 {
1323 1248 }
1324 1249  
1325   - Q_PROPERTY(br::Transform *transform READ get_transform WRITE set_transform RESET reset_transform STORED false)
1326 1250 Q_PROPERTY(int activeFrames READ get_activeFrames WRITE set_activeFrames RESET reset_activeFrames)
1327   - BR_PROPERTY(br::Transform *, transform, NULL)
  1251 + Q_PROPERTY(br::Idiocy::StreamModes readMode READ get_readMode WRITE set_readMode RESET reset_readMode)
  1252 +
1328 1253 BR_PROPERTY(int, activeFrames, 100)
  1254 + BR_PROPERTY(br::Idiocy::StreamModes, readMode, br::Idiocy::Auto)
1329 1255  
1330 1256 bool timeVarying() const { return true; }
1331 1257  
... ... @@ -1366,6 +1292,7 @@ public:
1366 1292 basis.setParent(this->parent());
1367 1293 basis.transforms.clear();
1368 1294 basis.activeFrames = this->activeFrames;
  1295 + basis.readMode = this->readMode;
1369 1296  
1370 1297 // We need at least a CompositeTransform * to acess transform's children.
1371 1298 CompositeTransform * downcast = dynamic_cast<CompositeTransform *> (transform);
... ... @@ -1448,8 +1375,6 @@ private:
1448 1375  
1449 1376 BR_REGISTER(Transform, StreamTransform)
1450 1377  
1451   -
1452   -
1453 1378 } // namespace br
1454 1379  
1455 1380 #include "stream.moc"
... ...