From fe7d1dc64dfd5338baed4c667f9a2866f36fe573 Mon Sep 17 00:00:00 2001
From: Andreas Schenk <andreas_schenk@hms.harvard.edu>
Date: Thu, 8 Sep 2011 18:14:57 -0400
Subject: [PATCH] added support for Ditabis Micron image plate image format

---
 modules/io/pymod/export_map_io.cc        |  13 +-
 modules/io/src/img/CMakeLists.txt        |   2 +
 modules/io/src/img/map_io_ipl_handler.cc | 415 +++++++++++++++++++++++
 modules/io/src/img/map_io_ipl_handler.hh |  78 +++++
 modules/io/src/io_manager.cc             |   4 +-
 modules/io/tests/test_io_img.cc          |  27 ++
 6 files changed, 535 insertions(+), 4 deletions(-)
 create mode 100644 modules/io/src/img/map_io_ipl_handler.cc
 create mode 100644 modules/io/src/img/map_io_ipl_handler.hh

diff --git a/modules/io/pymod/export_map_io.cc b/modules/io/pymod/export_map_io.cc
index beb7601bc..afe494d19 100644
--- a/modules/io/pymod/export_map_io.cc
+++ b/modules/io/pymod/export_map_io.cc
@@ -27,6 +27,7 @@
 #include  <ost/io/img/map_io_dat_handler.hh>
 #include  <ost/io/img/map_io_jpk_handler.hh>
 #include  <ost/io/img/map_io_nanoscope_handler.hh>
+#include  <ost/io/img/map_io_ipl_handler.hh>
 #include  <ost/io/img/image_format.hh>
 #include  <ost/io/img/load_map.hh>
 
@@ -79,18 +80,17 @@ void export_map_io()
         .export_values()
   ;
 
-  enum_<Subformat>("Format")
+  enum_<Subformat>("Subformat")
         .value("MRC_NEW_FORMAT", MRC_NEW_FORMAT)
         .value("MRC_OLD_FORMAT", MRC_OLD_FORMAT)
         .value("MRC_AUTO_FORMAT", MRC_AUTO_FORMAT)
         .export_values()
   ;
 
+
   class_<ImageFormatBase>("ImageFormatBase",no_init)
     .def("GetMaximum", &ImageFormatBase::GetMaximum)
-    .def("SetMaximum", &ImageFormatBase::GetMaximum)
     .def("GetMinimum", &ImageFormatBase::GetMinimum)
-    .def("SetMinimum", &ImageFormatBase::GetMinimum)
   ;
 
   class_<DX, bases<ImageFormatBase> >("DX", init<bool>(arg("normalize_on_save") = false))
@@ -154,6 +154,13 @@ void export_map_io()
     .def("GetBitDepth", &DAT::GetBitDepth)
   ;
 
+  class_<IPL, bases<ImageFormatBase> >("IPL", init<bool,Format>((arg("normalize_on_save") = true,arg("format")=OST_DEFAULT_FORMAT)))
+    .def("SetNormalizeOnSave", &IPL::SetNormalizeOnSave)
+    .def("GetNormalizeOnSave", &IPL::GetNormalizeOnSave)
+    .def("SetBitDepth", &IPL::SetBitDepth)
+    .def("GetBitDepth", &IPL::GetBitDepth)
+  ;
+
   class_<JPK, bases<TIF> >("JPK", init<boost::logic::tribool,Format,bool,bool,int>
            ((arg("normalize_on_save") =  boost::logic::tribool(boost::logic::indeterminate),arg("format")=OST_DEFAULT_FORMAT,arg("signed")=false,arg("phasecolor")=false,arg("subimage") = -1)))
   ;
diff --git a/modules/io/src/img/CMakeLists.txt b/modules/io/src/img/CMakeLists.txt
index 8fa9a30ff..d17f46e8e 100644
--- a/modules/io/src/img/CMakeLists.txt
+++ b/modules/io/src/img/CMakeLists.txt
@@ -7,6 +7,7 @@ map_io_mrc_handler.cc
 map_io_dm3_handler.cc
 map_io_tiff_handler.cc
 map_io_dat_handler.cc
+map_io_ipl_handler.cc
 map_io_jpk_handler.cc
 map_io_nanoscope_handler.cc
 map_io_png_handler.cc
