/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright 2012 The MITRE Corporation * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include #include #ifndef BR_EMBEDDED #include #include #include #include #include #include #endif // BR_EMBEDDED #include #include "openbr_internal.h" #include "openbr/core/bee.h" #include "openbr/core/common.h" #include "openbr/core/opencvutils.h" #include "openbr/core/qtutils.h" #ifdef CVMATIO #include "MatlabIO.hpp" #include "MatlabIOContainer.hpp" #endif namespace br { /*! * \ingroup galleries * \brief Weka ARFF file format. * \author Josh Klontz \cite jklontz * http://weka.wikispaces.com/ARFF+%28stable+version%29 */ class arffGallery : public Gallery { Q_OBJECT QFile arffFile; TemplateList readBlock(bool *done) { (void) done; qFatal("Not implemented."); return TemplateList(); } void write(const Template &t) { if (!arffFile.isOpen()) { arffFile.setFileName(file.name); arffFile.open(QFile::WriteOnly); arffFile.write("% OpenBR templates\n" "@RELATION OpenBR\n" "\n"); const int dimensions = t.m().rows * t.m().cols; for (int i=0; i("Label") + "'\n")); } void init() { // } }; BR_REGISTER(Gallery, arffGallery) /*! * \ingroup galleries * \brief A binary gallery. * * Designed to be a literal translation of templates to disk. * Compatible with TemplateList::fromBuffer. * \author Josh Klontz \cite jklontz */ class galGallery : public Gallery { Q_OBJECT QFile gallery; QDataStream stream; void init() { gallery.setFileName(file); if (file.get("remove")) gallery.remove(); QtUtils::touchDir(gallery); QFile::OpenMode mode = QFile::ReadWrite; if (file.get("append")) mode |= QFile::Append; if (!gallery.open(mode)) qFatal("Can't open gallery: %s", qPrintable(gallery.fileName())); stream.setDevice(&gallery); } TemplateList readBlock(bool *done) { if (stream.atEnd()) gallery.seek(0); TemplateList templates; while ((templates.size() < readBlockSize) && !stream.atEnd()) { Template m; stream >> m; templates.append(m); } *done = stream.atEnd(); return templates; } void write(const Template &t) { if (t.isEmpty() && t.file.isNull()) return; stream << t; } }; BR_REGISTER(Gallery, galGallery) /*! * \ingroup galleries * \brief Reads/writes templates to/from folders. * \author Josh Klontz \cite jklontz * \param regexp An optional regular expression to match against the files extension. */ class EmptyGallery : public Gallery { Q_OBJECT Q_PROPERTY(QString regexp READ get_regexp WRITE set_regexp RESET reset_regexp STORED false) BR_PROPERTY(QString, regexp, QString()) void init() { QtUtils::touchDir(QDir(file.name)); } TemplateList readBlock(bool *done) { TemplateList templates; *done = true; // Enrolling a null file is used as an idiom to initialize an algorithm if (file.isNull()) return templates; // Add immediate subfolders QDir dir(file); QList< QFuture > futures; foreach (const QString &folder, QtUtils::naturalSort(dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot))) { const QDir subdir = dir.absoluteFilePath(folder); futures.append(QtConcurrent::run(&EmptyGallery::getTemplates, subdir)); } foreach (const QFuture &future, futures) templates.append(future.result()); // Add root folder foreach (const QString &fileName, QtUtils::getFiles(file.name, false)) templates.append(File(fileName, dir.dirName())); if (!regexp.isEmpty()) { QRegExp re(regexp); re.setPatternSyntax(QRegExp::Wildcard); for (int i=templates.size()-1; i>=0; i--) { if (!re.exactMatch(templates[i].file.fileName())) { templates.removeAt(i); } } } return templates; } void write(const Template &t) { static QMutex diskLock; // Enrolling a null file is used as an idiom to initialize an algorithm if (file.name.isEmpty()) return; const QString newFormat = file.get("newFormat",QString()); QString destination = file.name + "/" + (file.getBool("preservePath") ? t.file.path()+"/" : QString()); destination += (newFormat.isEmpty() ? t.file.fileName() : t.file.baseName()+newFormat); QMutexLocker diskLocker(&diskLock); // Windows prefers to crash when writing to disk in parallel if (t.isNull()) { QtUtils::copyFile(t.file.resolved(), destination); } else { QScopedPointer format(Factory::make(destination)); format->write(t); } } static TemplateList getTemplates(const QDir &dir) { const QStringList files = QtUtils::getFiles(dir, true); TemplateList templates; templates.reserve(files.size()); foreach (const QString &file, files) templates.append(File(file, dir.dirName())); return templates; } }; BR_REGISTER(Gallery, EmptyGallery) /*! * \ingroup galleries * \brief Treats the gallery as a br::Format. * \author Josh Klontz \cite jklontz */ class DefaultGallery : public Gallery { Q_OBJECT TemplateList readBlock(bool *done) { *done = true; return TemplateList() << file; } void write(const Template &t) { QScopedPointer format(Factory::make(file)); format->write(t); } }; BR_REGISTER(Gallery, DefaultGallery) /*! * \ingroup galleries * \brief Combine all templates into one large matrix and process it as a br::Format * \author Josh Klontz \cite jklontz */ class matrixGallery : public Gallery { Q_OBJECT Q_PROPERTY(const QString extension READ get_extension WRITE set_extension RESET reset_extension STORED false) BR_PROPERTY(QString, extension, "mtx") TemplateList templates; ~matrixGallery() { if (templates.isEmpty()) return; QScopedPointer format(Factory::make(getFormat())); format->write(Template(file, OpenCVUtils::toMat(templates.data()))); } File getFormat() const { return file.name.left(file.name.size() - file.suffix().size()) + extension; } TemplateList readBlock(bool *done) { *done = true; return TemplateList() << getFormat(); } void write(const Template &t) { templates.append(t); } }; BR_REGISTER(Gallery, matrixGallery) /*! * \ingroup initializers * \brief Initialization support for memGallery. * \author Josh Klontz \cite jklontz */ class MemoryGalleries : public Initializer { Q_OBJECT void initialize() const {} void finalize() const { galleries.clear(); } public: static QHash galleries; /*!< TODO */ static QHash aligned; /*!< TODO */ }; QHash MemoryGalleries::galleries; QHash MemoryGalleries::aligned; BR_REGISTER(Initializer, MemoryGalleries) /*! * \ingroup galleries * \brief A gallery held in memory. * \author Josh Klontz \cite jklontz */ class memGallery : public Gallery { Q_OBJECT int block; void init() { block = 0; File galleryFile = file.name.mid(0, file.name.size()-4); if ((galleryFile.suffix() == "gal") && galleryFile.exists() && !MemoryGalleries::galleries.contains(file)) { QSharedPointer gallery(Factory::make(galleryFile)); MemoryGalleries::galleries[file] = gallery->read(); align(MemoryGalleries::galleries[file]); MemoryGalleries::aligned[file] = true; } } TemplateList readBlock(bool *done) { if (!MemoryGalleries::aligned[file]) { align(MemoryGalleries::galleries[file]); MemoryGalleries::aligned[file] = true; } TemplateList templates = MemoryGalleries::galleries[file].mid(block*readBlockSize, readBlockSize); *done = (templates.size() < readBlockSize); block = *done ? 0 : block+1; return templates; } void write(const Template &t) { MemoryGalleries::galleries[file].append(t); MemoryGalleries::aligned[file] = false; } static void align(TemplateList &templates) { if (!templates.empty() && templates[0].size() > 1) return; bool uniform = true; QVector alignedData(templates.bytes()); size_t offset = 0; for (int i=0; i 1) qFatal("Can't handle multi-matrix template %s.", qPrintable(t.file.flat())); cv::Mat &m = t; if (m.data) { const size_t size = m.total() * m.elemSize(); if (!m.isContinuous()) qFatal("Requires continuous matrix data of size %d for %s.", (int)size, qPrintable(t.file.flat())); memcpy(&(alignedData.data()[offset]), m.ptr(), size); m = cv::Mat(m.rows, m.cols, m.type(), &(alignedData.data()[offset])); offset += size; } uniform = uniform && (m.rows == templates.first().m().rows) && (m.cols == templates.first().m().cols) && (m.type() == templates.first().m().type()); } templates.uniform = uniform; templates.alignedData = alignedData; } }; BR_REGISTER(Gallery, memGallery) FileList FileList::fromGallery(const File & file, bool cache) { File targetMeta = file; targetMeta.name = targetMeta.path() + targetMeta.baseName() + "_meta" + targetMeta.hash() + ".mem"; FileList fileData; // Did we already read the data? if (MemoryGalleries::galleries.contains(targetMeta)) { return MemoryGalleries::galleries[targetMeta].files(); } TemplateList templates; // OK we read the data in some form, does the gallery type containing matrices? if ((QStringList() << "gal" << "mem" << "template").contains(file.suffix())) { // Retrieve it block by block, dropping matrices from read templates. QScopedPointer gallery(Gallery::make(file)); gallery->set_readBlockSize(10); bool done = false; while (!done) { TemplateList tList = gallery->readBlock(&done); for (int i=0; i < tList.size();i++) { tList[i].clear(); templates.append(tList[i].file); } } } else { // this is a gallery format that doesn't include matrices, so we can just read it QScopedPointer gallery(Gallery::make(file)); templates= gallery->read(); } if (cache) { QScopedPointer memOutput(Gallery::make(targetMeta)); memOutput->writeBlock(templates); } fileData = templates.files(); return fileData; } /*! * \ingroup galleries * \brief Treats each line as a file. * \author Josh Klontz \cite jklontz * * Columns should be comma separated with first row containing headers. * The first column in the file should be the path to the file to enroll. * Other columns will be treated as file metadata. * * \see txtGallery */ class csvGallery : public Gallery { Q_OBJECT Q_PROPERTY(int fileIndex READ get_fileIndex WRITE set_fileIndex RESET reset_fileIndex) BR_PROPERTY(int, fileIndex, 0) FileList files; ~csvGallery() { if (files.isEmpty()) return; QMap samples; foreach (const File &file, files) foreach (const QString &key, file.localKeys()) if (!samples.contains(key)) samples.insert(key, file.value(key)); // Don't create columns in the CSV for these special fields samples.remove("Points"); samples.remove("Rects"); QStringList lines; lines.reserve(files.size()+1); { // Make header QStringList words; words.append("File"); foreach (const QString &key, samples.keys()) words.append(getCSVElement(key, samples[key], true)); lines.append(words.join(",")); } // Make table foreach (const File &file, files) { QStringList words; words.append(file.name); foreach (const QString &key, samples.keys()) words.append(getCSVElement(key, file.value(key), false)); lines.append(words.join(",")); } QtUtils::writeFile(file, lines); } TemplateList readBlock(bool *done) { *done = true; TemplateList templates; if (!file.exists()) return templates; QStringList lines = QtUtils::readLines(file); QRegExp regexp("\\s*,\\s*"); QStringList headers; if (!lines.isEmpty()) headers = lines.takeFirst().split(regexp); foreach (const QString &line, lines) { QStringList words = line.split(regexp); if (words.size() != headers.size()) continue; File f; for (int i=0; i()) { if (header) return key; else return value.value(); } else if (value.canConvert()) { const QPointF point = value.value(); if (header) return key+"_X,"+key+"_Y"; else return QString::number(point.x())+","+QString::number(point.y()); } else if (value.canConvert()) { const QRectF rect = value.value(); if (header) return key+"_X,"+key+"_Y,"+key+"_Width,"+key+"_Height"; else return QString::number(rect.x())+","+QString::number(rect.y())+","+QString::number(rect.width())+","+QString::number(rect.height()); } else { if (header) return key; else return QString::number(std::numeric_limits::quiet_NaN()); } } }; BR_REGISTER(Gallery, csvGallery) /*! * \ingroup galleries * \brief Treats each line as a file. * \author Josh Klontz \cite jklontz * * The entire line is treated as the file path. An optional label may be specified using a space ' ' separator: * \verbatim ... \endverbatim * or \verbatim