// Copyright (c) 2012-2017 VideoStitch SAS // Copyright (c) 2018 stitchEm // PTO doc: // http://wiki.panotools.org/PTStitcher // http://wiki.panotools.org/PTOptimizer #ifndef __clang_analyzer__ // VSA-7043 #include "core/defs/panoInputDefsPimpl.hpp" #include "core/transformGeoParams.hpp" #include "util/base64.hpp" #include "util/strutils.hpp" #include "libvideostitch/logging.hpp" #include <fstream> #include <iostream> #include <string> #include <cstdlib> #include <cstdio> #include <cstring> #include <cassert> #include <cmath> #include <sstream> #include <clocale> namespace VideoStitch { namespace Core { namespace { /** * A context to pts context data. */ struct PtsContext { PtsContext() : useCameraCurve(false), hasGlobalExposure(false), wbr(0.0), wbb(0.0), eev(0.0), hasLastDummyImage(false), hasLastImage(false), lastWidth(0), lastHeight(0), hasLastExposureParams(false), lastExposureParams{}, hasVigParams(false) {} // Global bool useCameraCurve; bool hasGlobalExposure; double wbr; // white balance, red double wbb; // white balance, blue double eev; // If the pts has dummy images, these are their ids. std::vector<size_t> dummyImageIds; double emorParams[5]; // Per-input bool hasLastDummyImage; bool hasLastImage; int lastWidth; int lastHeight; char lastFilename[4096]; bool hasLastExposureParams; std::array<double, 4> lastExposureParams; // wbred, wbblue, flare, eev bool hasVigParams; double vigParams[5]; std::string sourceMask; }; double local_atof(const char* str) { double val; std::istringstream istr(str); istr.imbue(std::locale("C")); istr >> val; return val; } } // namespace Potential<PanoDefinition> PanoDefinition::Pimpl::mergeCalibrationIntoPano(const PanoDefinition* calibration, const PanoDefinition* pano) { if (calibration->numVideoInputs() != pano->numVideoInputs()) { std::stringstream msg; msg << "Cannot apply the calibration to the current panorama configuration. The calibration has "; msg << calibration->numVideoInputs() << " video inputs, the current panorama has " << pano->numVideoInputs(); return {Origin::PanoramaConfiguration, ErrType::InvalidConfiguration, msg.str()}; } auto newPano = Potential<PanoDefinition>(pano->clone()); for (videoreaderid_t i = 0; i < calibration->numVideoInputs(); ++i) { const Core::InputDefinition& calibInput = calibration->getVideoInputs()[i].get(); Core::InputDefinition& newInput = newPano->getVideoInputs()[i].get(); newInput.setMaskData(calibInput.getMaskData()); newInput.setCropLeft(calibInput.getCropLeft()); newInput.setCropRight(calibInput.getCropRight()); newInput.setCropTop(calibInput.getCropTop()); newInput.setCropBottom(calibInput.getCropBottom()); newInput.setFormat(calibInput.getFormat()); newInput.setUseMeterDistortion(calibInput.getUseMeterDistortion()); GeometryDefinition def = calibInput.getGeometries().at(0); /*Temporary hack, horizontal focal actually contains fov*/ /*Look for HFOV in InputDefinition::parseFromPtoLine switch for more infos about this hack*/ /*Convert fovs to focals*/ double fov = def.getHorizontalFocal(); double focal = TransformGeoParams::computeHorizontalScale(newInput, fov); def.setHorizontalFocal(focal); newInput.replaceGeometries(new GeometryDefinitionCurve(def)); } return newPano; } Potential<PanoDefinition> PanoDefinition::parseFromPto(const std::string& filename, const PanoDefinition* sourcePano) { std::unique_ptr<std::istream, VideoStitch::Util::IStreamDeleter> ifs(&std::cin); if (!(filename[0] == '-' && filename[1] == '\0')) { ifs = VideoStitch::Util::createIStream(filename); } if (!ifs->good()) { return Status(Origin::Stitcher, ErrType::InvalidConfiguration, "Cannot open file '" + filename + "' for reading."); } /*Change the numeric locale with locale("C") for avoiding the comma decimal separator issue */ std::setlocale(LC_NUMERIC, "C"); // TODO: parse 'm' line // m g1 i5 f0 m2 p0.00784314 std::unique_ptr<PanoDefinition> pano(new PanoDefinition()); PtsContext ptsContext; /* #-outputfile E:\VS\VideoStitch-assets\mappingPts01\PTGui-soft.jpg #-jpegparameters 100 0 #-tiffparameters 8bit packbits alpha_assoc #-vignettingparams */ // std::vector<Calibration::ControlPoint> controlPoints; // parse image lines std::string line; for (unsigned l = 0; !ifs->eof() && !ifs->fail(); ++l) { std::getline(*ifs, line); { // Remove windows eol. size_t len = line.size(); if (len > 1 && line[len - 1] == '\r') { line[len - 1] = '\0'; } } char* lineBuf = const_cast<char*>(line.c_str()); switch (lineBuf[0]) { case 'o': // Pts uses o to mean i. But in that case there should be a started image. if (!(ptsContext.hasLastImage || ptsContext.hasLastDummyImage)) { Logger::get(Logger::Verbose) << "Ignored unimplemented '" << line[0] << "' entry at line " << l << "." << std::endl; break; } // fallthrough case 'i': { Potential<InputDefinition> input(InputDefinition::parseFromPtoLine(lineBuf, pano->pimpl->inputs)); FAIL_RETURN(input.status()); ptsContext.hasLastDummyImage = false; if (ptsContext.hasLastImage) { input->setWidth(ptsContext.lastWidth); input->setHeight(ptsContext.lastHeight); input->setFilename(ptsContext.lastFilename); ptsContext.hasLastImage = false; } if (ptsContext.hasGlobalExposure || ptsContext.hasLastExposureParams) { double logRedCB = ptsContext.wbr + ptsContext.lastExposureParams[0]; double logBlueCB = ptsContext.wbb + ptsContext.lastExposureParams[1]; input->replaceRedCB(new Curve(1.0 / pow(2.0, logRedCB))); input->replaceBlueCB(new Curve(1.0 / pow(2.0, logBlueCB))); // Green is computed automatically input->replaceGreenCB(new Curve(1.0 / pow(2.0, -(logRedCB + logBlueCB)))); input->replaceExposureValue(new Curve(ptsContext.lastExposureParams[3])); pano->replaceExposureValue(new Curve(ptsContext.eev)); ptsContext.hasLastExposureParams = false; } if (ptsContext.hasVigParams) { input->pimpl->vignettingCoeff0 = 1.0; input->pimpl->vignettingCoeff1 = ptsContext.vigParams[0]; input->pimpl->vignettingCoeff2 = ptsContext.vigParams[1]; input->pimpl->vignettingCoeff3 = ptsContext.vigParams[2]; input->pimpl->vignettingCenterX = ptsContext.vigParams[3]; input->pimpl->vignettingCenterY = ptsContext.vigParams[4]; } if (ptsContext.useCameraCurve) { input->pimpl->emorA = ptsContext.emorParams[0]; input->pimpl->emorB = ptsContext.emorParams[1]; input->pimpl->emorC = ptsContext.emorParams[2]; input->pimpl->emorD = ptsContext.emorParams[3]; input->pimpl->emorE = ptsContext.emorParams[4]; input->pimpl->photoResponse = InputDefinition::PhotoResponse::InvEmorResponse; } input->pimpl->maskData = Util::base64Decode(ptsContext.sourceMask); ptsContext.sourceMask.clear(); pano->pimpl->inputs.push_back(input.release()); break; } case 'p': { FAIL_RETURN(pano->readParams(lineBuf)); break; } case '#': // TrX TrY TrZ j0 Va Vb Vc Vd Vx Vy Vm if (Util::startsWith(lineBuf, "#-")) { // read pts instructions. if (Util::startsWith(lineBuf, "#-cameracurve")) { // Equivalent to Ra Rb Rc Rd Re. sscanf(lineBuf, "#-cameracurve %lf %lf %lf %lf %lf", &ptsContext.emorParams[0], &ptsContext.emorParams[1], &ptsContext.emorParams[2], &ptsContext.emorParams[3], &ptsContext.emorParams[4]); } else if (Util::startsWith(lineBuf, "#-pmoptcameracurvemode")) { // TODO: handle the several modes, but everything seems to boil down to the same thing. ptsContext.useCameraCurve = true; } else if (Util::startsWith(lineBuf, "#-wbexposure")) { // Format is Er Eb Eev. 0 means no correction. sscanf(lineBuf, "#-wbexposure %lf %lf %lf", &ptsContext.wbr, &ptsContext.wbb, &ptsContext.eev); ptsContext.hasGlobalExposure = true; } else if (Util::startsWith(lineBuf, "#-dummyimage")) { ptsContext.dummyImageIds.push_back(pano->numInputs()); ptsContext.hasLastDummyImage = true; } else if (Util::startsWith(lineBuf, "#-imgfile")) { ptsContext.hasLastImage = (sscanf(lineBuf, "#-imgfile %i %i \"%[^\"]\"", &ptsContext.lastWidth, &ptsContext.lastHeight, ptsContext.lastFilename) == 3); } else if (Util::startsWith(lineBuf, "#-viewpoint")) { std::vector<double> lastViewpoint(5, 0); bool hasLastViewpoint = (sscanf(lineBuf, "#-viewpoint %lf %lf %lf %lf %lf", &lastViewpoint[0], &lastViewpoint[1], &lastViewpoint[2], &lastViewpoint[3], &lastViewpoint[4]) == 5); if (hasLastViewpoint && (lastViewpoint[0] != 0.0 || lastViewpoint[1] != 0.0 || lastViewpoint[2] != 0.0 || lastViewpoint[3] != 0.0 || lastViewpoint[4] != 0.0)) { return {Origin::PanoramaConfiguration, ErrType::UnsupportedAction, "Cannot import this PTGui file: the support for PTGui viewpoint correction has been deprecated."}; } } else if (Util::startsWith(lineBuf, "#-vignettingparams")) { ptsContext.hasVigParams = (sscanf(lineBuf, "#-vignettingparams %lf %lf %lf %lf %lf", &ptsContext.vigParams[0], &ptsContext.vigParams[1], &ptsContext.vigParams[2], &ptsContext.vigParams[3], &ptsContext.vigParams[4]) == 5); } else if (Util::startsWith(lineBuf, "#-exposureparams")) { ptsContext.hasLastExposureParams = (sscanf(lineBuf, "#-exposureparams %lf %lf %lf %lf", &ptsContext.lastExposureParams[0], &ptsContext.lastExposureParams[1], &ptsContext.lastExposureParams[2], &ptsContext.lastExposureParams[3]) == 4); } else if (Util::startsWith(lineBuf, "#-sourcemask")) { ptsContext.sourceMask = line.substr(13); } else { Logger::get(Logger::Verbose) << "Ignoring pts option lineBuf '" << lineBuf << "'" << std::endl; } } else if (Util::startsWith(lineBuf, "# PTGui Trial Project File")) { return {Origin::PanoramaConfiguration, ErrType::UnsupportedAction, "Cannot import from encrypted PTGui trial project files. Please export your project files using a " "full version of PTGui."}; } break; case '\0': break; // empty line, ignore case 'c': { // control points // int index0, index1; // float x0, y0, x1, y1; // sscanf(lineBuf, "c n%d N%d x%f y%f X%f Y%f", &index0, &index1, &x0, &y0, &x1, &y1); // const int64_t w0 = pano->getInput(index0).getWidth(); // const int64_t h0 = pano->getInput(index0).getHeight(); // const int64_t w1 = pano->getInput(index1).getWidth(); // const int64_t h1 = pano->getInput(index1).getHeight(); // controlPoints.push_back(Calibration::ControlPoint((Calibration::Node)index0, (Calibration::Node)index1, // x0 - (float)w0 / 2.0f, y0 - (float)h0 / 2.0f, x1 - (float)w1 / 2.0f, y1 - (float)h1 // / 2.0f, -1, -1.0)); break; } case 'v': break; // variable to optimize, ignore case '\n': break; // empty line, ignore case '\r': // empty line, ignore break; default: Logger::get(Logger::Verbose) << "Ignored unimplemented '" << line[0] << "' entry at line " << l << "." << std::endl; break; } } for (std::vector<size_t>::const_reverse_iterator it = ptsContext.dummyImageIds.rbegin(); it != ptsContext.dummyImageIds.rend(); ++it) { InputDefinition* dummy = pano->pimpl->inputs[*it]; delete dummy; pano->pimpl->inputs.erase(pano->pimpl->inputs.begin() + *it); } if (sourcePano != nullptr) { return PanoDefinition::Pimpl::mergeCalibrationIntoPano(pano.get(), sourcePano); } return Potential<PanoDefinition>(pano.release()); } namespace { enum IState { WhiteSpace, Name, Width, Height, IFormat, RedCB, BlueCB, ExposureValue, EmorA, EmorB, EmorC, EmorD, EmorE, ResponseType, LensDistA, LensDistB, LensDistC, CenterX, CenterY, ShearA, ShearB, VA, VB, VC, VD, VX, VY, TrX, TrY, TrZ, Pitch, Roll, Yaw, HFOV, Vm, U, CropLeft, CropRight, CropTop, CropBottom, FileName, TimeOffset, Stack, TranslationPlaneYaw, TranslationPlanePitch, }; } #define GETINTPARAM(name) \ if (useSame) { \ int prevId = atoi(sym); \ if (prevId < 0 || prevId >= (int)prevInputs.size()) { \ return {Origin::PanoramaConfiguration, ErrType::InvalidConfiguration, \ "No such previous input '" + std::string(sym) + "'"}; \ } \ newInput->pimpl->name = prevInputs[prevId]->pimpl->name; \ } else { \ newInput->pimpl->name = atoi(sym); \ } #define GETINTPARAM2(getter, setter) \ if (useSame) { \ int prevId = atoi(sym); \ if (prevId < 0 || prevId >= (int)prevInputs.size()) { \ return {Origin::PanoramaConfiguration, ErrType::InvalidConfiguration, \ "No such previous input '" + std::string(sym) + "'"}; \ } \ newInput->setter(prevInputs[prevId]->getter()); \ } else { \ newInput->setter(atoi(sym)); \ } #define GETFLOATPARAM(name) \ if (useSame) { \ int prevId = atoi(sym); \ if (prevId < 0 || prevId >= (int)prevInputs.size()) { \ return {Origin::PanoramaConfiguration, ErrType::InvalidConfiguration, \ "No such previous input '" + std::string(sym) + "'"}; \ } \ newInput->pimpl->name = prevInputs[prevId]->pimpl->name; \ } else { \ newInput->pimpl->name = local_atof(sym); \ } #define GETFLOATPARAM2(getter, setter) \ if (useSame) { \ int prevId = atoi(sym); \ if (prevId < 0 || prevId >= (int)prevInputs.size()) { \ return {Origin::PanoramaConfiguration, ErrType::InvalidConfiguration, \ "No such previous input '" + std::string(sym) + "'"}; \ } \ setter(prevInputs[prevId]->getter()); \ } else { \ setter(local_atof(sym)); \ } #define GETCURVEPARAM(name) \ if (useSame) { \ int prevId = atoi(sym); \ if (prevId < 0 || prevId >= (int)prevInputs.size()) { \ return {Origin::PanoramaConfiguration, ErrType::InvalidConfiguration, \ "No such previous input '" + std::string(sym) + "'"}; \ } \ newInput->replace##name(prevInputs[prevId]->get##name().clone()); \ } else { \ newInput->replace##name(new Core::Curve(local_atof(sym))); \ } #define SETWHITESPACESTATE \ state = WhiteSpace; \ sym = NULL; \ break namespace { int tagToInt(const char* tag, size_t tagLen) { switch (tagLen) { case 1: return tag[0]; case 2: return 256 * tag[0] + tag[1]; case 3: return 256 * 256 * tag[0] + 256 * tag[1] + tag[2]; default: return -1; } } Status photoResponseFromInt(int v, InputDefinition::PhotoResponse& response) { switch (v) { case 0: response = InputDefinition::PhotoResponse::EmorResponse; return Status::OK(); case 1: response = InputDefinition::PhotoResponse::LinearResponse; return Status::OK(); case 2: response = InputDefinition::PhotoResponse::GammaResponse; return Status::OK(); case 3: return {Origin::PanoramaConfiguration, ErrType::InvalidConfiguration, "Loading camera response from file is currently not supported"}; case 4: return {Origin::PanoramaConfiguration, ErrType::InvalidConfiguration, "ICC camera responses are currently not supported."}; default: return {Origin::PanoramaConfiguration, ErrType::InvalidConfiguration, "Unknown camera response type '" + std::to_string(v) + "'"}; } } } // namespace Potential<ReaderInputDefinition> ReaderInputDefinition::parseFromPtoLine( char* line, const std::vector<ReaderInputDefinition*>& prevInputs) { /* i w3456 h2304 f0 Eb1 Eev0 Er1 Ra0 Rb0 Rc0 Rd0 Re0 Va1 Vb0 Vc0 Vd0 Vx0 Vy0 a0 b-0.0127103 c0 d0 e0 g0 p2.19134 r-5.76063 t123 v61.2111 y-47.9372 Vm5 u10 n"/home/clem/photos/panoramas/pano7/img_6845.jpg" */ std::unique_ptr<ReaderInputDefinition> newInput(new ReaderInputDefinition()); const char* const lpEnd = line + strlen(line); const char* sym = NULL; bool useSame = false; // parameters can reference other parameters with "=<imageId>" syntax, e.g. "v=0" IState state = WhiteSpace; for (char* lp = line + 1; lp < lpEnd + 1; ++lp) { switch (state) { case WhiteSpace: if (*lp == ' ' || *lp == '\t' || *lp == '\0' || *lp == '\r' || *lp == '\n') { continue; } else { // begin reading a name state = Name; sym = lp; } break; case Name: // names are [a-zA-Z_] if (!(('a' <= *lp && *lp <= 'z') || ('A' <= *lp && *lp <= 'Z') || (*lp == '_'))) { size_t len = lp - sym; switch (tagToInt(sym, len)) { case 'w': state = Width; break; case 'h': state = Height; break; case 'n': state = FileName; break; case 'T' * 256 + 'O': state = TimeOffset; break; default: { std::stringstream msg; msg << "Invalid parameter '" << std::string(sym, len) << "' in image line. Context:" << std::endl; msg << " " << line << std::endl; return {Origin::PanoramaConfiguration, ErrType::InvalidConfiguration, msg.str()}; } } useSame = false; if (state == FileName) { if (*lp != '"') { std::stringstream msg; msg << "Invalid string parameter in image line. Context:" << std::endl; msg << " " << line << std::endl; return {Origin::PanoramaConfiguration, ErrType::InvalidConfiguration, msg.str()}; } else { sym = lp + 1; } } else if (*lp == '=') { sym = lp + 1; useSame = true; } else { sym = lp; } } break; case FileName: // FIXME: handle escaping if (*lp == '"') { newInput->setFilename(std::string(sym, lp - sym)); state = WhiteSpace; sym = NULL; } break; default: //[0-9.eE] if (!(('0' <= *lp && *lp <= '9') || *lp == 'e' || *lp == 'E' || *lp == '.' || *lp == '-')) { char c = *lp; *lp = 0; switch (state) { case Width: GETINTPARAM2(getWidth, setWidth); SETWHITESPACESTATE; case Height: GETINTPARAM2(getHeight, setHeight); SETWHITESPACESTATE; case TimeOffset: GETINTPARAM2(getFrameOffset, setFrameOffset); SETWHITESPACESTATE; case WhiteSpace: case Name: case FileName: default: return {Origin::PanoramaConfiguration, ErrType::InvalidConfiguration, "Invalid state for ReaderInputDefinition. Context: " + std::string(line)}; } *lp = c; } break; } } return Potential<ReaderInputDefinition>(newInput.release()); } Potential<InputDefinition> InputDefinition::parseFromPtoLine(char* line, const std::vector<InputDefinition*>& prevInputs) { /* i w3456 h2304 f0 Eb1 Eev0 Er1 Ra0 Rb0 Rc0 Rd0 Re0 Va1 Vb0 Vc0 Vd0 Vx0 Vy0 a0 b-0.0127103 c0 d0 e0 g0 p2.19134 r-5.76063 t123 v61.2111 y-47.9372 Vm5 u10 n"/home/clem/photos/panoramas/pano7/img_6845.jpg" */ std::unique_ptr<InputDefinition> newInput(new InputDefinition()); const char* const lpEnd = line + strlen(line); const char* sym = NULL; bool useSame = false; // parameters can reference other parameters with "=<imageId>" syntax, e.g. "v=0" GeometryDefinition def; IState state = WhiteSpace; for (char* lp = line + 1; lp < lpEnd + 1; ++lp) { switch (state) { case WhiteSpace: if (*lp == ' ' || *lp == '\t' || *lp == '\0' || *lp == '\r' || *lp == '\n') { continue; } else { // begin reading a name state = Name; sym = lp; } break; case Name: // names are [a-zA-Z_] if (!(('a' <= *lp && *lp <= 'z') || ('A' <= *lp && *lp <= 'Z') || (*lp == '_'))) { size_t len = lp - sym; switch (tagToInt(sym, len)) { case 'w': state = Width; break; case 'h': state = Height; break; case 'f': state = IFormat; break; case 'a': state = LensDistA; break; case 'b': state = LensDistB; break; case 'c': state = LensDistC; break; case 'd': state = CenterX; break; case 'e': state = CenterY; break; case 'g': state = ShearA; break; case 't': state = ShearB; break; case 'p': state = Pitch; break; case 'r': state = Roll; break; case 'y': state = Yaw; break; case 'v': state = HFOV; break; case 'u': state = U; break; case 'C': case 'S': state = CropLeft; break; case 'n': state = FileName; break; case 'j': state = Stack; break; case 'E' * 256 + 'r': state = RedCB; break; case 'E' * 256 + 'b': state = BlueCB; break; case 'R' * 256 + 'a': state = EmorA; break; case 'R' * 256 + 'b': state = EmorB; break; case 'R' * 256 + 'c': state = EmorC; break; case 'R' * 256 + 'd': state = EmorD; break; case 'R' * 256 + 'e': state = EmorE; break; case 'R' * 256 + 't': state = ResponseType; break; case 'V' * 256 + 'a': state = VA; break; case 'V' * 256 + 'b': state = VB; break; case 'V' * 256 + 'c': state = VC; break; case 'V' * 256 + 'd': state = VD; break; case 'V' * 256 + 'x': state = VX; break; case 'V' * 256 + 'y': state = VY; break; case 'V' * 256 + 'm': state = Vm; break; case 'V' * 256 + 'f': return {Origin::PanoramaConfiguration, ErrType::UnsupportedAction, "Flatfield vignetting correction (Vf) is currently not supported"}; case 'T' * 256 + 'O': state = TimeOffset; break; case 'E' * 256 * 256 + 'e' * 256 + 'v': state = ExposureValue; break; case 'T' * 256 * 256 + 'r' * 256 + 'X': state = TrX; break; case 'T' * 256 * 256 + 'r' * 256 + 'Y': state = TrY; break; case 'T' * 256 * 256 + 'r' * 256 + 'Z': state = TrZ; break; case 'T' * 256 * 256 + 'p' * 256 + 'y': state = TranslationPlaneYaw; break; case 'T' * 256 * 256 + 'p' * 256 + 'p': state = TranslationPlanePitch; break; default: { std::stringstream msg; msg << "Invalid parameter '" << std::string(sym, len) << "' in image line. Context:" << std::endl; msg << " " << line << std::endl; return {Origin::PanoramaConfiguration, ErrType::InvalidConfiguration, msg.str()}; } } useSame = false; if (state == FileName) { if (*lp != '"') { std::stringstream msg; msg << "Invalid string parameter in image line. Context:" << std::endl; msg << " " << line << std::endl; return {Origin::PanoramaConfiguration, ErrType::InvalidConfiguration, msg.str()}; } else { sym = lp + 1; } } else if (*lp == '=') { sym = lp + 1; useSame = true; } else { sym = lp; } } break; case FileName: // FIXME: handle escaping if (*lp == '"') { newInput->setFilename(std::string(sym, lp - sym)); state = WhiteSpace; sym = NULL; } break; default: //[0-9.eE] if (!(('0' <= *lp && *lp <= '9') || *lp == 'e' || *lp == 'E' || *lp == '.' || *lp == '-')) { char c = *lp; *lp = 0; switch (state) { case Width: GETINTPARAM2(getWidth, setWidth); SETWHITESPACESTATE; case Height: GETINTPARAM2(getHeight, setHeight); SETWHITESPACESTATE; case IFormat: if (useSame) { newInput->pimpl->format = prevInputs[atoi(sym)]->pimpl->format; } else if (!fromPTFormat(sym, &newInput->pimpl->format)) { std::stringstream msg; msg << "Unsupported format '" << sym << "' in image line. Context:" << std::endl; msg << " " << line << std::endl; return {Origin::PanoramaConfiguration, ErrType::UnsupportedAction, msg.str()}; } SETWHITESPACESTATE; case RedCB: GETCURVEPARAM(RedCB); SETWHITESPACESTATE; case BlueCB: GETCURVEPARAM(BlueCB); SETWHITESPACESTATE; case ExposureValue: GETCURVEPARAM(ExposureValue); SETWHITESPACESTATE; case EmorA: GETFLOATPARAM(emorA); SETWHITESPACESTATE; case EmorB: GETFLOATPARAM(emorB); SETWHITESPACESTATE; case EmorC: GETFLOATPARAM(emorC); SETWHITESPACESTATE; case EmorD: GETFLOATPARAM(emorD); SETWHITESPACESTATE; case EmorE: GETFLOATPARAM(emorE); SETWHITESPACESTATE; case ResponseType: FAIL_CAUSE(photoResponseFromInt(atoi(sym), newInput->pimpl->photoResponse), Origin::PanoramaConfiguration, ErrType::InvalidConfiguration, "Unsupported photo response. Context: " + std::string(line)); SETWHITESPACESTATE; case LensDistA: GETFLOATPARAM2(getGeometries().at(0).getDistortA, def.setDistortA); SETWHITESPACESTATE; case LensDistB: GETFLOATPARAM2(getGeometries().at(0).getDistortB, def.setDistortB); SETWHITESPACESTATE; case LensDistC: GETFLOATPARAM2(getGeometries().at(0).getDistortC, def.setDistortC); SETWHITESPACESTATE; case CenterX: GETFLOATPARAM2(getGeometries().at(0).getCenterX, def.setCenterX); SETWHITESPACESTATE; case CenterY: GETFLOATPARAM2(getGeometries().at(0).getCenterY, def.setCenterY); SETWHITESPACESTATE; case ShearA: if (local_atof(sym) != 0.0) { Logger::get(Logger::Warning) << "Warning: No support for shear, ignoring." << std::endl; } SETWHITESPACESTATE; case ShearB: if (local_atof(sym) != 0.0) { Logger::get(Logger::Warning) << "Warning: No support for shear, ignoring." << std::endl; } SETWHITESPACESTATE; case VA: GETFLOATPARAM(vignettingCoeff0); SETWHITESPACESTATE; case VB: GETFLOATPARAM(vignettingCoeff1); SETWHITESPACESTATE; case VC: GETFLOATPARAM(vignettingCoeff2); SETWHITESPACESTATE; case VD: GETFLOATPARAM(vignettingCoeff3); SETWHITESPACESTATE; case VX: GETFLOATPARAM(vignettingCenterX); SETWHITESPACESTATE; case VY: GETFLOATPARAM(vignettingCenterY); SETWHITESPACESTATE; case TrX: SETWHITESPACESTATE; case TrY: SETWHITESPACESTATE; case TrZ: SETWHITESPACESTATE; case TranslationPlaneYaw: GETFLOATPARAM(huginTranslationPlaneYaw); if (fabs(newInput->pimpl->huginTranslationPlaneYaw) > 0.0001) { Logger::get(Logger::Warning) << "Warning: No support for Hugin's translation plane Yaw (Tpy)." << std::endl; } SETWHITESPACESTATE; case TranslationPlanePitch: GETFLOATPARAM(huginTranslationPlanePitch); if (fabs(newInput->pimpl->huginTranslationPlanePitch) > 0.0001) { Logger::get(Logger::Warning) << "Warning: No support for Hugin's translation plane Pitch (Tpp)." << std::endl; } SETWHITESPACESTATE; case Pitch: GETFLOATPARAM2(getGeometries().at(0).getPitch, def.setPitch); SETWHITESPACESTATE; case Roll: GETFLOATPARAM2(getGeometries().at(0).getRoll, def.setRoll); SETWHITESPACESTATE; case Yaw: GETFLOATPARAM2(getGeometries().at(0).getYaw, def.setYaw); SETWHITESPACESTATE; case HFOV: /*Temporary hack, we store it there*/ GETFLOATPARAM2(getGeometries().at(0).getHorizontalFocal, def.setHorizontalFocal); SETWHITESPACESTATE; case Vm: // radial vignetting is default SETWHITESPACESTATE; case U: // GETFLOATPARAM(u); SETWHITESPACESTATE; case CropLeft: GETINTPARAM(cropLeft); state = CropRight; sym = lp + 1; break; case CropRight: GETINTPARAM(cropRight); state = CropTop; sym = lp + 1; break; case CropTop: GETINTPARAM(cropTop); state = CropBottom; sym = lp + 1; break; case CropBottom: GETINTPARAM(cropBottom); SETWHITESPACESTATE; case TimeOffset: GETINTPARAM2(getFrameOffset, setFrameOffset); SETWHITESPACESTATE; case Stack: GETINTPARAM(stack); SETWHITESPACESTATE; case WhiteSpace: case Name: case FileName: return {Origin::PanoramaConfiguration, ErrType::InvalidConfiguration, "Invalid state for InputDefinition. Context: " + std::string(line)}; } *lp = c; } break; } } if (def.getHorizontalFocal() == 0.0) { return {Origin::PanoramaConfiguration, ErrType::InvalidConfiguration, "Horizontal field of view not specified"}; } newInput->replaceGeometries(new VideoStitch::Core::GeometryDefinitionCurve(def)); return Potential<InputDefinition>(newInput.release()); } namespace { enum PState { PWhiteSpace, PName, PFormat, PWidth, PHeight, PExposure, PHFOV, POptions, PCropLeft, PCropRight, PCropTop, PCropBottom, PR, // LDR/HDR. Ignore. PIgnored }; } Status PanoDefinition::readParams(char* line) { /* p f2 w13116 h4342 v254 E0 R0 n"TIFF_m c:NONE r:CROP" */ const char* const lpEnd = line + strlen(line); const char* sym = NULL; bool needsDownRotation = false; PState state = PWhiteSpace; for (char* lp = line + 1; lp < lpEnd + 1; ++lp) { switch (state) { case WhiteSpace: if (*lp == ' ' || *lp == '\t' || *lp == '\0' || *lp == '\r' || *lp == '\n') { continue; } else { // begin reading a name state = PName; sym = lp; } break; case PName: // names are [a-zA-Z] if (!(('a' <= *lp && *lp <= 'z') || ('A' <= *lp && *lp <= 'Z') || (*lp == '_'))) { size_t len = lp - sym; if (len == 1) { switch (sym[0]) { case 'w': state = PWidth; break; case 'h': state = PHeight; break; case 'f': state = PFormat; break; case 'v': state = PHFOV; break; case 'E': state = PExposure; break; case 'R': state = PR; break; // LDR(0) /HDR(1). Ignore. case 'n': state = POptions; break; case 'S': state = PCropLeft; break; case 'u': // state = PFeathering; break; Ignore. case 'k': case 'b': case 'd': Logger::get(Logger::Verbose) << "Ignoring parameter '" << std::string(sym, len) << "' in panomara line. Context:" << std::endl; Logger::get(Logger::Verbose) << " " << line << std::endl; state = PIgnored; break; default: { std::stringstream msg; msg << "Invalid parameter '" << std::string(sym, len) << "' in panomara line. Context:" << std::endl; msg << " " << line << std::endl; return {Origin::PanoramaConfiguration, ErrType::InvalidConfiguration, msg.str()}; } } } else { // Try to parse a projection (for PTS). Core::PanoProjection format; if (fromPTSFormat(std::string(sym, len), &format)) { // Hack to transform fstereographic_down into fstereographic + rotation if (std::string(sym, len) == "fstereographic_down") { needsDownRotation = true; } state = PWhiteSpace; sym = NULL; pimpl->projection = format; } else { std::stringstream msg; msg << "invalid parameter '" << std::string(sym, len) << "' in panomara line. Context:" << std::endl; msg << " " << line << std::endl; return {Origin::PanoramaConfiguration, ErrType::InvalidConfiguration, msg.str()}; } } if (state == POptions) { if (*lp != '"') { std::stringstream msg; msg << "invalid string parameter in panomara line. Context:" << std::endl; msg << " " << line << std::endl; return {Origin::PanoramaConfiguration, ErrType::InvalidConfiguration, msg.str()}; } else { sym = lp + 1; } } else { sym = lp; } } break; case POptions: // FIXME: handle escaping if (*lp == '"') { // throw it away state = PWhiteSpace; sym = NULL; } break; default: //[0-9.eE] if (!(('0' <= *lp && *lp <= '9') || *lp == 'e' || *lp == 'E' || *lp == '.' || *lp == '-')) { char c = *lp; *lp = 0; switch (state) { case PWidth: setWidth(atoi(sym)); state = PWhiteSpace; sym = NULL; break; case PHeight: setHeight(atoi(sym)); state = PWhiteSpace; sym = NULL; break; case PFormat: { Core::PanoProjection format; if (!fromPTFormat(sym, &format)) { std::stringstream msg; msg << "Unsupported format '" << sym << "' in pano line. Context:" << std::endl; msg << " " << line << std::endl; return {Origin::PanoramaConfiguration, ErrType::UnsupportedAction, msg.str()}; } pimpl->projection = format; state = PWhiteSpace; sym = NULL; break; } case PExposure: replaceExposureValue(new Core::Curve(local_atof(sym))); state = PWhiteSpace; sym = NULL; break; case PHFOV: setHFOV(local_atof(sym)); state = PWhiteSpace; sym = NULL; break; case PCropLeft: Logger::get(Logger::Warning) << "Warning: No support for cropping, ignoring." << std::endl; state = PCropRight; sym = lp + 1; break; case PCropRight: Logger::get(Logger::Warning) << "Warning: No support for cropping, ignoring." << std::endl; state = PCropTop; sym = lp + 1; break; case PCropTop: Logger::get(Logger::Warning) << "Warning: No support for cropping, ignoring." << std::endl; state = PCropBottom; sym = lp + 1; break; case PCropBottom: Logger::get(Logger::Warning) << "Warning: No support for cropping, ignoring." << std::endl; state = PWhiteSpace; sym = NULL; break; case PR: // LDR/HDR. Ignore. Logger::get(Logger::Warning) << "Warning: No support for HDR output, ignoring." << std::endl; state = PWhiteSpace; sym = NULL; break; case PIgnored: state = PWhiteSpace; sym = NULL; break; default: { std::stringstream msg; msg << "Invalid state. Context:" << std::endl; msg << " " << line << std::endl; return {Origin::PanoramaConfiguration, ErrType::InvalidConfiguration, msg.str()}; } } *lp = c; } break; } } if (getHFOV() == 0.0) { return {Origin::PanoramaConfiguration, ErrType::InvalidConfiguration, "Horizontal field of view not specified"}; } if (getWidth() == 0) { return {Origin::PanoramaConfiguration, ErrType::InvalidConfiguration, "Image width not specified"}; } if (getHeight() == 0) { return {Origin::PanoramaConfiguration, ErrType::InvalidConfiguration, "Image height not specified"}; } if (needsDownRotation) { // FIXME: should be: add 90 degrees, not set constant. replaceGlobalOrientation(new QuaternionCurve(Quaternion<double>::fromEulerZXY(0.0, M_PI / 2.0, 0.0))); } return Status::OK(); } /* #-pathseparator \ #-encoding utf8 #-fileversion 40 #-previewwidth 1132 #-previewheight 566 #-vfov 180 #-resolution 300 #-fixaspect 1 #-ccdcrop 1 #-hasbeenoptimized 1 #-hvcpmode 1 #-morphmode 2 #-psdparameters 8bit packbits layered #-qtvrparameters 800 600 1 1000 70 0 0 -180 180 0 -90 90 90 10 120 1 #-honorexiforientation 1 #-exrparameters noalpha #-hdroutputhdrblended #-hdroutputtonemapped #-hdrfileformat hdr #-hdrmethod fuse #-hdrpsdparameters float none layered #-tonemapsettings 0 0 0.5 1 0 20 0 0 2 0.27 0.67 0.06 #-fusesettings 0.5 0 0.2 0 0 #-pmoptexposuremode auto #-pmoptvignettingmode enabled #-pmoptwbmode disabled #-pmoptflaremode disabled #-pmoptcameracurvemode auto #-blendweight 100 100 100 #-optviewpoint 000 #-colorcorrectlayers #-useexif1 #-batchbuilder_useexif 0 #-stitcher ptgui #-blender ptgui #-blenderfeather 1 #-optimizer ptgui #-interpolator default #-autocpdone #-imgrotate444 #-cpinactive #-imginactive #-linktoprevious #-previewinactive #-outputcrop 0 1 0 1 #-morphcp #-nooptcp #-alignsettings_generatecp 1 #-alignsettings_optimize 1 #-alignsettings_optimizeprealign 1 #-alignsettings_straighten 1 #-alignsettings_fit 1 #-alignsettings_chooseprojection 1 #-alignsettings_setoptimumsize 1 #-alignsettings_limitsize 500 #-alignsettings_optimizeexposure 0 #-hdrsettings_defaultlinkmode nolink #-hdrsettings_donotask 0 #-batchsettings_align 0 #-batchsettings_stitch 1 #-batchsettings_stitchonlyifcontrolpoints 1 #-defaultprojectfilenamemode firstsourceimage #-defaultprojectfilename_custom " Panorama" #-defaultprojectfoldermode sourcefolder #-defaultprojectfolder_custom "" #-defaultpanoramafilenamemode asproject #-defaultpanoramafilename_custom "" #-defaultpanoramafoldermode projectfolder #-defaultpanoramafolder_custom "" #-userelativesourceimagepaths 1 #-optimizeraskreinitialize 1 #-applytemplate_lens 1 #-applytemplate_imageparams 1 #-applytemplate_crop 1 #-applytemplate_mask 1 #-applytemplate_panoramasettings 1 #-applytemplate_projectsettings 1 #-applytemplate_optimizer 1 #-globalcrop 0 0 0 0 0 -0.0046875 0.001041666666666667 0.3151041666666667 #-theoreticalhfov -1 #-rect_compression_x 0 #-rect_compression_y 0 #-cylindrical_compression_y 0 #-transverse_cylindrical_compression_x 0 #-vedutismo_compression_x 1 #-transverse_vedutismo_compression_y 1 #-stereographic_compression 1 #-rectifisheye_compression 1 */ } // namespace Core } // namespace VideoStitch #endif // __clang_analyzer__