stacker.cpp

Go to the documentation of this file.
00001 // -*- c-basic-offset: 4 -*-
00002 
00011 /*  This program is free software; you can redistribute it and/or
00012 *  modify it under the terms of the GNU General Public
00013 *  License as published by the Free Software Foundation; either
00014 *  version 2 of the License, or (at your option) any later version.
00015 *
00016 *  This software is distributed in the hope that it will be useful,
00017 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00018 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00019 *  General Public License for more details.
00020 *
00021 *  You should have received a copy of the GNU General Public
00022 *  License along with this software. If not, see
00023 *  <http://www.gnu.org/licenses/>.
00024 *
00025 */
00026 
00027 #include <stdio.h>
00028 #include <iostream>
00029 #include <getopt.h>
00030 #include <hugin_utils/utils.h>
00031 #include <hugin_utils/stl_utils.h>
00032 #include <vigra/imageinfo.hxx>
00033 #include <vigra/codec.hxx>
00034 #include <vigra/stdimage.hxx>
00035 #include <vigra_ext/impexalpha.hxx>
00036 #include <vigra/tiff.hxx>
00037 #include <vigra_ext/tiffUtils.h>
00038 #include <vigra_ext/openmp_vigra.h>
00039 
00041 void SetCompression(vigra::ImageExportInfo& output, const std::string& compression)
00042 {
00043     const std::string ext(hugin_utils::toupper(hugin_utils::getExtension(output.getFileName())));
00044     if (!compression.empty())
00045     {
00046         if (ext == "JPEG" || ext == "JPG")
00047         {
00048             output.setCompression(std::string("JPEG QUALITY=" + compression).c_str());
00049         }
00050         else
00051         {
00052             output.setCompression(compression.c_str());
00053         };
00054     };
00055 };
00056 
00058 template <class ImageType, class MaskType>
00059 bool SaveImage(ImageType& image, MaskType& mask, vigra::ImageExportInfo& exportImageInfo, std::string filetype, std::string pixelType)
00060 {
00061     typedef typename vigra::NumericTraits<typename ImageType::value_type>::isScalar scalar;
00062     const int numberChannels = scalar().asBool ? 1 : 3;
00063     exportImageInfo.setPixelType(pixelType.c_str());
00064     if (vigra::isBandNumberSupported(filetype, numberChannels +1))
00065     {
00066         try
00067         {
00068             vigra::exportImageAlpha(vigra::srcImageRange(image), vigra::srcImage(mask), exportImageInfo);
00069         }
00070         catch (std::exception& e)
00071         {
00072             std::cerr << "ERROR: Could not save " << exportImageInfo.getFileName() << std::endl
00073                 << "Cause: " << e.what() << std::endl;
00074             return false;
00075         }
00076         return true;
00077     }
00078     else
00079     {
00080         if (vigra::isBandNumberSupported(filetype, numberChannels))
00081         {
00082             std::cout << "Warning: Filetype " << filetype << " does not support alpha channels." << std::endl
00083                 << "Saving image without alpha channel." << std::endl;
00084             try
00085             {
00086                 vigra::exportImage(vigra::srcImageRange(image), exportImageInfo);
00087             }
00088             catch (std::exception& e)
00089             {
00090                 std::cerr << "ERROR: Could not save " << exportImageInfo.getFileName() << std::endl
00091                     << "Cause: " << e.what() << std::endl;
00092                 return false;
00093             };
00094             return true;
00095         }
00096         else
00097         {
00098             std::cerr << "ERROR: Output filetype " << filetype << " does not support " << numberChannels << " channels." << std::endl
00099                 << "Can't save image." << std::endl;
00100         };
00101     };
00102     return false;
00103 };
00104 
00106 template <class ImageType, class MaskType>
00107 bool SaveFinalImage(ImageType& image, MaskType& mask, const std::string& inputPixelType, vigra::ImageExportInfo& output)
00108 {
00109     VIGRA_UNIQUE_PTR<vigra::Encoder> encoder(vigra::encoder(output));
00110     if (vigra::isPixelTypeSupported(encoder->getFileType(), inputPixelType))
00111     {
00112         return SaveImage(image, mask, output, encoder->getFileType(), inputPixelType);
00113     }
00114     else
00115     {
00116         if (vigra::isPixelTypeSupported(encoder->getFileType(), "UINT8"))
00117         {
00118             // transform to UINT8
00119             output.setForcedRangeMapping(0, vigra::NumericTraits<typename vigra::NumericTraits<typename ImageType::PixelType>::ValueType>::max(), 0, 255);
00120             return SaveImage(image, mask, output, encoder->getFileType(), "UINT8");
00121         }
00122         else
00123         {
00124             std::cerr << "ERROR: Output file type " << encoder->getFileType() << " does not support" << std::endl
00125                 << "requested pixeltype " << inputPixelType << "." << std::endl
00126                 << "Save output in other file format." << std::endl;
00127         };
00128     };
00129     return false;
00130 };
00131 
00133 static void usage(const char* name)
00134 {
00135     std::cout << name << ": stack images" << std::endl
00136         << name << " version " << hugin_utils::GetHuginVersion() << std::endl
00137         << std::endl
00138         << "Usage:  " << name << " [options] images" << std::endl
00139         << std::endl
00140         << "     --output=FILE            Set the filename for the output file." << std::endl
00141         << "     --compression=value      Compression of the output files" << std::endl
00142         << "                                For jpeg output: 0-100" << std::endl
00143         << "                                For tiff output: PACKBITS, DEFLATE, LZW" << std::endl
00144         << "     --mode=STRING            Select the mode for stacking" << std::endl
00145         << "                              Possible names are described below." << std::endl
00146         << std::endl
00147         << "        min|minimum|darkest   Select the darkest pixel" << std::endl
00148         << "        max|maximum|brightest Select the brightest pixel" << std::endl
00149         << "        avg|average|mean      Calculate the mean for each position" << std::endl
00150         << "        median                Calculate the median for each position" << std::endl
00151         << "        winsor                Calculate the Winsor trimmed mean" << std::endl
00152         << "                              for each position. The parameter can be" << std::endl
00153         << "                              set with --winsor-trim=NUMBER (default: 0.2)" << std::endl
00154         << "        sigma                 Calculate the sigma clipped mean for" << std::endl
00155         << "                              each position. Fine-tune with" << std::endl
00156         << "                              --max-sigma=NUMBER (default: 2) and" << std::endl
00157         << "                              --max-iterations=NUMBER (default: 5)" << std::endl
00158         << std::endl
00159         << "     --mask-input             Mask input images" << std::endl
00160         << "                              Only pixel which differ more than" << std::endl
00161         << "                              mask-sigma * standard deviation" << std::endl
00162         << "                              are visible in output" << std::endl
00163         << "                              available for modes median|winsor|clip" << std::endl
00164         << "     --mask-suffix=STRING     Suffix for the masked input images" << std::endl
00165         << "                              (default: _mask)" << std::endl
00166         << "     --mask-sigma=NUMBER      Sigma parameter for input images masking" << std::endl
00167         << "                              (default: 2)" << std::endl
00168         << "     --multi-layer-output     Output layered TIFF instead of single images" << std::endl
00169         << "                              (has only effect with --mask-input)" << std::endl
00170         << "     --bigtiff                Write output in BigTIFF format" << std::endl
00171         << "                              (only with TIFF output)" << std::endl
00172         << "     -h, --help               Shows this help" << std::endl
00173         << std::endl;
00174 };
00175 
00176 static struct GeneralParameters
00177 {
00178 public:
00179     // for output file
00180     std::string outputFilename;
00181     std::string compression;
00182     // stacking mode
00183     std::string stackMode;
00184     // for sigma clipping
00185     double sigma = 2;
00186     int maxIterations = 5;
00187     // for Winsor trimmed mean
00188     double winsorTrim = 0.2;
00189     // for masking input images
00190     double maskSigma = 2;
00191     // should masked input images saved
00192     bool maskInput = false;
00193     std::string maskSuffix = "_mask";
00194     bool multiLayer = false;
00195     bool useBigTIFF = false;
00196 } Parameters;
00197 
00198 class InputImage
00199 {
00200 public:
00201     explicit InputImage(const std::string filename) : m_info(filename.c_str())
00202     {
00203         m_filename = filename;
00204         m_canvassize=m_info.getCanvasSize();
00205         if (m_canvassize.area() == 0)
00206         {
00207             // not all images contains the canvas size/full image size
00208             // in this case take also the position into account to get full image size
00209             m_canvassize =vigra::Size2D(m_info.width(), m_info.height());
00210         };
00211         m_offsetX = m_info.getPosition().x;
00212         m_offsetY = m_info.getPosition().y;
00213         m_decoder=vigra::decoder(m_info);
00214         m_hasAlpha = m_info.numExtraBands() == 1;
00215         m_width=m_decoder->getWidth();
00216         m_height=m_decoder->getHeight();
00217         m_bands=m_decoder->getNumBands();
00218         m_offset=m_decoder->getOffset();
00219         m_x = 0;
00220         m_y = 0;
00221     };
00222     ~InputImage()
00223     {
00224         m_decoder->abort();
00225     };
00226     const std::string getPixelType() const { return m_info.getPixelType(); };
00227     const bool isColor() const { return m_info.isColor(); };
00228     const bool isGrayscale() const { return m_info.isGrayscale(); };
00229     const int numBands() const { return m_bands; };
00230     const int numExtraBands() const { return m_info.numExtraBands(); }
00231     const int numPixelSamples() const { return m_bands - m_info.numExtraBands(); };
00232     const float getXResolution() const { return m_info.getXResolution(); };
00233     const float getYResolution() const { return m_info.getYResolution(); };
00234     const vigra::ImageImportInfo::ICCProfile getICCProfile() const { return m_info.getICCProfile(); };
00235     const std::string getFilename() const { return m_filename; };
00236     const vigra::Rect2D getROI() const { return vigra::Rect2D(vigra::Point2D(m_offsetX,m_offsetY), vigra::Size2D(m_width, m_height)); };
00237     const vigra::Size2D getCanvasSize() const { return m_canvassize; };
00238     const std::string getMaskFilename() const { return hugin_utils::stripExtension(m_filename) + Parameters.maskSuffix + ".tif"; };
00239     const vigra::ImageImportInfo& getImageImportInfo() const { return m_info; };
00240     void readLine(const int y)
00241     {
00242         if (y < m_offsetY + static_cast<int>(m_height))
00243         {
00244             if (y < m_offsetY)
00245             {
00246                 m_noData = true;
00247             }
00248             else
00249             {
00250                 m_noData = false;
00251                 m_decoder->nextScanline();
00252                 ++m_y;
00253                 while (y+1 < static_cast<int>(m_y) + m_offsetY)
00254                 {
00255                     m_decoder->nextScanline();
00256                     ++m_y;
00257                 }
00258             }
00259         }
00260         else
00261         {
00262             m_noData = true;
00263         };
00264     };
00265     template<class ValueType>
00266     void getValue(const int x, vigra::RGBValue<ValueType>& value, ValueType& mask)
00267     {
00268         if (m_noData)
00269         {
00270             mask = vigra::NumericTraits<ValueType>::zero();
00271         }
00272         else
00273         {
00274             if (x < m_offsetX)
00275             {
00276                 mask = vigra::NumericTraits<ValueType>::zero();
00277             }
00278             else
00279             {
00280                 if (x < m_offsetX + static_cast<int>(m_width))
00281                 {
00282                     const ValueType* band0= static_cast<const ValueType*>(m_decoder->currentScanlineOfBand(0));
00283                     const ValueType* band1 = static_cast<const ValueType*>(m_decoder->currentScanlineOfBand(1));
00284                     const ValueType* band2 = static_cast<const ValueType*>(m_decoder->currentScanlineOfBand(2));
00285                     band0 += m_offset*(x - m_offsetX);
00286                     band1 += m_offset*(x - m_offsetX);
00287                     band2 += m_offset*(x - m_offsetX);
00288                     if (m_bands == 4)
00289                     {
00290                         const ValueType* band3 = static_cast<const ValueType*>(m_decoder->currentScanlineOfBand(3));
00291                         band3 += m_offset*(x - m_offsetX);
00292                         mask = *band3;
00293                     }
00294                     else
00295                     {
00296                         mask = vigra::NumericTraits<ValueType>::max();
00297                     };
00298                     value = vigra::RGBValue<ValueType>(*band0, *band1, *band2);
00299                 }
00300                 else
00301                 {
00302                     mask = vigra::NumericTraits<ValueType>::zero();
00303                 };
00304             };
00305         };
00306     };
00307     template<class ValueType>
00308     void getValue(const int x, ValueType& value, ValueType& mask)
00309     {
00310         if (m_noData)
00311         {
00312             mask = vigra::NumericTraits<ValueType>::zero();
00313         }
00314         else
00315         {
00316             if (x < m_offsetX)
00317             {
00318                 mask = vigra::NumericTraits<ValueType>::zero();
00319             }
00320             else
00321             {
00322                 if (x < m_offsetX + static_cast<int>(m_width))
00323                 {
00324                     const ValueType* band0 = static_cast<const ValueType*>(m_decoder->currentScanlineOfBand(0));
00325                     band0 += m_offset*(x - m_offsetX);
00326                     if (m_bands == 2)
00327                     {
00328                         const ValueType* band1 = static_cast<const ValueType*>(m_decoder->currentScanlineOfBand(1));
00329                         band1 += m_offset*(x - m_offsetX);
00330                         mask = *band1;
00331                     }
00332                     else
00333                     {
00334                         mask = vigra::NumericTraits<ValueType>::max();
00335                     };
00336                     value = *band0;
00337                 }
00338                 else
00339                 {
00340                     mask = vigra::NumericTraits<ValueType>::zero();
00341                 };
00342             };
00343         };
00344     };
00345 
00346 private:
00347     std::string m_filename;
00348     vigra::ImageImportInfo m_info;
00349     vigra::Size2D m_canvassize;
00350     int m_offsetX, m_offsetY;
00351     unsigned m_x, m_y, m_width, m_height, m_offset, m_bands;
00352     VIGRA_UNIQUE_PTR<vigra::Decoder> m_decoder;
00353     bool m_hasAlpha, m_noData;
00354 };
00355 
00356 template<class ValueType>
00357 void getMean(const std::vector<ValueType>& values, ValueType& val)
00358 {
00359     size_t n = 0;
00360     typedef vigra::NumericTraits<ValueType> RealTraits;
00361     typename RealTraits::RealPromote mean = RealTraits::zero();
00362     for (auto& x : values)
00363     {
00364         ++n;
00365         mean += (x - mean) / n;
00366     };
00367     val = RealTraits::fromRealPromote(mean);
00368 };
00369 
00370 template<class ValueType>
00371 void getMeanSigma(const std::vector<ValueType>& values, ValueType& val, typename vigra::NumericTraits<ValueType>::RealPromote& sigma)
00372 {
00373     typedef vigra::NumericTraits<ValueType> RealTraits;
00374     typedef typename RealTraits::RealPromote RealType;
00375     size_t n = 0;
00376     RealType mean = RealTraits::zero();
00377     RealType m2 = RealTraits::zero();
00378     for (auto& x : values)
00379     {
00380         ++n;
00381         const RealType delta = x - mean;
00382         mean += delta / n;
00383         const RealType delta2 = x - mean;
00384         m2 += delta*delta2;
00385     };
00386     val = RealTraits::fromRealPromote(mean);
00387     if (n >= 2)
00388     {
00389         sigma = sqrt(m2 / n);
00390     }
00391     else
00392     {
00393         sigma = vigra::NumericTraits<RealType>::zero();
00394     };
00395 };
00396 
00397 template<class ValueType>
00398 class MinStacker
00399 {
00400 public:
00401     void reset() { m_min = vigra::NumericTraits<ValueType>::max(); };
00402     void operator()(const ValueType& val, vigra::VigraTrueType) { if (val < m_min) m_min = val; };
00403     void operator()(const ValueType& val, vigra::VigraFalseType) { if (val.luminance() < m_min.luminance()) m_min = val; }
00404     void operator()(const ValueType& val)
00405     {
00406         typedef typename vigra::NumericTraits<ValueType>::isScalar is_scalar;
00407         operator()(val, is_scalar());
00408     };
00409     void getResult(ValueType& val) { val = m_min; };
00410     bool IsValid() { return m_min != vigra::NumericTraits<ValueType>::max(); };
00411 private:
00412     ValueType m_min;
00413 };
00414 
00415 template<class ValueType>
00416 class MaxStacker
00417 {
00418 public:
00419     virtual void reset() { m_max = vigra::NumericTraits<ValueType>::min(); };
00420     void operator()(const ValueType& val, vigra::VigraTrueType) { if (val > m_max) m_max = val; };
00421     void operator()(const ValueType& val, vigra::VigraFalseType) { if (val.luminance() > m_max.luminance()) m_max = val;}
00422     virtual void operator()(const ValueType& val)
00423     {
00424         typedef typename vigra::NumericTraits<ValueType>::isScalar is_scalar;
00425         operator()(val, is_scalar());
00426     };
00427     virtual void getResult(ValueType& val) { val = m_max; };
00428     virtual bool IsValid() { return m_max != vigra::NumericTraits<ValueType>::min(); };
00429 private:
00430     ValueType m_max;
00431 };
00432 
00433 template<class ValueType>
00434 class AverageStacker
00435 {
00436 public:
00437     virtual void reset() { m_sum = vigra::NumericTraits<ValueType>::zero(); m_count = 0; };
00438     virtual void operator()(const ValueType& val) { m_sum += val; ++m_count;};
00439     virtual void getResult(ValueType& val) { val = m_sum / m_count; };
00440     virtual bool IsValid() { return m_count > 0; };
00441 
00442 private:
00443     typename vigra::NumericTraits<ValueType>::RealPromote m_sum;
00444     size_t m_count;
00445 };
00446 
00447 template<class ValueType>
00448 class MedianStacker
00449 {
00450 public:
00451     void reset() { m_values.clear(); };
00452     void operator()(const ValueType& val) { m_values.push_back(val); };
00453     void getResult(ValueType& val)
00454     {
00455         sort();
00456         if (m_values.size() % 2 == 1)
00457         {
00458             val = m_values[(m_values.size() - 1) / 2];
00459         }
00460         else
00461         {
00462             const int index = m_values.size() / 2;
00463             val = 0.5 * (m_values[index - 1] + m_values[index]);
00464         };
00465     };
00466     bool IsValid() { return !m_values.empty();};
00467     void getResultAndSigma(ValueType& val, typename vigra::NumericTraits<ValueType>::RealPromote& sigma)
00468     {
00469         sort();
00470         if (m_values.size() % 2 == 1)
00471         {
00472             val = m_values[(m_values.size() - 1) / 2];
00473         }
00474         else
00475         {
00476             const int index = m_values.size() / 2;
00477             val = 0.5 * (m_values[index - 1] + m_values[index]);
00478         };
00479         ValueType mean;
00480         getMeanSigma(m_values, mean, sigma);
00481     };
00482     const std::string getName() const { return "median"; };
00483 protected:
00484     // sort gray scale
00485     void sort(vigra::VigraTrueType)
00486     {
00487         std::sort(m_values.begin(), m_values.end());
00488     };
00489     // sort color values
00490     void sort(vigra::VigraFalseType)
00491     {
00492         std::sort(m_values.begin(), m_values.end(),
00493             [](const ValueType & a, const ValueType & b) {return a.luminance() < b.luminance(); });
00494     };
00495     // generic sort
00496     void sort()
00497     {
00498         typedef typename vigra::NumericTraits<ValueType>::isScalar is_scalar;
00499         sort(is_scalar());
00500     };
00501 
00502     std::vector<ValueType> m_values;
00503 };
00504 
00505 template<class ValueType>
00506 class WinsorMeanStacker : public MedianStacker<ValueType>
00507 {
00508 public:
00509     virtual void getResult(ValueType& val)
00510     {
00511         this->sort();
00512         const int indexTrim = hugin_utils::floori(Parameters.winsorTrim * this->m_values.size());
00513         for (size_t i = 0; i < indexTrim; ++i)
00514         {
00515             this->m_values[i] = this->m_values[indexTrim];
00516         }
00517         for (size_t i = this->m_values.size() - indexTrim; i < this->m_values.size(); ++i)
00518         {
00519             this->m_values[i] = this->m_values[this->m_values.size() - indexTrim - 1];
00520         };
00521         getMean(this->m_values, val);
00522     };
00523     virtual void getResultAndSigma(ValueType& val, typename vigra::NumericTraits<ValueType>::RealPromote& sigma)
00524     {
00525         this->sort();
00526         const int indexTrim = hugin_utils::floori(Parameters.winsorTrim * this->m_values.size());
00527         for (size_t i = 0; i < indexTrim; ++i)
00528         {
00529             this->m_values[i] = this->m_values[indexTrim];
00530         }
00531         for (size_t i = this->m_values.size() - indexTrim; i < this->m_values.size(); ++i)
00532         {
00533             this->m_values[i] = this->m_values[this->m_values.size() - indexTrim - 1];
00534         };
00535         getMeanSigma(this->m_values, val, sigma);
00536     };
00537     const std::string getName() const { return "Winsor clipped mean"; };
00538 };
00539 
00540 template<class ValueType>
00541 class SigmaMeanStacker
00542 {
00543 public:
00544     virtual void reset() { m_values.clear(); m_sortValues.clear(); };
00545     void operator()(const ValueType& val, vigra::VigraTrueType) { m_values.push_back(val); m_sortValues.push_back(val); };
00546     void operator()(const ValueType& val, vigra::VigraFalseType) { m_values.push_back(val); m_sortValues.push_back(val.luminance()); };
00547     virtual void operator()(const ValueType& val)
00548     {
00549         typedef typename vigra::NumericTraits<ValueType>::isScalar is_scalar;
00550         operator()(val, is_scalar());
00551     };
00552     virtual void getResult(ValueType& val)
00553     {
00554         size_t iteration = 0;
00555         while (iteration < Parameters.maxIterations)
00556         {
00557             double mean, sigma;
00558             getMeanSigma(m_sortValues, mean, sigma);
00559             const size_t oldSize = m_sortValues.size();
00560             for (int i = m_sortValues.size() - 1; i >= 0 && m_sortValues.size() > 1; --i)
00561             {
00562                 // check if values are in range
00563                 if (abs(m_sortValues[i] - mean) > Parameters.sigma * sigma)
00564                 {
00565                     m_sortValues.erase(m_sortValues.begin() + i);
00566                     m_values.erase(m_values.begin() + i);
00567                 };
00568             };
00569             if (m_sortValues.size() == oldSize)
00570             {
00571                 // no values outside range, return mean value
00572                 getMean(m_values, val);
00573                 return;
00574             };
00575             ++iteration;
00576         };
00577         getMean(m_values, val);
00578     };
00579     virtual void getResultAndSigma(ValueType& val, typename vigra::NumericTraits<ValueType>::RealPromote& sigma)
00580     {
00581         size_t iteration = 0;
00582         while (iteration < Parameters.maxIterations)
00583         {
00584             double grayMean, graySigma;
00585             getMeanSigma(m_sortValues, grayMean, graySigma);
00586             const size_t oldSize = m_sortValues.size();
00587             for (int i = m_sortValues.size() - 1; i >= 0 && m_sortValues.size() > 1; --i)
00588             {
00589                 // check if values are in range
00590                 if (abs(m_sortValues[i] - grayMean) > Parameters.sigma * graySigma)
00591                 {
00592                     m_sortValues.erase(m_sortValues.begin() + i);
00593                     m_values.erase(m_values.begin() + i);
00594                 };
00595             };
00596             if (m_sortValues.size() == oldSize)
00597             {
00598                 // no values outside range, return mean value
00599                 getMeanSigma(m_values, val, sigma);
00600                 return;
00601             };
00602             ++iteration;
00603         };
00604         getMeanSigma(m_values, val, sigma);
00605     };
00606     virtual bool IsValid() { return !m_values.empty(); };
00607     const std::string getName() const { return "sigma clipped mean"; };
00608 
00609 private:
00610     std::vector<ValueType> m_values;
00611     std::vector<double> m_sortValues;
00612 };
00613 
00614 bool CheckInput(const std::vector<InputImage*>& images, vigra::Rect2D& outputROI, vigra::Size2D& canvasSize)
00615 {
00616     if (images.empty())
00617     {
00618         return false;
00619     };
00620     // get ROI and canvas size which contains all images
00621     outputROI = images[0]->getROI();
00622     canvasSize = images[0]->getCanvasSize();
00623     for (size_t i = 1; i < images.size(); ++i)
00624     {
00625         outputROI |= images[i]->getROI();
00626         if (images[i]->getCanvasSize().width() > canvasSize.width())
00627         {
00628             canvasSize.setWidth(images[i]->getCanvasSize().width());
00629         };
00630         if (images[i]->getCanvasSize().height() > canvasSize.height())
00631         {
00632             canvasSize.setHeight(images[i]->getCanvasSize().height());
00633         };
00634     };
00635     if (outputROI.area() == 0)
00636     {
00637         std::cerr << "ERROR: You can't stack non-overlapping images." << std::endl;
00638         return false;
00639     };
00640     return true;
00641 }
00642 
00644 template <class PixelType, class Functor>
00645 bool StackImages(std::vector<InputImage*>& images, Functor& stacker)
00646 {
00647     typedef typename vigra::NumericTraits<PixelType>::ValueType ChannelType;
00648     vigra::Rect2D outputROI;
00649     vigra::Size2D canvasSize;
00650     if (!CheckInput(images, outputROI, canvasSize))
00651     {
00652         return false;
00653     }
00654     // prepare output
00655     vigra::ImageExportInfo exportImageInfo(Parameters.outputFilename.c_str(), Parameters.useBigTIFF ? "w8" : "w");
00656     exportImageInfo.setXResolution(images[0]->getXResolution());
00657     exportImageInfo.setYResolution(images[0]->getYResolution());
00658     exportImageInfo.setPosition(outputROI.upperLeft());
00659     exportImageInfo.setCanvasSize(canvasSize);
00660     exportImageInfo.setICCProfile(images[0]->getICCProfile());
00661     SetCompression(exportImageInfo, Parameters.compression);
00662     vigra::BasicImage<PixelType> output(outputROI.size());
00663     vigra::BImage mask(output.size(),vigra::UInt8(0));
00664     // loop over all lines
00665     for (size_t y = outputROI.top(); y < outputROI.bottom(); ++y)
00666     {
00667         // load next line
00668 #pragma omp parallel for
00669         for (int i = 0; i < images.size(); ++i)
00670         {
00671             images[i]->readLine(y);
00672         };
00673         // process current line
00674 #pragma omp parallel for schedule(static, 100)
00675         for (int x = outputROI.left(); x < outputROI.right(); ++x)
00676         {
00677             // we need a private copy for each thread
00678             Functor privateStacker(stacker);
00679             privateStacker.reset();
00680             for (size_t i = 0; i < images.size(); ++i)
00681             {
00682                 PixelType value;
00683                 ChannelType maskValue;
00684                 images[i]->getValue(x, value, maskValue);
00685                 if (maskValue > 0)
00686                 {
00687                     privateStacker(value);
00688                 }
00689             };
00690             if (privateStacker.IsValid())
00691             {
00692                 privateStacker.getResult(output(x - outputROI.left(), y - outputROI.top()));
00693                 mask(x-outputROI.left(), y-outputROI.top()) = 255;
00694             };
00695         };
00696     };
00697     std::cout << "Write result to " << Parameters.outputFilename << std::endl;
00698     return SaveFinalImage(output, mask, images[0]->getPixelType(), exportImageInfo);
00699 };
00700 
00701 template <class PixelType>
00702 class FilterMask
00703 {
00704 public:
00705     typedef typename vigra::NumericTraits<PixelType>::RealPromote realPixelType;
00706     typedef vigra::TinyVector<realPixelType, 2> first_argument_type;
00707     typedef PixelType second_argument_type;
00708     typedef vigra::UInt8 third_argument_type;
00709     typedef vigra::UInt8 result_type;
00710     vigra::UInt8 operator()(const vigra::TinyVector<realPixelType, 2>& limits, const PixelType& color, const vigra::UInt8& mask, vigra::VigraFalseType) const
00711     {
00712         if (mask > 0 && (color.red() < limits[0].red() || color.red()>limits[1].red() ||
00713             color.green() < limits[0].green() || color.green() > limits[1].green() ||
00714             color.blue() < limits[0].blue() || color.blue() > limits[1].blue()))
00715         {
00716             return 255;
00717         }
00718         else
00719         {
00720             return 0;
00721         };
00722     };
00723 
00724     vigra::UInt8 operator()(const vigra::TinyVector<realPixelType, 2>& limits, const PixelType& gray, const vigra::UInt8& mask, vigra::VigraTrueType) const
00725     {
00726         if (mask > 0 && (gray < limits[0] || gray>limits[1]))
00727         {
00728             return 255;
00729         }
00730         else
00731         {
00732             return 0;
00733         };
00734     };
00735 
00736     vigra::UInt8 operator()(const vigra::TinyVector<realPixelType, 2>& limits, const PixelType& pixel, const vigra::UInt8& mask) const
00737     {
00738         typedef typename vigra::NumericTraits<PixelType>::isScalar is_scalar;
00739         return (*this)(limits, pixel, mask, is_scalar());
00740     }
00741 
00742 };
00743 
00744 template <class PixelType, class Functor>
00745 bool StackImagesAndMask(std::vector<InputImage*>& images, Functor& stacker)
00746 {
00747     typedef typename vigra::NumericTraits<PixelType>::ValueType ChannelType;
00748     vigra::Rect2D outputROI;
00749     vigra::Size2D canvasSize;
00750     if (!CheckInput(images, outputROI, canvasSize))
00751     {
00752         return false;
00753     }
00754     // prepare output
00755     vigra::ImageExportInfo exportImageInfo(Parameters.outputFilename.c_str(), Parameters.useBigTIFF ? "w8" : "w");
00756     exportImageInfo.setXResolution(images[0]->getXResolution());
00757     exportImageInfo.setYResolution(images[0]->getYResolution());
00758     exportImageInfo.setPosition(outputROI.upperLeft());
00759     exportImageInfo.setCanvasSize(canvasSize);
00760     exportImageInfo.setICCProfile(images[0]->getICCProfile());
00761     SetCompression(exportImageInfo, Parameters.compression);
00762     // for multi-layer output
00763     vigra::TiffImage* tiffImage;
00764     vigra::BasicImage<PixelType> output(outputROI.size());
00765     vigra::BImage mask(output.size(), vigra::UInt8(0));
00766     vigra::BasicImage<vigra::TinyVector<typename vigra::NumericTraits<PixelType>::RealPromote, 2>> limits(output.size());
00767     // loop over all lines
00768     for (size_t y = outputROI.top(); y < outputROI.bottom(); ++y)
00769     {
00770         // load next line
00771 #pragma omp parallel for
00772         for (int i = 0; i < images.size(); ++i)
00773         {
00774             images[i]->readLine(y);
00775         };
00776         // process current line
00777 #pragma omp parallel for schedule(static, 100)
00778         for (int x = outputROI.left(); x < outputROI.right(); ++x)
00779         {
00780             // we need a private copy for each thread
00781             Functor privateStacker(stacker);
00782             privateStacker.reset();
00783             for (size_t i = 0; i < images.size(); ++i)
00784             {
00785                 PixelType value;
00786                 ChannelType maskValue;
00787                 images[i]->getValue(x, value, maskValue);
00788                 if (maskValue > 0)
00789                 {
00790                     privateStacker(value);
00791                 }
00792             };
00793             if (privateStacker.IsValid())
00794             {
00795                 PixelType mean;
00796                 typename vigra::NumericTraits<PixelType>::RealPromote sigma;
00797                 privateStacker.getResultAndSigma(mean, sigma);
00798                 output(x - outputROI.left(), y - outputROI.top()) = mean;
00799                 mask(x - outputROI.left(), y - outputROI.top()) = 255;
00800                 limits(x - outputROI.left(), y - outputROI.top()) = vigra::TinyVector<PixelType, 2>(mean - Parameters.maskSigma*sigma, mean + Parameters.maskSigma*sigma);
00801             };
00802         };
00803     };
00804     std::cout << "Write result to " << Parameters.outputFilename << std::endl;
00805     if (Parameters.multiLayer)
00806     {
00807         tiffImage = TIFFOpen(Parameters.outputFilename.c_str(), "w");
00808         vigra_ext::createTiffDirectory(tiffImage, stacker.getName(), stacker.getName(), 
00809             Parameters.compression.empty() ? "LZW" : Parameters.compression, 0, images.size() + 1,
00810             outputROI.upperLeft(), canvasSize, images[0]->getICCProfile());
00811         vigra_ext::createAlphaTiffImage(vigra::srcImageRange(output), vigra::maskImage(mask), tiffImage);
00812         TIFFFlush(tiffImage);
00813     }
00814     else
00815     {
00816         if (!SaveFinalImage(output, mask, images[0]->getPixelType(), exportImageInfo))
00817         {
00818             return false;
00819         };
00820     };
00821     // we don't need median image any more
00822     output.resize(0, 0);
00823     std::cout << "Masking input images with sigma=" << Parameters.maskSigma;
00824     if (Parameters.multiLayer)
00825     {
00826         std::cout << std::endl;
00827     }
00828     else
00829     {
00830         std::cout << " and suffix " << Parameters.maskSuffix << ".tif" << std::endl;
00831     };
00832     for (size_t i = 0; i < images.size(); ++i)
00833     {
00834         std::cout << "Masking " << images[i]->getFilename();
00835         if (Parameters.multiLayer)
00836         {
00837             std::cout << std::endl;
00838         }
00839         else
00840         {
00841             std::cout << " -> " << images[i]->getMaskFilename() << std::endl;
00842         };
00843         vigra::BasicImage<PixelType> image(images[i]->getROI().size());
00844         vigra::BImage mask(image.size(), 255);
00845         if (images[i]->numExtraBands() == 1)
00846         {
00847             vigra::importImageAlpha(images[i]->getImageImportInfo(), vigra::destImage(image), vigra::destImage(mask));
00848         }
00849         else
00850         {
00851             vigra::importImage(images[i]->getImageImportInfo(), vigra::destImage(image));
00852         };
00853         vigra::Rect2D roi = images[i]->getROI();
00854         roi.moveBy(-outputROI.upperLeft());
00855         vigra::combineThreeImages(vigra::srcImageRange(limits, roi), vigra::srcImage(image), vigra::srcImage(mask), vigra::destImage(mask), FilterMask<PixelType>());
00856         if (hugin_utils::FileExists(images[i]->getMaskFilename()))
00857         {
00858             std::cout << "Masked file \"" << images[i]->getMaskFilename() << "\" already exists." << std::endl
00859                 << "Processing aborted." << std::endl;
00860             return false;
00861         }
00862         if (Parameters.multiLayer)
00863         {
00864             vigra_ext::createTiffDirectory(tiffImage, images[i]->getFilename(), images[i]->getFilename(), 
00865                 Parameters.compression.empty() ? "LZW" : Parameters.compression, i+1, images.size() + 1,
00866                 images[i]->getROI().upperLeft(), images[i]->getCanvasSize(), images[i]->getICCProfile());
00867             vigra_ext::createAlphaTiffImage(vigra::srcImageRange(image), vigra::maskImage(mask), tiffImage);
00868             TIFFFlush(tiffImage);
00869         }
00870         else
00871         {
00872             vigra::ImageExportInfo exportMaskImage(images[i]->getMaskFilename().c_str(), Parameters.useBigTIFF ? "w8" : "w");
00873             exportMaskImage.setXResolution(images[i]->getXResolution());
00874             exportMaskImage.setYResolution(images[i]->getYResolution());
00875             exportMaskImage.setPosition(images[i]->getROI().upperLeft());
00876             exportMaskImage.setCanvasSize(images[i]->getCanvasSize());
00877             exportMaskImage.setICCProfile(images[i]->getICCProfile());
00878             exportMaskImage.setPixelType(images[i]->getPixelType().c_str());
00879             exportMaskImage.setCompression("LZW");
00880             try
00881             {
00882                 vigra::exportImageAlpha(vigra::srcImageRange(image), vigra::srcImage(mask), exportMaskImage);
00883             }
00884             catch (std::exception& e)
00885             {
00886                 std::cerr << "Could not save masked images \"" << exportMaskImage.getFileName() << "\"." << std::endl
00887                     << "Error code: " << e.what() << std::endl
00888                     << "Processing aborted." << std::endl;
00889                 return false;
00890             }
00891         };
00892     };
00893     if (Parameters.multiLayer)
00894     {
00895         TIFFClose(tiffImage);
00896     }
00897     return true;
00898 };
00899 
00900 void CleanUp(std::vector<InputImage*>& images)
00901 {
00902     for (auto& img : images)
00903     {
00904         delete img;
00905     };
00906 };
00907 
00908 template <class PixelType>
00909 bool main_stacker(std::vector<InputImage*>& images)
00910 {
00911     if (Parameters.stackMode == "min" || Parameters.stackMode == "minimum" || Parameters.stackMode == "darkest")
00912     {
00913         std::cout << "Merging stack with minimum operator." << std::endl;
00914         MinStacker<PixelType> stacker;
00915         return StackImages<PixelType>(images, stacker);
00916     }
00917     else
00918     {
00919         if (Parameters.stackMode == "max" || Parameters.stackMode == "maximum" || Parameters.stackMode == "brightest")
00920         {
00921             std::cout << "Merging stack with maximum operator." << std::endl;
00922             MaxStacker<PixelType> stacker;
00923             return StackImages<PixelType>(images, stacker);
00924         }
00925         else
00926         {
00927             if (Parameters.stackMode == "avg" || Parameters.stackMode == "average" || Parameters.stackMode == "mean")
00928             {
00929                 std::cout << "Merging stack with average operator." << std::endl;
00930                 AverageStacker<PixelType> stacker;
00931                 return StackImages<PixelType>(images, stacker);
00932             }
00933             else
00934             {
00935                 if (Parameters.stackMode == "median")
00936                 {
00937                     MedianStacker<PixelType> stacker;
00938                     std::cout << "Merging stack with median operator." << std::endl;
00939                     if (Parameters.maskInput)
00940                     {
00941                         return StackImagesAndMask<PixelType>(images, stacker);
00942                     }
00943                     else
00944                     {
00945                         return StackImages<PixelType>(images, stacker);
00946                     };
00947                 }
00948                 else
00949                 {
00950                     if (Parameters.stackMode == "winsor")
00951                     {
00952                         WinsorMeanStacker<PixelType> stacker;
00953                         std::cout << "Merging stack with Winsor clipping operator (trim=" << Parameters.winsorTrim << ")." << std::endl;
00954                         if (Parameters.maskInput)
00955                         {
00956                             return StackImagesAndMask<PixelType>(images, stacker);
00957                         }
00958                         else
00959                         {
00960                             return StackImages<PixelType>(images, stacker);
00961                         };
00962                     }
00963                     else
00964                     {
00965                         if (Parameters.stackMode == "sigma")
00966                         {
00967                             SigmaMeanStacker<PixelType> stacker;
00968                             std::cout << "Merging stack with sigma clipping operator (max sigma=" << Parameters.sigma << ", max " << Parameters.maxIterations << " iterations)." << std::endl;
00969                             if (Parameters.maskInput)
00970                             {
00971                                 return StackImagesAndMask<PixelType>(images, stacker);
00972                             }
00973                             else
00974                             {
00975                                 return StackImages<PixelType>(images, stacker);
00976                             };
00977                         }
00978                         else
00979                         {
00980                             std::cerr << "ERROR: " << "\"" << Parameters.stackMode << "\" is not a valid stack mode." << std::endl
00981                                 << "        Allowed values are min|max|average|median|winsor|sigma" << std::endl;
00982                             return false;
00983                         }
00984                     };
00985                 };
00986             };
00987         };
00988     };
00989 };
00990 
00991 int main(int argc, char* argv[])
00992 {
00993     // parse arguments
00994     const char* optstring = "o:h";
00995 
00996     enum
00997     {
00998         OPT_COMPRESSION = 1000,
00999         OPT_STACKMODE,
01000         OPT_WINSOR_TRIM,
01001         OPT_SIGMA_MAX,
01002         OPT_MAX_ITER,
01003         OPT_MASK_INPUT,
01004         OPT_MASK_SUFFIX,
01005         OPT_MASK_SIGMA,
01006         OPT_MULTILAYER,
01007         OPT_BIGTIFF
01008     };
01009     static struct option longOptions[] =
01010     {
01011         { "output", required_argument, NULL, 'o' },
01012         { "compression", required_argument, NULL, OPT_COMPRESSION },
01013         { "mode", required_argument, NULL, OPT_STACKMODE },
01014         { "winsor-trim", required_argument, NULL, OPT_WINSOR_TRIM},
01015         { "max-sigma", required_argument, NULL, OPT_SIGMA_MAX},
01016         { "max-iterations", required_argument, NULL, OPT_MAX_ITER},
01017         { "mask-input", no_argument, NULL, OPT_MASK_INPUT},
01018         { "mask-suffix", required_argument, NULL, OPT_MASK_SUFFIX},
01019         { "mask-sigma", required_argument, NULL, OPT_MASK_SIGMA },
01020         { "multi-layer-output", no_argument, NULL, OPT_MULTILAYER },
01021         { "bigtiff", no_argument, NULL, OPT_BIGTIFF },
01022         { "help", no_argument, NULL, 'h' },
01023         0
01024     };
01025 
01026     int c;
01027     while ((c = getopt_long(argc, argv, optstring, longOptions, nullptr)) != -1)
01028     {
01029         switch (c)
01030         {
01031             case 'o':
01032                 Parameters.outputFilename = optarg;
01033                 break;
01034             case 'h':
01035                 usage(hugin_utils::stripPath(argv[0]).c_str());
01036                 return 0;
01037                 break;
01038             case OPT_COMPRESSION:
01039                 Parameters.compression = hugin_utils::toupper(optarg);
01040                 break;
01041             case OPT_STACKMODE:
01042                 Parameters.stackMode=optarg;
01043                 Parameters.stackMode = hugin_utils::tolower(Parameters.stackMode);
01044                 break;
01045             case OPT_WINSOR_TRIM:
01046                 {
01047                     std::string text(optarg);
01048                     int pos = text.find("%");
01049                     if (pos != std::string::npos)
01050                     {
01051                         text = text.substr(0, pos);
01052                         if (!hugin_utils::stringToDouble(text, Parameters.winsorTrim))
01053                         {
01054                             std::cerr << hugin_utils::stripPath(argv[0]) << ": No valid number for Winsor trim factor given." << std::endl;
01055                             return 1;
01056                         };
01057                         Parameters.winsorTrim /= 100.0;
01058                     }
01059                     else
01060                     {
01061                         if (!hugin_utils::stringToDouble(text, Parameters.winsorTrim))
01062                         {
01063                             std::cerr << hugin_utils::stripPath(argv[0]) << ": No valid number for Winsor trim factor given." << std::endl;
01064                             return 1;
01065                         };
01066                     };
01067                     if (Parameters.winsorTrim<0.01 || Parameters.winsorTrim>0.49)
01068                     {
01069                         std::cerr << hugin_utils::stripPath(argv[0]) << ": Winsor trim factor " << Parameters.winsorTrim << " not in valid range (0.01 - 0.49)." << std::endl;
01070                         return 1;
01071                     };
01072                 };
01073                 break;
01074             case OPT_SIGMA_MAX:
01075                     {
01076                         std::string text(optarg);
01077                         if (!hugin_utils::stringToDouble(text, Parameters.sigma))
01078                         {
01079                             std::cerr << hugin_utils::stripPath(argv[0]) << ": No valid number for maximal sigma value." << std::endl;
01080                             return 1;
01081                         };
01082                         if (Parameters.sigma<0.01)
01083                         {
01084                             std::cerr << hugin_utils::stripPath(argv[0]) << ": Maximal sigma value have to be positive." << std::endl;
01085                             return 1;
01086                         };
01087                     };
01088                     break;
01089             case OPT_MAX_ITER:
01090                 {
01091                     std::string text(optarg);
01092                     if (!hugin_utils::stringToInt(text, Parameters.maxIterations))
01093                     {
01094                         std::cerr << hugin_utils::stripPath(argv[0]) << ": No valid number for maximal iterations." << std::endl;
01095                         return 1;
01096                     };
01097                     if (Parameters.maxIterations<1)
01098                     {
01099                         std::cerr << hugin_utils::stripPath(argv[0]) << ": Maximal iterations values have to be at least 1." << std::endl;
01100                         return 1;
01101                     };
01102                 };
01103                 break;
01104             case OPT_MASK_INPUT:
01105                 Parameters.maskInput = true;
01106                 break;
01107             case OPT_MASK_SUFFIX:
01108                 Parameters.maskSuffix = optarg;
01109                 break;
01110             case OPT_MASK_SIGMA:
01111                 {
01112                     std::string text(optarg);
01113                     if (!hugin_utils::stringToDouble(text, Parameters.maskSigma))
01114                     {
01115                         std::cerr << hugin_utils::stripPath(argv[0]) << ": No valid number for maximal sigma value." << std::endl;
01116                         return 1;
01117                     };
01118                     if (Parameters.maskSigma<0.01)
01119                     {
01120                         std::cerr << hugin_utils::stripPath(argv[0]) << ": Maximal sigma value have to be positive." << std::endl;
01121                         return 1;
01122                     };
01123                 };
01124                 break;
01125             case OPT_MULTILAYER:
01126                 Parameters.multiLayer = true;
01127                 break;
01128             case OPT_BIGTIFF:
01129                 Parameters.useBigTIFF = true;
01130                 break;
01131             case ':':
01132             case '?':
01133                 // missing argument or invalid switch
01134                 return 1;
01135                 break;
01136             default:
01137                 // this should not happen
01138                 abort();
01139         }
01140     };
01141 
01142     unsigned nFiles = argc - optind;
01143     if (nFiles < 1)
01144     {
01145         std::cerr << hugin_utils::stripPath(argv[0]) << ": at least one image need to be specified" << std::endl;
01146         return 1;
01147     }
01148 
01149     // extract file names
01150     std::vector<std::string> files;
01151     for (size_t i = 0; i < nFiles; i++)
01152     {
01153         std::string currentFile(argv[optind + i]);
01154         // check file existence
01155         if (hugin_utils::FileExists(currentFile) && vigra::isImage(currentFile.c_str()))
01156         {
01157             files.push_back(currentFile);
01158         };
01159     }
01160 
01161     if (files.empty())
01162     {
01163         std::cerr << "ERROR: " << hugin_utils::stripPath(argv[0]) << " needs at least one image." << std::endl;
01164         return 1;
01165     };
01166 
01167     if (Parameters.outputFilename.empty())
01168     {
01169         Parameters.outputFilename = "final.tif";
01170     };
01171     // if no extension is given assume TIF format
01172     hugin_utils::EnforceExtension(Parameters.outputFilename, "tif");
01173     if (!hugin_utils::IsFileTypeSupported(Parameters.outputFilename))
01174     {
01175         std::cerr << "ERROR: Extension \"" << hugin_utils::getExtension(Parameters.outputFilename) << "\" is unknown." << std::endl;
01176         return 1;
01177     };
01178     const std::string extension = hugin_utils::tolower(hugin_utils::getExtension(Parameters.outputFilename));
01179     if (Parameters.multiLayer && extension != "tif" && extension != "tiff")
01180     {
01181         std::cerr << "ERROR: Multi layer output expects a tiff file as output." << std::endl
01182             << "       Other image formates are not compatible with this option." << std::endl;
01183         return 1;
01184     };
01185     bool success = false;
01186     std::vector<InputImage*> images;
01187     for (size_t i = 0; i < files.size(); ++i)
01188     {
01189         images.push_back(new InputImage(files[i]));
01190     };
01191     if (!images[0]->isColor() && !images[0]->isGrayscale())
01192     {
01193         std::cerr << "ERROR: Only RGB and grayscale images are supported." << std::endl
01194             << "       Image \"" << images[0]->getFilename() << "\" has " << images[0]->numPixelSamples() << " channels per pixel." << std::endl;
01195         CleanUp(images);
01196         return 1;
01197     }
01198     if (images[0]->numExtraBands() > 1)
01199     {
01200         std::cerr << "ERROR: Images with several alpha channels are not supported." << std::endl
01201             << "       Image \"" << images[0]->getFilename() << "\" has " << images[0]->numExtraBands() << " extra channels." << std::endl;
01202         CleanUp(images);
01203         return 1;
01204     }
01205     const std::string pixeltype(images[0]->getPixelType());
01206     //check, that image information matches
01207     for (size_t i = 1; i < files.size(); ++i)
01208     {
01209         if (!images[i]->isColor() && !images[i]->isGrayscale())
01210         {
01211             std::cerr << "ERROR: Only RGB and grayscale images are supported." << std::endl
01212                 << "       Image \"" << images[i]->getFilename() << "\" has " << images[i]->numPixelSamples() << " channels per pixel." << std::endl;
01213             CleanUp(images);
01214             return 1;
01215         };
01216         if (images[i]->numExtraBands() > 1)
01217         {
01218             std::cerr << "ERROR: Images with several alpha channels are not supported." << std::endl
01219                 << "       Image \"" << images[i]->getFilename() << "\" has " << images[i]->numExtraBands() << " extra channels." << std::endl;
01220             CleanUp(images);
01221             return 1;
01222         };
01223         if (images[0]->isColor() != images[i]->isColor())
01224         {
01225             std::cerr << "ERROR: You can't merge color and grayscale images." << std::endl;
01226             CleanUp(images);
01227             return 1;
01228         };
01229         if (images[0]->numPixelSamples() != images[i]->numPixelSamples())
01230         {
01231             std::cerr << "ERROR: You can't merge image with different number of channels." << std::endl
01232                 << "       Image \"" << images[0]->getFilename() << "\" has " << images[0]->numBands() << " channels," << std::endl
01233                 << "       but image \"" << images[i]->getFilename() << "\" has " << images[i]->numBands() << " channels." << std::endl;
01234             CleanUp(images);
01235             return 1;
01236         };
01237         if (pixeltype!=images[i]->getPixelType())
01238         {
01239             std::cerr << "ERROR: You can't merge images with different pixel types." << std::endl
01240                 << "       Image \"" << images[0]->getFilename() << "\" has pixel type " << images[0]->getPixelType() << "," << std::endl
01241                 << "       but image \"" << images[i]->getFilename() << "\" has pixel type " << images[i]->getPixelType() << "." << std::endl;
01242             CleanUp(images);
01243             return 1;
01244         };
01245     };
01246 
01247     if (images[0]->isColor())
01248     {
01249         if (pixeltype == "UINT8")
01250         {
01251             success = main_stacker<vigra::RGBValue<vigra::UInt8>>(images);
01252         }
01253         else if (pixeltype == "UINT16")
01254         {
01255             success = main_stacker<vigra::RGBValue<vigra::UInt16>>(images);
01256         }
01257         else
01258         {
01259             std::cerr << " ERROR: unsupported pixel type: " << pixeltype << std::endl;
01260         };
01261     }
01262     else
01263     {
01264         //grayscale images
01265         if (pixeltype == "UINT8")
01266         {
01267            success = main_stacker<vigra::UInt8>(images);
01268         }
01269         else if (pixeltype == "UINT16")
01270         {
01271             success = main_stacker<vigra::UInt16>(images);
01272         }
01273         else
01274         {
01275             std::cerr << " ERROR: unsupported pixel type: " << pixeltype << std::endl;
01276         };
01277     };
01278 
01279     CleanUp(images);
01280     if (success)
01281     {
01282         return 0;
01283     };
01284     return 1;
01285 }

Generated on 23 Jan 2018 for Hugintrunk by  doxygen 1.4.7