gnuplot-iostream.h
Go to the documentation of this file.
00001 /*
00002 Copyright (c) 2009 Daniel Stahlke
00003 
00004 Permission is hereby granted, free of charge, to any person obtaining a copy
00005 of this software and associated documentation files (the "Software"), to deal
00006 in the Software without restriction, including without limitation the rights
00007 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
00008 copies of the Software, and to permit persons to whom the Software is
00009 furnished to do so, subject to the following conditions:
00010 
00011 The above copyright notice and this permission notice shall be included in
00012 all copies or substantial portions of the Software.
00013 
00014 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
00015 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
00016 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
00017 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
00018 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
00019 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
00020 THE SOFTWARE.
00021 */
00022 
00023 #ifndef GNUPLOT_IOSTREAM_H
00024 #define GNUPLOT_IOSTREAM_H
00025 
00026 // C system includes
00027 #include <stdio.h>
00028 #ifdef GNUPLOT_ENABLE_PTY
00029 #include <termios.h>
00030 #include <unistd.h>
00031 #include <pty.h>
00032 #endif // GNUPLOT_ENABLE_PTY
00033 
00034 // C++ system includes
00035 #include <fstream>
00036 #include <iostream>
00037 #include <sstream>
00038 #include <stdexcept>
00039 #include <string>
00040 #include <utility>
00041 #include <iomanip>
00042 #include <vector>
00043 
00044 // library includes: double quotes make cpplint not complain
00045 #include "boost/iostreams/device/file_descriptor.hpp"
00046 #include "boost/iostreams/stream.hpp"
00047 #include "boost/version.hpp"
00048 #include "boost/utility.hpp"
00049 #ifdef GNUPLOT_ENABLE_BLITZ
00050 #include "blitz/array.h"
00051 #endif
00052 
00053 // This is the version of boost which has v3 of the filesystem libraries by default.
00054 #if BOOST_VERSION >= 104600
00055 #define GNUPLOT_USE_TMPFILE
00056 #include "boost/filesystem.hpp"
00057 #endif // BOOST_VERSION
00058 
00059 // Patch for Windows by Damien Loison
00060 #ifdef WIN32
00061 #define PCLOSE _pclose
00062 #define POPEN  _popen
00063 #define FILENO _fileno
00064 #else
00065 #define PCLOSE pclose
00066 #define POPEN  popen
00067 #define FILENO fileno
00068 #endif
00069 
00071 
00072 #ifdef GNUPLOT_USE_TMPFILE
00073 // RAII temporary file.  File is removed when this object goes out of scope.
00074 class GnuplotTmpfile {
00075 public:
00076         GnuplotTmpfile() :
00077                 file(boost::filesystem::unique_path(
00078                         boost::filesystem::temp_directory_path() /
00079                         "tmp-gnuplot-%%%%-%%%%-%%%%-%%%%"))
00080         { }
00081 
00082 private:
00083         // noncopyable
00084         GnuplotTmpfile(const GnuplotTmpfile &);
00085         const GnuplotTmpfile& operator=(const GnuplotTmpfile &);
00086 
00087 public:
00088         ~GnuplotTmpfile() {
00089                 // it is never good to throw exceptions from a destructor
00090                 try {
00091                         remove(file);
00092                 } catch(const std::exception &e) {
00093                         std::cerr << "Failed to remove temporary file " << file << std::endl;
00094                 }
00095         }
00096 
00097 public:
00098         boost::filesystem::path file;
00099 };
00100 #endif // GNUPLOT_USE_TMPFILE
00101 
00103 
00104 // Used for reading stuff sent from gnuplot via gnuplot's "print" function.
00105 class GnuplotFeedback {
00106 public:
00107         GnuplotFeedback() { }
00108         virtual ~GnuplotFeedback() { }
00109         virtual std::string filename() const = 0;
00110         virtual FILE *handle() const = 0;
00111 
00112 private:
00113         // noncopyable
00114         GnuplotFeedback(const GnuplotFeedback &);
00115         const GnuplotFeedback& operator=(const GnuplotFeedback &);
00116 };
00117 
00118 #ifdef GNUPLOT_ENABLE_PTY
00119 #define GNUPLOT_ENABLE_FEEDBACK
00120 class GnuplotFeedbackPty : public GnuplotFeedback {
00121 public:
00122         explicit GnuplotFeedbackPty(bool debug_messages) :
00123                 pty_fn(),
00124                 pty_fh(NULL),
00125                 master_fd(-1),
00126                 slave_fd(-1)
00127         {
00128         // adapted from http://www.gnuplot.info/files/gpReadMouseTest.c
00129                 if(0 > openpty(&master_fd, &slave_fd, NULL, NULL, NULL)) {
00130                         perror("openpty");
00131                         throw std::runtime_error("openpty failed");
00132                 }
00133                 char pty_fn_buf[1024];
00134                 if(ttyname_r(slave_fd, pty_fn_buf, 1024)) {
00135                         perror("ttyname_r");
00136                         throw std::runtime_error("ttyname failed");
00137                 }
00138                 pty_fn = std::string(pty_fn_buf);
00139                 if(debug_messages) {
00140                         std::cerr << "feedback_fn=" << pty_fn << std::endl;
00141                 }
00142 
00143                 // disable echo
00144                 struct termios tios;
00145                 if(tcgetattr(slave_fd, &tios) < 0) {
00146                         perror("tcgetattr");
00147                         throw std::runtime_error("tcgetattr failed");
00148                 }
00149                 tios.c_lflag &= ~(ECHO | ECHONL);
00150                 if(tcsetattr(slave_fd, TCSAFLUSH, &tios) < 0) {
00151                         perror("tcsetattr");
00152                         throw std::runtime_error("tcsetattr failed");
00153                 }
00154 
00155                 pty_fh = fdopen(master_fd, "r");
00156                 if(!pty_fh) {
00157                         throw std::runtime_error("fdopen failed");
00158                 }
00159         }
00160 
00161 private:
00162         // noncopyable
00163         GnuplotFeedbackPty(const GnuplotFeedbackPty &);
00164         const GnuplotFeedbackPty& operator=(const GnuplotFeedbackPty &);
00165 
00166 public:
00167         ~GnuplotFeedbackPty() {
00168                 if(pty_fh) fclose(pty_fh);
00169                 if(master_fd > 0) ::close(master_fd);
00170                 if(slave_fd  > 0) ::close(slave_fd);
00171         }
00172 
00173         std::string filename() const {
00174                 return pty_fn;
00175         }
00176 
00177         FILE *handle() const {
00178                 return pty_fh;
00179         }
00180 
00181 private:
00182         std::string pty_fn;
00183         FILE *pty_fh;
00184         int master_fd, slave_fd;
00185 };
00186 //#elif defined GNUPLOT_USE_TMPFILE
00188 //#define GNUPLOT_ENABLE_FEEDBACK
00189 //class GnuplotFeedbackTmpfile : public GnuplotFeedback {
00190 //public:
00191 //      explicit GnuplotFeedbackTmpfile(bool debug_messages) :
00192 //              tmp_file(),
00193 //              fh(NULL)
00194 //      {
00195 //              if(debug_messages) {
00196 //                      std::cerr << "feedback_fn=" << filename() << std::endl;
00197 //              }
00198 //              fh = fopen(filename().c_str(), "a");
00199 //      }
00200 //
00201 //      ~GnuplotFeedbackTmpfile() {
00202 //              fclose(fh);
00203 //      }
00204 //
00205 //private:
00206 //      // noncopyable
00207 //      GnuplotFeedbackTmpfile(const GnuplotFeedbackTmpfile &);
00208 //      const GnuplotFeedbackTmpfile& operator=(const GnuplotFeedbackTmpfile &);
00209 //
00210 //public:
00211 //      std::string filename() const {
00212 //              return tmp_file.file.string();
00213 //      }
00214 //
00215 //      FILE *handle() const {
00216 //              return fh;
00217 //      }
00218 //
00219 //private:
00220 //      GnuplotTmpfile tmp_file;
00221 //      FILE *fh;
00222 //};
00223 #endif // GNUPLOT_ENABLE_PTY, GNUPLOT_USE_TMPFILE
00224 
00226 
00227 // This is for sending array data to gnuplot directly or via a file.
00228 class GnuplotWriter {
00229 public:
00230         explicit GnuplotWriter(std::ostream *_stream, bool _send_e=true) :
00231                 stream(_stream),
00232                 send_e(_send_e)
00233         { }
00234 
00235 private:
00236         template <class T>
00237         void sendEntry(T v) {
00238                 *stream << v << " ";
00239         }
00240 
00241         template <class T, class U>
00242         void sendEntry(std::pair<T, U> v) {
00243                 sendEntry(v.first, v.second);
00244         }
00245 
00246         template <class T, class U>
00247         void sendEntry(T t, U u) {
00248                 sendEntry(t);
00249                 sendEntry(u);
00250         }
00251 
00252         std::string formatCode(   float *) { return "%float"; }
00253         std::string formatCode(  double *) { return "%double"; }
00254         std::string formatCode(  int8_t *) { return "%int8"; }
00255         std::string formatCode( uint8_t *) { return "%uint8"; }
00256         std::string formatCode( int16_t *) { return "%int16"; }
00257         std::string formatCode(uint16_t *) { return "%uint16"; }
00258         std::string formatCode( int32_t *) { return "%int32"; }
00259         std::string formatCode(uint32_t *) { return "%uint32"; }
00260         std::string formatCode( int64_t *) { return "%int64"; }
00261         std::string formatCode(uint64_t *) { return "%uint64"; }
00262 
00263 public:
00264         // used for one STL container
00265         template <class T>
00266         void sendIter(T p, T last) {
00267                 while(p != last) {
00268                         sendEntry(*p);
00269                         *stream << "\n";
00270                         ++p;
00271                 }
00272                 if(send_e) {
00273                         *stream << "e" << std::endl; // gnuplot's "end of array" token
00274                 }
00275         }
00276 
00277         // used for two STL containers
00278         template <class T, class U>
00279         void sendIterPair(T x, T x_last, U y, U y_last) {
00280                 while(x != x_last && y != y_last) {
00281                         sendEntry(*x, *y);
00282                         *stream << "\n";
00283                         ++x;
00284                         ++y;
00285                 }
00286                 // assert inputs same size
00287                 assert(x==x_last && y==y_last);
00288                 if(send_e) {
00289                         *stream << "e" << std::endl; // gnuplot's "end of array" token
00290                 }
00291         }
00292 
00293         // this handles STL containers as well as blitz::Array<T, 1> and
00294         // blitz::Array<blitz::TinyVector<T, N>, 1>
00295         template <class T>
00296         void send(T arr) {
00297                 sendIter(arr.begin(), arr.end());
00298         }
00299 
00300         template <class T>
00301         void sendBinary(const std::vector<T> &arr) {
00302                 stream->write(reinterpret_cast<const char *>(&arr[0]), arr.size() * sizeof(T));
00303         }
00304 
00305         template <class T>
00306         std::string binfmt(const std::vector<T> &arr) {
00307                 std::ostringstream tmp;
00308                 tmp << " format='" << formatCode((T*)NULL) << "'";
00309                 tmp << " array=(" << arr.size() << ")";
00310                 tmp << " ";
00311                 return tmp.str();
00312         }
00313 
00314         // send vector of vectors containing data points
00315         template <class T>
00316         void send(const std::vector<std::vector <T> > &vectors) {
00317                 // all vectors need to have the same size
00318                 assert(vectors.size() > 0);
00319                 for(size_t i=1; i<vectors.size(); i++) {
00320                         assert(vectors[i].size() == vectors[i-1].size());
00321                 }
00322 
00323                 for(size_t i=0; i<vectors[0].size(); i++) {
00324                         for(size_t j=0; j<vectors.size(); j++) {
00325                                 sendEntry(vectors[j][i]);
00326                         }
00327                         *stream << "\n";
00328                 }
00329                 if(send_e) {
00330                         *stream << "e" << std::endl; // gnuplot's "end of array" token
00331                 }
00332         }
00333 
00334         template <class T>
00335         std::string binfmt(const std::vector<std::vector<T> > &arr) {
00336                 assert(arr.size() > 0);
00337                 std::ostringstream tmp;
00338                 tmp << " format='";
00339                 for(size_t i=0; i<arr.size(); i++) {
00340                         tmp << formatCode((T*)NULL);
00341                 }
00342                 tmp << "' array=(" << arr[0].size() << ")";
00343                 tmp << " ";
00344                 return tmp.str();
00345         }
00346 
00347         // send vector of vectors containing data points
00348         template <class T>
00349         void sendBinary(const std::vector<std::vector <T> > &vectors) {
00350                 // all vectors need to have the same size
00351                 assert(vectors.size() > 0);
00352                 for(size_t i=1; i<vectors.size(); i++) {
00353                         assert(vectors[i].size() == vectors[i-1].size());
00354                 }
00355 
00356                 for(size_t i=0; i<vectors[0].size(); i++) {
00357                         for(size_t j=0; j<vectors.size(); j++) {
00358                                 const T &val = vectors[j][i];
00359                                 stream->write(reinterpret_cast<const char *>(&val), sizeof(T));
00360                         }
00361                 }
00362         }
00363 
00364 #ifdef GNUPLOT_ENABLE_BLITZ
00365         // Note: T could be either a scalar or a blitz::TinyVector.
00366         template <class T>
00367         void send(const blitz::Array<T, 2> &a) {
00368                 for(int i=a.lbound(0); i<=a.ubound(0); i++) {
00369                         for(int j=a.lbound(1); j<=a.ubound(1); j++) {
00370                                 sendEntry(a(i, j));
00371                                 *stream << "\n";
00372                         }
00373                         *stream << "\n"; // blank line between rows
00374                 }
00375                 if(send_e) {
00376                         *stream << "e" << std::endl; // gnuplot's "end of array" token
00377                 }
00378         }
00379 
00380         template <class T, int d>
00381         void sendBinary(const blitz::Array<T, d> &arr) {
00382                 stream->write(reinterpret_cast<const char *>(arr.data()), arr.size() * sizeof(T));
00383         }
00384 
00385         template <class T>
00386         std::string binfmt(const blitz::Array<T, 2> &arr) {
00387                 std::ostringstream tmp;
00388                 tmp << " format='" << formatCode((T*)NULL) << "'";
00389                 tmp << " array=(" << arr.extent(0) << "," << arr.extent(1) << ")";
00390                 if(arr.isMajorRank(0)) tmp << "scan=yx"; // i.e. C-style ordering
00391                 tmp << " ";
00392                 return tmp.str();
00393         }
00394 
00395 private:
00396         template <class T, int N>
00397         void sendEntry(blitz::TinyVector<T, N> v) {
00398                 for(int i=0; i<N; i++) {
00399                         sendEntry(v[i]);
00400                 }
00401         }
00402 
00403         template <class T, int N>
00404         std::string formatCode(blitz::TinyVector<T, N> *) {
00405                 std::ostringstream tmp;
00406                 for(int i=0; i<N; i++) {
00407                         tmp << formatCode((T*)NULL);
00408                 }
00409                 return tmp.str();
00410         }
00411 #endif // GNUPLOT_ENABLE_BLITZ
00412 
00413 private:
00414         std::ostream *stream;
00415         bool send_e;
00416 };
00417 
00419 
00420 class Gnuplot : public boost::iostreams::stream<
00421         boost::iostreams::file_descriptor_sink>
00422 {
00423 public:
00424         explicit Gnuplot(const std::string &cmd = "gnuplot") :
00425                 boost::iostreams::stream<boost::iostreams::file_descriptor_sink>(
00426                         FILENO(pout = POPEN(cmd.c_str(), "w")),
00427                         boost::iostreams::never_close_handle
00428                 ),
00429                 pout(pout), // keeps '-Weff++' quiet
00430                 is_pipe(true),
00431                 feedback(NULL),
00432                 writer(this),
00433                 tmp_files(),
00434                 debug_messages(false)
00435         {
00436                 *this << std::scientific << std::setprecision(18);  // refer <iomanip>
00437         }
00438 
00439         explicit Gnuplot(FILE *fh) :
00440                 boost::iostreams::stream<boost::iostreams::file_descriptor_sink>(
00441                         FILENO(pout = fh),
00442                         boost::iostreams::never_close_handle
00443                 ),
00444                 pout(pout), // keeps '-Weff++' quiet
00445                 is_pipe(false),
00446                 feedback(NULL),
00447                 writer(this),
00448                 tmp_files(),
00449                 debug_messages(false)
00450         {
00451                 *this << std::scientific << std::setprecision(18);  // refer <iomanip>
00452         }
00453 
00454 private:
00455         // noncopyable
00456         Gnuplot(const Gnuplot &);
00457         const Gnuplot& operator=(const Gnuplot &);
00458 
00459 public:
00460         ~Gnuplot() {
00461                 if(debug_messages) {
00462                         std::cerr << "ending gnuplot session" << std::endl;
00463                 }
00464 
00465                 // FIXME - boost's close method calls close() on the file descriptor, but
00466                 // we need to use pclose instead.  For now, just skip calling boost's close
00467                 // and use flush just in case.
00468                 *this << std::flush;
00469                 fflush(pout);
00470                 // Wish boost had a pclose method...
00471                 //close();
00472 
00473                 if(is_pipe) {
00474                         if(PCLOSE(pout)) {
00475                                 std::cerr << "pclose returned error" << std::endl;
00476                         }
00477                 } else {
00478                         if(fclose(pout)) {
00479                                 std::cerr << "fclose returned error" << std::endl;
00480                         }
00481                 }
00482 
00483                 if(feedback) delete(feedback);
00484         }
00485 
00486 public:
00487         // These next few methods just pass through to the underlying GnuplotWriter object, which
00488         // in turn writes to the iostream.
00489 
00490         template <class T1>
00491         Gnuplot &send(T1 arg1) {
00492                 writer.send(arg1);
00493                 return *this;
00494         }
00495 
00496         // Handle fixed length C style arrays.
00497         // Ideally, this specialization would be done in GnuplotWriter and called from the previous
00498         // generic send(T1) function.  However, that way doesn't seem to compile.
00499         template <typename T, std::size_t N>
00500         Gnuplot &send(T (&arr)[N]) {
00501                 writer.sendIter(arr, arr+N);
00502                 return *this;
00503         }
00504 
00505         template <class T1, class T2>
00506         Gnuplot &send(T1 arg1, T2 arg2) {
00507                 writer.sendIter(arg1, arg2);
00508                 return *this;
00509         }
00510 
00511         template <class T1, class T2, class T3, class T4>
00512         Gnuplot &send(T1 arg1, T2 arg2, T3 arg3, T4 arg4) {
00513                 writer.sendIterPair(arg1, arg2, arg3, arg4);
00514                 return *this;
00515         }
00516 
00517         template <class T1>
00518         Gnuplot &sendBinary(T1 arg1) {
00519                 writer.sendBinary(arg1);
00520                 return *this;
00521         }
00522 
00523         template <class T>
00524         std::string binfmt(T arg) {
00525                 return writer.binfmt(arg);
00526         }
00527 
00528 private:
00529         std::string make_tmpfile() {
00530 #ifdef GNUPLOT_USE_TMPFILE
00531                 boost::shared_ptr<GnuplotTmpfile> tmp_file(new GnuplotTmpfile());
00532                 // The file will be removed once the pointer is removed from the
00533                 // tmp_files container.
00534                 tmp_files.push_back(tmp_file);
00535                 return tmp_file->file.string();
00536 #else
00537                 throw(std::logic_error("no filename given and temporary files not enabled"));
00538 #endif // GNUPLOT_USE_TMPFILE
00539         }
00540 
00541 public:
00542         // NOTE: empty filename makes temporary file
00543         template <class T1>
00544         std::string file(T1 arg1, std::string filename="") {
00545                 if(filename.empty()) filename = make_tmpfile();
00546                 std::fstream tmp_stream(filename.c_str(), std::fstream::out);
00547                 GnuplotWriter tmp_writer(&tmp_stream, false);
00548                 tmp_writer.send(arg1);
00549                 tmp_stream.close();
00550 
00551                 std::ostringstream cmdline;
00552                 // FIXME - hopefully filename doesn't contain quotes or such...
00553                 cmdline << " '" << filename << "' ";
00554                 return cmdline.str();
00555         }
00556 
00557         // NOTE: empty filename makes temporary file
00558         template <class T1>
00559         std::string binaryFile(T1 arg1, std::string filename="") {
00560                 if(filename.empty()) filename = make_tmpfile();
00561                 std::fstream tmp_stream(filename.c_str(), std::fstream::out | std::fstream::binary);
00562                 GnuplotWriter tmp_writer(&tmp_stream);
00563                 tmp_writer.sendBinary(arg1);
00564                 tmp_stream.close();
00565 
00566                 std::ostringstream cmdline;
00567                 // FIXME - hopefully filename doesn't contain quotes or such...
00568                 cmdline << " '" << filename << "' binary" << binfmt(arg1);
00569                 return cmdline.str();
00570         }
00571 
00572         void clearTmpfiles() {
00573                 // destructors will cause deletion
00574                 tmp_files.clear();
00575         }
00576 
00577 #ifdef GNUPLOT_ENABLE_FEEDBACK
00578         // Input variables are set to the mouse position and button.  If the gnuplot
00579         // window is closed, button -1 is returned.  The msg parameter is the prompt
00580         // that is printed to the console.
00581         void getMouse(
00582                 double &mx, double &my, int &mb,
00583                 std::string msg="Click Mouse!"
00584         ) {
00585                 allocFeedback();
00586 
00587                 *this << "set mouse" << std::endl;
00588                 *this << "pause mouse \"" << msg << "\\n\"" << std::endl;
00589                 *this << "if (exists(\"MOUSE_X\")) print MOUSE_X, MOUSE_Y, MOUSE_BUTTON; else print 0, 0, -1;" << std::endl;
00590                 if(debug_messages) {
00591                         std::cerr << "begin scanf" << std::endl;
00592                 }
00593                 if(3 != fscanf(feedback->handle(), "%50lf %50lf %50d", &mx, &my, &mb)) {
00594                         throw std::runtime_error("could not parse reply");
00595                 }
00596                 if(debug_messages) {
00597                         std::cerr << "end scanf" << std::endl;
00598                 }
00599         }
00600 
00601         void allocFeedback() {
00602                 if(!feedback) {
00603 #ifdef GNUPLOT_ENABLE_PTY
00604                         feedback = new GnuplotFeedbackPty(debug_messages);
00605 //#elif defined GNUPLOT_USE_TMPFILE
00607 //                      feedback = new GnuplotFeedbackTmpfile(debug_messages);
00608 #endif
00609                         *this << "set print \"" << feedback->filename() << "\"" << std::endl;
00610                 }
00611         }
00612 #endif // GNUPLOT_ENABLE_FEEDBACK
00613 
00614 private:
00615         FILE *pout;
00616         bool is_pipe;
00617         GnuplotFeedback *feedback;
00618         GnuplotWriter writer;
00619 #ifdef GNUPLOT_USE_TMPFILE
00620         std::vector<boost::shared_ptr<GnuplotTmpfile> > tmp_files;
00621 #else
00622         // just a placeholder
00623         std::vector<int> tmp_files;
00624 #endif // GNUPLOT_USE_TMPFILE
00625 
00626 public:
00627         bool debug_messages;
00628 };
00629 
00630 #endif // GNUPLOT_IOSTREAM_H


sr_self_test
Author(s): Ugo Cupcic
autogenerated on Fri Aug 28 2015 13:09:38