@@ -28,6 +29,7 @@ map_io_situs_handler.hh
 map_io_handler.hh
 map_io_mrc_handler.hh
 map_io_dat_handler.hh
+map_io_ipl_handler.hh
 map_io_jpk_handler.hh
 map_io_nanoscope_handler.hh
 map_io_png_handler.hh
diff --git a/modules/io/src/img/map_io_ipl_handler.cc b/modules/io/src/img/map_io_ipl_handler.cc
new file mode 100644
index 000000000..61274b0cf
--- /dev/null
+++ b/modules/io/src/img/map_io_ipl_handler.cc
@@ -0,0 +1,415 @@
+//------------------------------------------------------------------------------
+// This file is part of the OpenStructure project <www.openstructure.org>
+//
+// Copyright (C) 2008-2011 by the OpenStructure authors
+// Copyright (C) 2003-2010 by the IPLT authors
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License as published by the Free
+// Software FounIPLion; either version 3.0 of the License, or (at your option)
+// any later version.
+// This library is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this library; if not, write to the Free Software FounIPLion, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110String::npos301  USA
+//------------------------------------------------------------------------------
+#include <cassert>
+#include <ctime>
+#include <iomanip>
+
+#include <boost/shared_array.hpp>
+#include <boost/filesystem/operations.hpp>
+#include <boost/filesystem/fstream.hpp>
+#include <ost/stdint.hh>
+#include <ost/units.hh>
+#include <ost/log.hh>
+#include <ost/img/image.hh>
+#include <ost/img/alg/normalizer_factory.hh>
+#include <ost/img/progress.hh>
+#include <ost/io/io_exception.hh>
+#include <ost/io/convert.hh>
+#include <ost/io/converting_streams.hh>
+#include <ost/img/alg/discrete_shrink.hh>
+
+#include "map_io_ipl_handler.hh"
+
+namespace ost { namespace io {
+
+String IPL::FORMAT_STRING = "defined_ipl";
+
+IPL::IPL(bool normalize_on_save, Format bit_depth):
+    ImageFormatBase(FORMAT_STRING),
+    normalize_on_save_(normalize_on_save),
+    bit_depth_(OST_DEFAULT_FORMAT)
+{
+  this->SetBitDepth(bit_depth);
+}
+
+Format IPL::GetBitDepth() const
+{
+  return bit_depth_;
+}
+
+void IPL::SetBitDepth (Format bitdepth)
+{
+  if( ! (bitdepth==OST_BIT16_FORMAT || bitdepth==OST_BIT32_FORMAT || bitdepth==OST_DEFAULT_FORMAT))
+  {
+    throw IOException("Unsupported bit depth for IPL file format.");
+  }
+
+  bit_depth_ = bitdepth;
+}
+
+bool IPL::GetNormalizeOnSave() const
+{
+  return normalize_on_save_;
+}
+
+void IPL::SetNormalizeOnSave(bool normalize_on_save)
+{
+  normalize_on_save_ = normalize_on_save;
+}
+
+Real IPL::GetMaximum() const
+{
+  switch(bit_depth_){
+  case OST_BIT32_FORMAT:
+    return 4294967295.0;
+  default:
+    return 65535.0;
+  }
+}
+
+Real IPL::GetMinimum() const
+{
+  return 0.0;
+}
+
+bool MapIOIPLHandler::MatchContent(unsigned char* header)
+{
+  String magic_token("DITABIS micron Data File");
+  if(magic_token.compare(0,magic_token.size(),reinterpret_cast<char*>(header),magic_token.size())==0)
+  {
+    return true;
+  }
+  return false;
+}
+
+namespace detail{
+
+class IPLHeader{
+public:
+  IPLHeader():
+    date(),
+    header_length(2048),
+    size_x(),
+    size_y(),
+    bit_depth(),
+    resolution_x(),
+    resolution_y(),
+    magnification(1),
+    thumb_nail_zoom(10),
+    channel("PMT LOWSENS"),
+    params("dummy.set"),
+    format("2 2 2 2  Standard.fmt"),
+    laser(30),
+    gain(20000),
+    offset_correction(true),
+    offset(0),
+    comment()
+  {}
+  IPLHeader(const img::ConstImageHandle& im,Format bit_depth):
+    date(),
+    header_length(2048),
+    size_x(im.GetSize()[0]),
+    size_y(im.GetSize()[1]),
+    bit_depth(bit_depth==OST_BIT32_FORMAT ? 4: 2),
+    resolution_x(im.GetSpatialSampling()[0]),
+    resolution_y(im.GetSpatialSampling()[1]),
+    magnification(1),
+    thumb_nail_zoom(10),
+    channel("PMT LOWSENS"),
+    params("dummy.set"),
+    format("2 2 2 2  Standard.fmt"),
+    laser(30),
+    gain(20000),
+    offset_correction(true),
+    offset(0),
+    comment()
+  {}
+  String date;
+  int header_length;
+  int size_x;
+  int size_y;
+  int bit_depth;
+  Real resolution_x;
+  Real resolution_y;
+  int magnification;
+  int thumb_nail_zoom;
+  String channel;
+  String params;
+  String format;
+  int laser;
+  int gain;
+  bool offset_correction;
+  int offset;
+  String comment;
+};
+
+std::ostream& operator<< (std::ostream& out, const IPLHeader& h )
+{
+  uint start_pos = out.tellp();
+  out << "DITABIS micron Data File\r\n";
+  time_t rawtime=time(NULL);
+  char * timestr = asctime(localtime(&rawtime));
+  timestr[strlen(timestr)-1] = '\0';
+  out << "CREATED = "<< timestr <<" \r\n";
+  out << "HEADER = "<< h.header_length <<" \r\n";
+  // x and y get swapped here (follows the behaviour of the original conversion software)
+  out << "YPIXEL = "<< h.size_x <<" \r\n";
+  out << "XPIXEL = "<< h.size_y <<" \r\n";
+  out << "BYTE PER PIXEL = "<< h.bit_depth <<" \r\n";
+  // x and y get swapped here (follows the behaviour of the original conversion software)
+  out << "XRESOLUTION = "<< std::setprecision(0)<< h.resolution_y/Units::nm <<" (" << std::setprecision(2)<< h.resolution_y/Units::nm<<") \r\n";
+  out << "YRESOLUTION = "<< std::setprecision(0)<< h.resolution_x/Units::nm <<" (" << std::setprecision(2)<< h.resolution_x/Units::nm<<") \r\n";
+  out << "MAGNIFICATION = "<< h.magnification <<" \r\n";
+  out << "THUMB-NAIL-ZOOM = "<< h.thumb_nail_zoom <<" \r\n";
+  out << "CHANNEL = "<< h.channel <<" \r\n";
+  out << "PARAMS = "<< h.params <<" \r\n";
+  out << "FORMAT = "<< h.format <<" \r\n";
+  out << "LASER = "<< h.laser <<" \r\n";
+  out << "GAIN = "<< h.gain <<" \r\n";
+  if(h.offset_correction){
+    out << "OFFSET CORRECTION = YES \r\n";
+  }else{
+    out << "OFFSET CORRECTION = NO \r\n";
+  }
+  out << "OFFSET = "<< h.offset <<" \r\n";
+  out << "COMMENT = Created by OpenStructure \r\n";
+  out << " \r\n";
+  uint fillsize=h.header_length-out.tellp()+start_pos;
+  char empty[fillsize];
+  std::fill_n(empty,fillsize,0);
+  out.write(empty,fillsize);
+  return out;
+}
+
+std::istream& operator>> (std::istream& in, IPLHeader& h)
+{
+  String line;
+  uint start_pos = in.tellg();
+  do{
+    std::getline(in,line);
+    if(line.find("DITABIS micron Data File")!=String::npos){
+      //ignore
+    }else if(line.find("CREATED")!=String::npos){
+      h.date=line.substr(line.find("=")+2);
+    }else if(line.find("HEADER")!=String::npos){
+      std::istringstream ( line.substr(line.find("=")+2) ) >> h.header_length;
+    // x and y get swapped here (follows the behaviour of the original conversion software)
+    }else if(line.find("XPIXEL")!=String::npos){
+      std::istringstream ( line.substr(line.find("=")+2) ) >> h.size_y;
+    }else if(line.find("YPIXEL")!=String::npos){
+      std::istringstream ( line.substr(line.find("=")+2) ) >> h.size_x;
+    }else if(line.find("BYTE PER PIXEL")!=String::npos){
+      std::istringstream ( line.substr(line.find("=")+2) ) >> h.bit_depth;
+      // x and y get swapped here (follows the behaviour of the original conversion software)
+    }else if(line.find("XRESOLUTION")!=String::npos){
+      std::istringstream ( line.substr(line.find("(")+1) ) >> h.resolution_y;
+      h.resolution_y*=Units::nm;
+    }else if(line.find("YRESOLUTION")!=String::npos){
+      std::istringstream ( line.substr(line.find("(")+1) ) >> h.resolution_x;
+      h.resolution_x*=Units::nm;
+    }else if(line.find("MAGNIFICATION")!=String::npos){
+      std::istringstream ( line.substr(line.find("=")+2) ) >> h.magnification;
+    }else if(line.find("THUMB-NAIL-ZOOM")!=String::npos){
+      std::istringstream ( line.substr(line.find("=")+2) ) >> h.thumb_nail_zoom;
+    }else if(line.find("CHANNEL")!=String::npos){
+      h.channel=line.substr(line.find("=")+2);
+    }else if(line.find("PARAMS")!=String::npos){
+      h.params=line.substr(line.find("=")+2);
+    }else if(line.find("FORMAT")!=String::npos){
+      h.format=line.substr(line.find("=")+2);
+    }else if(line.find("LASER")!=String::npos){
+      std::istringstream ( line.substr(line.find("=")+2) ) >> h.laser;
+    }else if(line.find("GAIN")!=String::npos){
+      std::istringstream ( line.substr(line.find("=")+2) ) >> h.gain;
+    }else if(line.find("OFFSET CORRECTION")!=String::npos){
+      if(line.substr(line.find("=")+2).find("YES")!=String::npos){
+        h.offset_correction=true;
+      }else{
+        h.offset_correction=false;
+      }
+    }else if(line.find("OFFSET")!=String::npos){
+      std::istringstream ( line.substr(line.find("=")+2) ) >> h.offset;
+    }else if(line.find("COMMENT")!=String::npos){
+      h.comment=line.substr(line.find("=")+2);
+    }else if(line.find(" ")!=String::npos){
+      //ignore
+    }else{
+      LOG_ERROR("IPL import: unknown header line: " << line);
+    }
+  }while(in.peek()!=0);
+  uint fillsize=h.header_length-in.tellg()+start_pos;
+  char empty[h.header_length];
+  std::fill_n(empty,fillsize,0);
+  in.read(empty,fillsize);
+  return in;
+}
+
+}//ns
+
+bool MapIOIPLHandler::MatchType(const ImageFormatBase& type)
+{
+  if(type.GetFormatString()==IPL::FORMAT_STRING) {
+    return true;
+  }
+  return false;
+}
+
+bool MapIOIPLHandler::MatchSuffix(const String& loc)
+{
+    if(detail::FilenameEndsWith(loc,".IPL") || detail::FilenameEndsWith(loc,".ipl") ) {
+      return true;
+    }
+    return false;
+}
+
+void MapIOIPLHandler::Import(img::MapHandle& sh, const boost::filesystem::path& loc,const ImageFormatBase& formatstruct )
+{
+  boost::filesystem::ifstream infile(loc, std::ios::binary);
+  if(!infile) {
+    throw IOException("could not open "+loc.string());
+  }
+  this->Import(sh,infile,formatstruct);
+  infile.close();
+}
+
+template <typename DATATYPE>
+void real_filler(img::image_state::RealSpatialImageState&  isi, std::istream& file)
+{
+  BinaryIStream<OST_LITTLE_ENDIAN> file_bin(file);
+  img::Size size = isi.GetSize();
+  char this_dummy; //create dummy variable to give to img::Progress as this
+  img::Progress::Instance().Register(&this_dummy,size[1],100);
+  for(unsigned int row=0;row<size[1];row++) {
+    for(unsigned int column=0;column<size[0];column++) {
+      DATATYPE value;
+      file_bin >> value;
+      isi.Value(img::Point(column,row))=static_cast<Real>(value);
+    }
+    img::Progress::Instance().AdvanceProgress(&this_dummy);
+  }
+  img::Progress::Instance().DeRegister(&this_dummy);
+}
+
+template <typename DATATYPE>
+void real_dumper( const img::ConstImageHandle& sh, std::ostream& file, const IPL& formatIPL, int shrinksize)
+{
+  img::image_state::RealSpatialImageState *isi=dynamic_cast<img::image_state::RealSpatialImageState*>(sh.ImageStatePtr().get());
+  if(! isi){
+    throw(IOException("IPL export: dynamic cast failed in real dumper."));
+  }
+  BinaryOStream<OST_LITTLE_ENDIAN> file_bin(file);
+  img::alg::Normalizer norm = img::alg::CreateNoOpNormalizer();
+  if (formatIPL.GetNormalizeOnSave() == true) {
+    norm = img::alg::CreateLinearRangeNormalizer(sh,formatIPL.GetMinimum(),formatIPL.GetMaximum());
+  }
+  img::Size size = isi->GetSize();
+  img::ImageHandle thumbnail=sh.Apply(img::alg::DiscreteShrink(img::Size(shrinksize,shrinksize,1)));
+  img::image_state::RealSpatialImageState *thumb_isi=dynamic_cast<img::image_state::RealSpatialImageState*>(thumbnail.ImageStatePtr().get());
+  if(! thumb_isi){
+    throw(IOException("IPL export: dynamic cast failed in real dumper."));
+  }
+
+  char this_dummy; //create dummy variable to give to img::Progress as this
+  img::Progress::Instance().Register(&this_dummy,size[1]+1,100);
+  for(unsigned int row=0;row<size[1];row++) {
+    for(unsigned int column=0;column<size[0];column++)
+    {
+      file_bin << static_cast<DATATYPE>(norm.Convert(isi->Value(ost::img::Point(column,row,0))));
+    }
+    img::Progress::Instance().AdvanceProgress(&this_dummy);
+  }
+  img::Progress::Instance().AdvanceProgress(&this_dummy);
+  img::Size thumb_size = thumb_isi->GetSize();
+  for(unsigned int row=0;row<thumb_size[1];row++) {
+    for(unsigned int column=0;column<thumb_size[0];column++)
+    {
+      file_bin << static_cast<DATATYPE>(norm.Convert(thumb_isi->Value(ost::img::Point(column,row,0))));
+    }
+    img::Progress::Instance().AdvanceProgress(&this_dummy);
+  }
+  img::Progress::Instance().DeRegister(&this_dummy);
+}
+
+
+void MapIOIPLHandler::Import(img::MapHandle& sh, std::istream& file, const ImageFormatBase& formatstruct)
+{
+
+  IPL form;
+  IPL& formatIPL = form;
+  if (formatstruct.GetFormatString()==IPL::FORMAT_STRING) {
+    formatIPL = formatstruct.As<IPL>();
+  } else {
+    assert (formatstruct.GetFormatString()==UndefinedImageFormat::FORMAT_STRING);
+  }
+
+  detail::IPLHeader header;
+  file >> header;
+
+  sh.Reset(img::Extent(img::Point(0,0),img::Size(header.size_x,header.size_y)), img::REAL, img::SPATIAL);
+  sh.SetSpatialSampling(geom::Vec3(header.resolution_x,header.resolution_y,1.0));
+  img::image_state::RealSpatialImageState * isi;
+  if(! (isi=dynamic_cast<img::image_state::RealSpatialImageState*>(sh.ImageStatePtr().get()))) {
+    throw IOException("internal error in IPL io: expected RealSpatialImageState");
+  }
+
+  if(header.bit_depth==4){
+    real_filler<uint32_t>(*isi,file);
+  }else{
+    real_filler<uint16_t>(*isi,file);
+  }
+}
+
+void MapIOIPLHandler::Export(const img::MapHandle& mh2,
+                                  const boost::filesystem::path& loc,const ImageFormatBase& formatstruct) const
+{
+  boost::filesystem::ofstream outfile(loc, std::ios::binary);
+  if(!outfile)
+  {
+    throw IOException("could not open "+loc.string());
+  }
+  this->Export(mh2,outfile,formatstruct);
+  outfile.close();
+}
+
+void MapIOIPLHandler::Export(const img::MapHandle& sh, std::ostream& file,const ImageFormatBase& formatstruct) const
+{
+
+  IPL form;
+  IPL& formatIPL = form;
+  if (formatstruct.GetFormatString()==IPL::FORMAT_STRING) {
+    formatIPL = formatstruct.As<IPL>();
+  } else {
+    assert (formatstruct.GetFormatString()==UndefinedImageFormat::FORMAT_STRING);
+  }
+  if (sh.GetSize()[2]!=1 || sh.GetDomain()!=img::SPATIAL || sh.GetType()!=img::REAL) {
+    throw IOException("IPL IO: IPL format only supports spatial 2D images.");
+  }
+  detail::IPLHeader header(sh,formatIPL.GetBitDepth());
+  file << header;
+  if(header.bit_depth==4){
+    real_dumper<uint32_t>(sh,file,formatIPL,header.thumb_nail_zoom);
+  }else{
+    real_dumper<uint16_t>(sh,file,formatIPL,header.thumb_nail_zoom);
+  }
+}
+
+}} // namespaces
+
+
diff --git a/modules/io/src/img/map_io_ipl_handler.hh b/modules/io/src/img/map_io_ipl_handler.hh
new file mode 100644
index 000000000..3e76ac989
--- /dev/null
+++ b/modules/io/src/img/map_io_ipl_handler.hh
@@ -0,0 +1,78 @@
+//------------------------------------------------------------------------------
+// This file is part of the OpenStructure project <www.openstructure.org>
+//
+// Copyright (C) 2008-2011 by the OpenStructure authors
+// Copyright (C) 2003-2010 by the IPLT authors
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License as published by the Free
+// Software Foundation; either version 3.0 of the License, or (at your option)
+// any later version.
+// This library is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this library; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+//------------------------------------------------------------------------------
+#ifndef OST_IO_MAP_IO_IPL_HANDLER_HH
+#define OST_IO_MAP_IO_IPL_HANDLER_HH
+
+
+/*
+Andreas Schenk
+*/
+
+#include "map_io_handler.hh"
+
+namespace ost { namespace io {
+
+class DLLEXPORT_OST_IO IPL: public ImageFormatBase
+{
+
+ public:
+
+  IPL(bool normalize_on_save = true, Format bit_depth = OST_DEFAULT_FORMAT);
+
+  Format GetBitDepth() const;
+  void SetBitDepth ( Format bitdepth);
+
+
+  bool GetNormalizeOnSave() const;
+  void SetNormalizeOnSave(bool normalize_on_save=true);
+  Real GetMaximum() const;
+  Real GetMinimum() const;
+  static String FORMAT_STRING;
+
+ private:
+  bool normalize_on_save_;
+  Format bit_depth_;
+
+};
+
+class DLLEXPORT_OST_IO MapIOIPLHandler: public MapIOHandler
+{
+  public:
+    /// \brief Map IO handler to read/write Ipl map files
+    ///
+    /// This map IO handler reads and writes Ipl formatted map files.
+    virtual void Import(img::MapHandle& sh, const boost::filesystem::path& loc,const ImageFormatBase& formatstruct );
+    virtual void Import(img::MapHandle& sh, std::istream& loc, const ImageFormatBase& formatstruct);
+    virtual void Export(const img::MapHandle& sh, const boost::filesystem::path& loc, const ImageFormatBase& formatstruct) const;
+    virtual void Export(const img::MapHandle& sh, std::ostream& loc,const ImageFormatBase& formatstruct) const;
+    static bool MatchContent(unsigned char* header);
+    static bool MatchType(const ImageFormatBase& type);
+    static bool MatchSuffix(const String& loc);
+    static bool ProvidesImport() { return true; }
+    static bool ProvidesExport() { return true; }
+    static String GetFormatName() { return String("IPL"); }
+    static String GetFormatDescription() {return String("Ditabis Micron Image Plate Scanner Format");}
+};
+
+typedef MapIOHandlerFactory<MapIOIPLHandler> MapIOIPLHandlerFactory;
+
+}} // ns
+
+#endif
diff --git a/modules/io/src/io_manager.cc b/modules/io/src/io_manager.cc
index 12e714fd6..071be5c11 100644
--- a/modules/io/src/io_manager.cc
+++ b/modules/io/src/io_manager.cc
@@ -38,6 +38,7 @@
 #  include  <ost/io/img/map_io_jpk_handler.hh>
 #  include  <ost/io/img/map_io_nanoscope_handler.hh>
 #  include  <ost/io/img/map_io_df3_handler.hh>
