Commit 7685e92fa6b8a203861b22aa90362d47c7553805

Authored by Josh Klontz
2 parents 6dc7d8cd e2a5bc7e

Merge pull request #191 from biometrics/streaming_gallery

Preliminary work on not loading complete galleries during enrollment
openbr/core/core.cpp
... ... @@ -134,55 +134,51 @@ struct AlgorithmCore
134 134 if (input.name.isEmpty()) return FileList();
135 135 else gallery = getMemoryGallery(input);
136 136 }
137   - TemplateList data(TemplateList::fromGallery(input));
138 137  
139 138 bool multiProcess = Globals->file.getBool("multiProcess", false);
  139 + bool fileExclusion = false;
140 140  
141   - if (gallery.contains("append"))
142   - {
143   - // Remove any templates which are already in the gallery
144   - QScopedPointer<Gallery> g(Gallery::make(gallery));
145   - files = g->files();
146   - QSet<QString> nameSet = QSet<QString>::fromList(files.names());
147   - for (int i = data.size() - 1; i>=0; i--) {
148   - if (nameSet.contains(data[i].file.name))
149   - {
150   - data.removeAt(i);
151   - }
152   - }
  141 + // In append mode, we will exclude any templates with filenames already present in the output gallery
  142 + if (gallery.contains("append") && gallery.exists() ) {
  143 + FileList::fromGallery(gallery,true);
  144 + fileExclusion = true;
153 145 }
154 146  
155   - if (data.empty())
156   - return files;
  147 + Gallery * temp = Gallery::make(input);
  148 + qint64 total = temp->totalSize();
157 149  
158   - // Store steps for ProgressCounter
159 150 Globals->currentStep = 0;
160   - Globals->totalSteps = data.length();
  151 + Globals->totalSteps = total;
161 152  
162 153 QScopedPointer<Transform> basePipe;
163 154  
164   - if (!multiProcess)
165   - {
166   - QString pipeDesc = "GalleryOutput("+gallery.flat()+")+ProgressCounter("+QString::number(data.length())+")+Discard";
  155 + QString pipeDesc = "GalleryOutput("+gallery.flat()+")+ProgressCounter("+QString::number(total)+")+Discard";
  156 +
  157 + if (!multiProcess) {
167 158 basePipe.reset(Transform::make(pipeDesc,NULL));
168 159 CompositeTransform * downcast = dynamic_cast<CompositeTransform *>(basePipe.data());
169   - if (downcast == NULL)
170   - qFatal("downcast failed?");
171 160  
172   - // replace that placeholder with the current algorithm
  161 + if (downcast == NULL) qFatal("downcast failed?");
  162 +
173 163 downcast->transforms.prepend(this->transform.data());
  164 + if (fileExclusion) {
  165 + Transform * temp = Transform::make("FileExclusion(" + gallery.flat() + ")", downcast);
  166 + downcast->transforms.prepend(temp);
  167 + }
174 168  
175 169 // call init on the pipe to collapse the algorithm (if its top level is a pipe)
176 170 downcast->init();
177 171 }
178   - else
179   - {
180   - QString pipeDesc = "ProcessWrapper("+transformString+")"+"+GalleryOutput("+gallery.flat()+")+ProgressCounter("+QString::number(data.length())+")+Discard";
  172 + else {
  173 + pipeDesc = "ProcessWrapper("+transformString+")"+pipeDesc;
  174 + if (fileExclusion)
  175 + pipeDesc = "FileExclusion(" + gallery.flat() +")" + pipeDesc;
  176 +
181 177 basePipe.reset(Transform::make(pipeDesc,NULL));
182 178 }
183 179  
184 180 // Next, we make a Stream (with placeholder transform)
185   - QString streamDesc = "Stream(readMode=DistributeFrames)";
  181 + QString streamDesc = "Stream(readMode=StreamGallery)";
186 182 QScopedPointer<Transform> baseStream(Transform::make(streamDesc, NULL));
187 183 WrapperTransform * wrapper = dynamic_cast<WrapperTransform *> (baseStream.data());
188 184  
... ... @@ -194,9 +190,11 @@ struct AlgorithmCore
194 190  
195 191 Globals->startTime.start();
196 192  
197   - wrapper->projectUpdate(data,data);
  193 + TemplateList data, output;
  194 + data.append(input);
  195 + wrapper->projectUpdate(data, output);
198 196  
199   - files.append(data.files());
  197 + files.append(output.files());
200 198  
201 199 return files;
202 200 }
... ... @@ -337,13 +335,9 @@ struct AlgorithmCore
337 335 // comparison against the smaller gallery (which will be enrolled, and stored in memory).
338 336 bool needEnrollRows = false;
339 337  
340   -
341   -
342   -
343 338 if (output.exists() && output.get<bool>("cache", false)) return;
344 339 if (queryGallery == ".") queryGallery = targetGallery;
345 340  
346   -
347 341 // To decide which gallery is larger, we need to read both, but at this point we just want the
348 342 // metadata, and don't need the enrolled matrices.
349 343 FileList targetMetadata;
... ... @@ -359,15 +353,21 @@ struct AlgorithmCore
359 353  
360 354 File rowGallery = queryGallery;
361 355 File colGallery = targetGallery;
362   - int rowSize = queryMetadata.size();
  356 + qint64 rowSize;
363 357  
  358 + Gallery * temp;
364 359 if (transposeMode)
365 360 {
366 361 rowGallery = targetGallery;
367 362 colGallery = queryGallery;
368   - rowSize = targetMetadata.size();
  363 + temp = Gallery::make(targetGallery);
369 364 }
370   -
  365 + else
  366 + {
  367 + temp = Gallery::make(queryGallery);
  368 + }
  369 + rowSize = temp->totalSize();
  370 + delete temp;
371 371  
372 372 // Is the column gallery already enrolled? We keep the enrolled column gallery in memory, and in multi-process
373 373 // mode, every worker process retains a copy of this gallery in memory. When not in multi-process mode, we can
... ... @@ -423,8 +423,6 @@ struct AlgorithmCore
423 423 // progress counting step.
424 424 // After the base algorithm is built, the whole thing will be run in a stream, so that I/O can be handled sequentially.
425 425  
426   -
427   -
428 426 // The actual comparison step is done by a GalleryCompare transform, which has a Distance, and a gallery as data.
429 427 // Incoming templates are compared against the templates in the gallery, and the output is the resulting score
430 428 // vector.
... ...
openbr/openbr_plugin.cpp
... ... @@ -865,14 +865,14 @@ void br::Context::printStatus()
865 865 const float p = progress();
866 866 if (p < 1) {
867 867 int s = timeRemaining();
868   - fprintf(stderr, "%05.2f%% ELAPSED=%s REMAINING=%s COUNT=%g/%g \r", p*100, QtUtils::toTime(Globals->startTime.elapsed()/1000.0f).toStdString().c_str(), QtUtils::toTime(s).toStdString().c_str(), Globals->currentStep, Globals->totalSteps);
  868 + fprintf(stderr,"%05.2f%% ELAPSED=%s REMAINING=%s COUNT=%g \r", p*100, QtUtils::toTime(Globals->startTime.elapsed()/1000.0f).toStdString().c_str(), QtUtils::toTime(s).toStdString().c_str(), Globals->currentStep);
869 869 }
870 870 }
871 871  
872 872 float br::Context::progress() const
873 873 {
874 874 if (totalSteps == 0) return -1;
875   - return currentStep / totalSteps;
  875 + return currentProgress / totalSteps;
876 876 }
877 877  
878 878 void br::Context::setProperty(const QString &key, const QString &value)
... ...
openbr/openbr_plugin.h
... ... @@ -687,6 +687,10 @@ public:
687 687 Q_PROPERTY(double currentStep READ get_currentStep WRITE set_currentStep RESET reset_currentStep)
688 688 BR_PROPERTY(double, currentStep, 0)
689 689  
  690 + Q_PROPERTY(double currentProgress READ get_currentProgress WRITE set_currentProgress RESET reset_currentProgress)
  691 + BR_PROPERTY(double, currentProgress, 0)
  692 +
  693 +
690 694 /*!
691 695 * \brief Used internally to compute progress() and timeRemaining().
692 696 */
... ... @@ -1095,6 +1099,9 @@ public:
1095 1099 static Gallery *make(const File &file); /*!< \brief Make a gallery to/from a file on disk. */
1096 1100 void init();
1097 1101  
  1102 + virtual qint64 totalSize() { return std::numeric_limits<qint64>::max(); }
  1103 + virtual qint64 position() { return 0; }
  1104 +
1098 1105 private:
1099 1106 QSharedPointer<Gallery> next;
1100 1107 };
... ...
openbr/plugins/gallery.cpp
... ... @@ -23,6 +23,7 @@
23 23 #include <QSqlError>
24 24 #include <QSqlQuery>
25 25 #include <QSqlRecord>
  26 +#include <QXmlStreamReader>