+#  include  <ost/io/img/map_io_ipl_handler.hh>
 #endif
 namespace ost { namespace io {
 
@@ -63,7 +64,8 @@ IOManager::IOManager()
   RegisterFactory(MapIOHandlerFactoryBasePtr(new MapIOJpkHandlerFactory));
   RegisterFactory(MapIOHandlerFactoryBasePtr(new MapIODatHandlerFactory));
   RegisterFactory(MapIOHandlerFactoryBasePtr(new MapIONanoscopeHandlerFactory));
-  RegisterFactory(MapIOHandlerFactoryBasePtr(new MapIODF3HandlerFactory));  
+  RegisterFactory(MapIOHandlerFactoryBasePtr(new MapIODF3HandlerFactory));
+  RegisterFactory(MapIOHandlerFactoryBasePtr(new MapIOIPLHandlerFactory));
 #endif
 }
 
diff --git a/modules/io/tests/test_io_img.cc b/modules/io/tests/test_io_img.cc
index d8165dd76..9f21257d3 100644
--- a/modules/io/tests/test_io_img.cc
+++ b/modules/io/tests/test_io_img.cc
@@ -34,6 +34,7 @@
 #include  <ost/io/img/map_io_dat_handler.hh>
 #include  <ost/io/img/map_io_jpk_handler.hh>
 #include  <ost/io/img/map_io_nanoscope_handler.hh>
+#include  <ost/io/img/map_io_ipl_handler.hh>
 #include  <ost/img/alg/normalizer_factory.hh>
 
 using namespace ost;
@@ -82,6 +83,7 @@ BOOST_AUTO_TEST_CASE(test_io_img)
   }
   //int 16 formats
   std::map<String,ImageFormatBase*> int_formats;