26 27 #endif // BR_EMBEDDED
27 28 #include <opencv2/highgui/highgui.hpp>
28 29 #include "openbr_internal.h"
... ... @@ -127,6 +128,7 @@ class galGallery : public Gallery
127 128 Template m;
128 129 stream >> m;
129 130 templates.append(m);
  131 + templates.last().file.set("progress", totalSize());
130 132 }
131 133  
132 134 *done = stream.atEnd();
... ... @@ -140,6 +142,17 @@ class galGallery : public Gallery
140 142  
141 143 stream << t;
142 144 }
  145 +
  146 + qint64 totalSize()
  147 + {
  148 + return gallery.size();
  149 + }
  150 +
  151 + qint64 position()
  152 + {
  153 + return gallery.pos();
  154 + }
  155 +
143 156 };
144 157  
145 158 BR_REGISTER(Gallery, galGallery)
... ... @@ -328,6 +341,7 @@ class memGallery : public Gallery
328 341 {
329 342 Q_OBJECT
330 343 int block;
  344 + qint64 gallerySize;
331 345  
332 346 void init()
333 347 {
... ... @@ -338,6 +352,7 @@ class memGallery : public Gallery
338 352 MemoryGalleries::galleries[file] = gallery->read();
339 353 align(MemoryGalleries::galleries[file]);
340 354 MemoryGalleries::aligned[file] = true;
  355 + gallerySize = MemoryGalleries::galleries[file].size();
341 356 }
342 357 }
343 358  
... ... @@ -349,6 +364,10 @@ class memGallery : public Gallery
349 364 }
350 365  
351 366 TemplateList templates = MemoryGalleries::galleries[file].mid(block*readBlockSize, readBlockSize);
  367 + for (qint64 i = 0; i < templates.size();i++) {
  368 + templates[i].file.set("progress", i + block * readBlockSize);
  369 + }
  370 +