+  int_formats["IPL (16 bit)"]=new IPL(true,OST_BIT16_FORMAT);
   int_formats["DAT (16 bit)"]=new DAT(true,OST_BIT16_FORMAT);
   int_formats["TIF (16 bit)"]=new TIF;
   int_formats["JPK (16 bit)"]=new JPK;
@@ -108,6 +110,31 @@ BOOST_AUTO_TEST_CASE(test_io_img)
     delete it->second;
   }
 
+  //int 32 formats
+  std::map<String,ImageFormatBase*> int32_formats;
+  int32_formats["IPL (16 bit)"]=new IPL(true,OST_BIT32_FORMAT);
+  for(std::map<String,ImageFormatBase*>::iterator it=int32_formats.begin();it!=int32_formats.end();++it){
+    ost::io::SaveImage(testimage,fname,*(it->second));
+    ost::img::ImageHandle loadedimage=ost::io::LoadImage(fname,*(it->second));
+    ost::img::alg::Normalizer norm=ost::img::alg::CreateLinearRangeNormalizer(testimage,0.0,4294967295.0);
+    ost::img::ImageHandle scaled_image=testimage.Apply(norm);
+    bool failed=false;
+    ost::img::ExtentIterator eit(scaled_image.GetExtent());
+    for(;!eit.AtEnd();++eit) {
+      if( static_cast<int>(scaled_image.GetReal(eit))!=static_cast<int>(loadedimage.GetReal(eit))){
+        failed=true;
+        break;
+      }
+    }
+    if(failed){
+      BOOST_ERROR("Image IO failed for plugin " << it->first << " at point "
+                  << ost::img::Point(eit)<< ". Should be "
+                  << static_cast<int>(scaled_image.GetReal(eit)) << ", but "
+                  << static_cast<int>(loadedimage.GetReal(eit)) << " found.");
+    }
+    delete it->second;
+  }
+
   //byte formats  
   std::map<String,ImageFormatBase*> byte_formats;
   byte_formats["DAT (byte)"]=new DAT(true,OST_BIT8_FORMAT);
-- 
GitLab