352 371 *done = (templates.size() < readBlockSize);
353 372 block = *done ? 0 : block+1;
354 373 return templates;
... ... @@ -389,6 +408,16 @@ class memGallery : public Gallery
389 408 templates.alignedData = alignedData;
390 409 }
391 410  
  411 + qint64 totalSize()
  412 + {
  413 + return gallerySize;
  414 + }
  415 +
  416 + qint64 position()
  417 + {
  418 + return block * readBlockSize;
  419 + }
  420 +
392 421 };
393 422  
394 423 BR_REGISTER(Gallery, memGallery)
... ... @@ -449,16 +478,19 @@ FileList FileList::fromGallery(const File &amp; file, bool cache)
449 478 *
450 479 * \see txtGallery
451 480 */
452   -class csvGallery : public Gallery
  481 +class csvGallery : public FileGallery
453 482 {
454 483 Q_OBJECT
455 484 Q_PROPERTY(int fileIndex READ get_fileIndex WRITE set_fileIndex RESET reset_fileIndex)
456 485 BR_PROPERTY(int, fileIndex, 0)
457 486  
458 487 FileList files;
  488 + QStringList headers;
459 489  
460 490 ~csvGallery()
461 491 {
  492 + f.close();
  493 +
462 494 if (files.isEmpty()) return;
463 495  
464 496 QMap<QString,QVariant> samples;
... ... @@ -496,25 +528,37 @@ class csvGallery : public Gallery
496 528  
497 529 TemplateList readBlock(bool *done)
498 530 {
499   - *done = true;
  531 + *done = false;
500 532 TemplateList templates;
501   - if (!file.exists()) return templates;
502   -
503   - QStringList lines = QtUtils::readLines(file);
  533 + if (!file.exists()) {
  534 + *done = true;
  535 + return templates;
  536 + }
504 537 QRegExp regexp("\\s*,\\s*");
505   - QStringList headers;
506   - if (!lines.isEmpty()) headers = lines.takeFirst().split(regexp);
507 538  
508   - foreach (const QString &line, lines) {
  539 + if (f.pos() == 0)
  540 + {
  541 + // read a line
  542 + QByteArray lineBytes = f.readLine();
  543 + QString line = QString::fromLocal8Bit(lineBytes).trimmed();
  544 + headers = line.split(regexp);
  545 + }
  546 +
  547 + for (qint64 i = 0; i < this->readBlockSize && !f.atEnd(); i++){
  548 + QByteArray lineBytes = f.readLine();
  549 + QString line = QString::fromLocal8Bit(lineBytes).trimmed();
  550 +
509 551 QStringList words = line.split(regexp);
510 552 if (words.size() != headers.size()) continue;
511   - File f;
512   - for (int i=0; i<words.size(); i++) {
513   - if (i == 0) f.name = words[i];
514   - else f.set(headers[i], words[i]);
  553 + File fi;
  554 + for (int j=0; j<words.size(); j++) {
  555 + if (j == 0) fi.name = words[j];
  556 + else fi.set(headers[j], words[j]);
515 557 }
516   - templates.append(f);
  558 + templates.append(fi);
  559 + templates.last().file.set("progress", f.pos());
517 560 }
  561 + *done = f.atEnd();
518 562  
519 563 return templates;
520 564 }
... ... @@ -568,18 +612,12 @@ BR_REGISTER(Gallery, csvGallery)
568 612 \endverbatim
569 613 * \see csvGallery
570 614 */
571   -class txtGallery : public Gallery
  615 +class txtGallery : public FileGallery
572 616 {
573 617 Q_OBJECT
574 618 Q_PROPERTY(QString label READ get_label WRITE set_label RESET reset_label STORED false)
575 619 BR_PROPERTY(QString, label, "")
576 620  
577   - QFile f;
578   - ~txtGallery()
579   - {
580   - f.close();
581   - }
582   -
583 621 TemplateList readBlock(bool *done)
584 622 {
585 623 *done = false;
... ... @@ -597,6 +635,7 @@ class txtGallery : public Gallery
597 635 int splitIndex = line.lastIndexOf(' ');
598 636 if (splitIndex == -1) templates.append(File(line));
599 637 else templates.append(File(line.mid(0, splitIndex), line.mid(splitIndex+1)));
  638 + templates.last().file.set("progress", this->position());
600 639 }
601 640  
602 641 if (f.atEnd()) {
... ... @@ -608,14 +647,6 @@ class txtGallery : public Gallery
608 647 return templates;
609 648 }
610 649  
611   - void init()
612   - {
613   - f.setFileName(file);
614   - QtUtils::touchDir(f);
615   - if (!f.open(QFile::ReadWrite))
616   - qFatal("Failed to open %s for read/write.", qPrintable(file));
617   - }
618   -
619 650 void write(const Template &t)
620 651 {
621 652 QString line = t.file.name;
... ... @@ -627,21 +658,16 @@ class txtGallery : public Gallery
627 658 };
628 659  
629 660 BR_REGISTER(Gallery, txtGallery)
  661 +
630 662 /*!
631 663 * \ingroup galleries
632 664 * \brief Treats each line as a call to File::flat()
633 665 * \author Josh Klontz \cite jklontz
634 666 */
635   -class flatGallery : public Gallery
  667 +class flatGallery : public FileGallery
636 668 {
637 669 Q_OBJECT
638 670  
639   - QFile f;
640   - ~flatGallery()
641   - {
642   - f.close();
643   - }
644   -
645 671 TemplateList readBlock(bool *done)
646 672 {
647 673 *done = false;
... ... @@ -654,8 +680,10 @@ class flatGallery : public Gallery
654 680 {
655 681 QByteArray line = f.readLine();
656 682  
657   - if (!line.isEmpty())
  683 + if (!line.isEmpty()) {
658 684 templates.append(File(QString::fromLocal8Bit(line).trimmed()));
  685 + templates.last().file.set("progress", this->position());
  686 + }
659 687  
660 688 if (f.atEnd()) {
661 689 *done=true;
... ... @@ -666,15 +694,6 @@ class flatGallery : public Gallery
666 694 return templates;
667 695 }
668 696  
669   - void init()
670   - {
671   - f.setFileName(file);
672   - QtUtils::touchDir(f);
673   - if (!f.open(QFile::ReadWrite))
674   - qFatal("Failed to open %s for read/write.", qPrintable(file));
675   -
676   - }
677   -
678 697 void write(const Template &t)
679 698 {
680 699 f.write((t.file.flat()+"\n").toLocal8Bit() );
... ... @@ -688,29 +707,140 @@ BR_REGISTER(Gallery, flatGallery)
688 707 * \brief A \ref sigset input.
689 708 * \author Josh Klontz \cite jklontz
690 709 */
691   -class xmlGallery : public Gallery
  710 +class xmlGallery : public FileGallery
692 711 {
693 712 Q_OBJECT
694 713 Q_PROPERTY(bool ignoreMetadata READ get_ignoreMetadata WRITE set_ignoreMetadata RESET reset_ignoreMetadata STORED false)
695 714 BR_PROPERTY(bool, ignoreMetadata, false)
696 715 FileList files;
697 716  
  717 + QXmlStreamReader reader;
  718 +
  719 + QString currentSignatureName;
  720 + bool signatureActive;
  721 +
698 722 ~xmlGallery()
699 723 {
  724 + f.close();
700 725 if (!files.isEmpty())
701 726 BEE::writeSigset(file, files, ignoreMetadata);
702 727 }
703 728  
704 729 TemplateList readBlock(bool *done)
705 730 {
  731 + if (reader.atEnd())
  732 + f.seek(0);
  733 +
  734 + TemplateList templates;
  735 + qint64 count = 0;
  736 + while (!reader.atEnd())
  737 + {
  738 + // if an identity is active we try to read presentations
  739 + if (signatureActive)
  740 + {
  741 + while (signatureActive)
  742 + {
  743 + QXmlStreamReader::TokenType signatureToken = reader.readNext();
  744 +
  745 + // did the signature end?
  746 + if (signatureToken == QXmlStreamReader::EndElement && reader.name() == "biometric-signature") {
  747 + signatureActive = false;
  748 + break;
  749 + }
  750 + // did we reach the end of the document? Theoretically this shoudln't happen without reaching the end of
  751 + if (signatureToken == QXmlStreamReader::EndDocument)
  752 + break;
  753 +
  754 + // a presentation!
  755 + if (signatureToken == QXmlStreamReader::StartElement && reader.name() == "presentation") {
  756 + templates.append(Template(File("",currentSignatureName)));
  757 + foreach (const QXmlStreamAttribute & attribute, reader.attributes()) {
  758 + // file-name is stored directly on file, not as a key/value pair
  759 + if (attribute.name() == "file-name")
  760 + templates.last().file.name = attribute.value().toString();
  761 + // other values are directly set as metadata
  762 + else if (!ignoreMetadata) templates.last().file.set(attribute.name().toString(), attribute.value().toString());
  763 + }
  764 +
  765 + // a presentation can have bounding boxes as child elements
  766 + bool signatureActive = true;
  767 + QList<QRectF> rects = templates.last().file.rects();
  768 + while (signatureActive)
  769 + {
  770 + QXmlStreamReader::TokenType pToken = reader.readNext();
  771 + if (pToken == QXmlStreamReader::EndElement && reader.name() == "presentation")
  772 + break;
  773 +
  774 + if (pToken == QXmlStreamReader::StartElement)
  775 + {
  776 + // get boudning box properties as attributes, just going to assume this all works
  777 + qreal x = reader.attributes().value("x").toDouble();
  778 + qreal y = reader.attributes().value("y").toDouble();
  779 + qreal width = reader.attributes().value("width").toDouble();
  780 + qreal height = reader.attributes().value("height").toDouble();
  781 + rects += QRectF(x, y, width, height);
  782 + }
  783 + }
  784 + templates.last().file.setRects(rects);
  785 + templates.last().file.set("progress", f.pos());
  786 +
  787 + count++;
  788 + if (count >= this->readBlockSize) {
  789 + *done = false;
  790 + return templates;
  791 + }
  792 + }
  793 + }
  794 + }
  795 + // otherwise, keep reading elements until the next identity is reacehed
  796 + else
  797 + {
  798 + QXmlStreamReader::TokenType token = reader.readNext();
  799 +
  800 + // end of file?
  801 + if (token == QXmlStreamReader::EndDocument)
  802 + break;
  803 +
  804 + // we are only interested in new elements
  805 + if (token != QXmlStreamReader::StartElement)
  806 + continue;
  807 +
  808 + QStringRef elName = reader.name();
  809 +
  810 + // biometric-signature-set is the root element
  811 + if (elName == "biometric-signature-set")
  812 + continue;
  813 +
  814 + // biometric-signature -- an identity
  815 + if (elName == "biometric-signature")
  816 + {
  817 + // read the name associated with the current signature
  818 + if (!reader.attributes().hasAttribute("name"))
  819 + {
  820 + qDebug() << "Biometric signature missing name";
  821 + continue;
  822 + }
  823 + currentSignatureName = reader.attributes().value("name").toString();
  824 + signatureActive = true;
  825 + }
  826 + }
  827 +
  828 + }
706 829 *done = true;
707   - return TemplateList(BEE::readSigset(file, ignoreMetadata));
  830 +
  831 + return templates;
708 832 }
709 833  
710 834 void write(const Template &t)
711 835 {
712 836 files.append(t.file);
713 837 }
  838 +
  839 + void init()
  840 + {
  841 + FileGallery::init();
  842 + reader.setDevice(&f);
  843 + }
714 844 };
715 845  
716 846 BR_REGISTER(Gallery, xmlGallery)
... ... @@ -1178,6 +1308,17 @@ BR_REGISTER(Gallery, vbbGallery)
1178 1308  
1179 1309 #endif
1180 1310  
  1311 +void FileGallery::init()
  1312 +{
  1313 + f.setFileName(file);
  1314 + QtUtils::touchDir(f);
  1315 + if (!f.open(QFile::ReadWrite))
  1316 + qFatal("Failed to open %s for read/write.", qPrintable(file));
  1317 + fileSize = f.size();
  1318 +
  1319 + Gallery::init();
  1320 +}
  1321 +
1181 1322 } // namespace br
1182 1323  
1183 1324 #include "gallery.moc"
... ...
openbr/plugins/misc.cpp
... ... @@ -517,14 +517,18 @@ class ProgressCounterTransform : public TimeVaryingTransform
517 517  
518 518 qint64 elapsed = timer.elapsed();
519 519  
  520 + if (!dst.empty()) {
  521 + Globals->currentProgress = dst.last().file.get<qint64>("progress",0);
  522 + dst.last().file.remove("progress");
  523 + Globals->currentStep++;
  524 + }
  525 +
520 526 // updated every second
521 527 if (elapsed > 1000) {
522 528 Globals->printStatus();
523 529 timer.start();
524 530 }
525 531  
526   - Globals->currentStep++;
527   -
528 532 return;
529 533 }
530 534  
... ... @@ -537,12 +541,13 @@ class ProgressCounterTransform : public TimeVaryingTransform
537 541 {
538 542 (void) data;
539 543 float p = br_progress();
540   - qDebug("%05.2f%% ELAPSED=%s REMAINING=%s COUNT=%g/%g \r", p*100, QtUtils::toTime(Globals->startTime.elapsed()/1000.0f).toStdString().c_str(), QtUtils::toTime(0).toStdString().c_str(), Globals->currentStep, Globals->totalSteps);
  544 + qDebug("%05.2f%% ELAPSED=%s REMAINING=%s COUNT=%g \r", p*100, QtUtils::toTime(Globals->startTime.elapsed()/1000.0f).toStdString().c_str(), QtUtils::toTime(0).toStdString().c_str(), Globals->currentStep);
541 545 }
542 546  
543 547 void init()
544 548 {
545 549 timer.start();
  550 + Globals->currentStep = 0;
546 551 }
547 552  
548 553 public:
... ... @@ -689,6 +694,40 @@ public:
689 694  
690 695 BR_REGISTER(Transform, OutputTransform)
691 696  
  697 +class FileExclusionTransform : public UntrainableMetaTransform
  698 +{
  699 + Q_OBJECT
  700 +
  701 + Q_PROPERTY(QString exclusionGallery READ get_exclusionGallery WRITE set_exclusionGallery RESET reset_exclusionGallery STORED false)
  702 + BR_PROPERTY(QString, exclusionGallery, "")
  703 +
  704 + QSet<QString> excluded;
  705 +
  706 + void project(const Template & src, Template & dst) const
  707 + {
  708 + qFatal("FileExclusion can't do anything here");
  709 + }
  710 +
  711 + void project(const TemplateList &src, TemplateList &dst) const
  712 + {
  713 + foreach(const Template & srcTemp, src)
  714 + {
  715 + if (!excluded.contains(srcTemp.file))
  716 + dst.append(srcTemp);
  717 + }
  718 + }
  719 +
  720 + void init()
  721 + {
  722 + if (exclusionGallery.isEmpty())
  723 + return;
  724 + FileList temp = FileList::fromGallery(exclusionGallery);
  725 + excluded = QSet<QString>::fromList(temp.names());
  726 + }
  727 +};
  728 +
  729 +BR_REGISTER(Transform, FileExclusionTransform)
  730 +
692 731 }
693 732  
694 733 #include "misc.moc"
... ...
openbr/plugins/openbr_internal.h
... ... @@ -345,6 +345,21 @@ protected:
345 345 UntrainableMetadataTransform() : MetadataTransform(false) {}
346 346 };
347 347  
  348 +class FileGallery : public Gallery
  349 +{
  350 + Q_OBJECT
  351 +public:
  352 + QFile f;
  353 + qint64 fileSize;
  354 +
  355 + virtual ~FileGallery() { f.close(); }
  356 +
  357 + void init();
  358 +
  359 + qint64 totalSize() { return fileSize; }
  360 + qint64 position() { return f.pos(); }
  361 +};
  362 +
348 363 }
349 364  
350 365 #endif // OPENBR_INTERNAL_H
... ...
openbr/plugins/process.cpp
... ... @@ -497,6 +497,8 @@ class ProcessWrapperTransform : public TimeVaryingTransform
497 497  
498 498 void projectUpdate(const TemplateList &src, TemplateList &dst)
499 499 {
  500 + if (src.empty())
  501 + return;
500 502  
501 503 if (!processActive)
502 504 {
... ...