//
// This file is part of MorphoGraphX - http://www.MorphoGraphX.org
// Copyright (C) 2012-2016 Richard S. Smith and collaborators.
//
// If you use MorphoGraphX in your work, please cite:
//   http://dx.doi.org/10.7554/eLife.05864
//
// MorphoGraphX is free software, and is licensed under under the terms of the
// GNU General (GPL) Public License version 2.0, http://www.gnu.org/licenses.
//
#include <SystemProcessLoad.hpp>

#include <Dir.hpp>
#include <Image.hpp>
#include <MGXViewer/qglviewer.h>
#include <PlyFile.hpp>
#include <GraphUtils.hpp>
#include "Thrust.hpp"

// #include "H5Cpp.h"
#include <string>

#include <QCheckBox>
#include <QDialog>
#include <QDoubleSpinBox>
#include <QFile>
#include <QFileDialog>
#include <QFileInfo>
#include <QHash>
#include <QListWidget>
#include <QListWidgetItem>
#include <QMessageBox>
#include <QRegExp>

#include <CImg.h>

#include "ui_ImportMesh.h"
#include "ui_LoadMesh.h"
#include "ui_LoadStack.h"

#ifdef WIN32
#  define FileDialogOptions QFileDialog::DontUseNativeDialog
#else
#  define FileDialogOptions 0
#endif

using namespace qglviewer;
using namespace cimg_library;
// using namespace H5;

#include <QTextStream>
#include <stdio.h>

static QTextStream err(stderr);

namespace mgx
{
  typedef CImg<ushort> CImgUS;

  QList<int> extractVersion(QIODevice &file)
  {
    QByteArray version;
    version.reserve(10);
    do {
      char c;
      qint64 qty = file.read(&c, 1);
      if(qty == 0) {
        throw QString("extractVersion:Could not read enough data from file "
          "(read = %1, expected = %2)").arg(version.size()).arg(version.size() + 1);
      }
      version.push_back(c);
    } while(version[version.size() - 1] != ' ');
    version.chop(1);
    QList<QByteArray> values = version.split('.');
    QList<int> result;
    foreach(const QByteArray &a, values) {
      bool ok;
      result << a.toInt(&ok);
      if(!ok)
        throw QString("extractVersion:Version number must contain only "
          "integers (read = '%1')").arg(QString(a));
    }
    return result;
  }

  struct SwapBytes {
    ushort operator()(const ushort& us)
    {
      // Uncomment the following lines to test speed
      // ushort t = us;
      // for(int i = 0 ; i < 19 ; ++i)
      // t = ((t & 0x00ff) << 8) + ((t & 0xff00) >> 8);
      // return t;

      return ((us & 0x00ff) << 8) + ((us & 0xff00) >> 8);
    }
  };

  bool StackSwapBytes::run(const Store* input, Store* output)
  {
    thrust::transform(THRUST_RETAG(input->data().begin()), THRUST_RETAG(input->data().end()),
                      THRUST_RETAG(output->data().begin()), SwapBytes());
    output->copyMetaData(input);
    output->changed();
    return true;
  }

  struct StackHdr {
    uint hdrsz;
    uint xsz, ysz, zsz;
    float xum, yum, zum;
    uint datasz;

    StackHdr()
      : hdrsz(1024)
    {
    }
  };

  bool StackImport::initialize(QWidget* parent)
  {

    int stackId = parm("Stack").toInt();
    QString storeName = parm("Store");
    Point3f step(parm("X Step").toFloat(), parm("Y Step").toFloat(), parm("Z Step").toFloat());
    float brightness(parm("Brightness").toFloat());
    QString filename(parm("Profile File"));

    // Get stack and store
    Stack* stk = currentStack();
    if(stackId != -1)
      stk = stack(parm("Stack").toInt());
    if(!checkState().stack(STACK_ANY, stackId))
      return false;

    Store* store = stk->currentStore();
    if(storeName == "Main")
      store = stk->main();
    else if(storeName == "Work")
      store = stk->work();

    QString currentFile(store->file());

    if(currentFile.endsWith(".txt", Qt::CaseInsensitive)) {
      // If current stack file is a .txt file, load it as a profile
      LoadProfile(currentFile);
      loadedFile = currentFile;
    } else if(filename.endsWith(".txt", Qt::CaseInsensitive)) {
      // If file list contains one
      LoadProfile(filename);
      loadedFile = filename;
    }

    if(!parent) return true; // if called from command line

    // Set up dialog
    QDialog dlg(parent);
    this->dlg = &dlg;
    Ui_LoadStackDialog ui;
    this->ui = &ui;
    ui.setupUi(&dlg);
    bool res = false;

    QObject::connect(ui.AddFiles, SIGNAL(clicked()), this, SLOT(AddFilesSlot()));
    QObject::connect(ui.RemoveFiles, SIGNAL(clicked()), this, SLOT(RemoveFilesSlot()));
    QObject::connect(ui.LoadProfile, SIGNAL(clicked()), this, SLOT(LoadProfile()));
    QObject::connect(ui.SaveProfile, SIGNAL(clicked()), this, SLOT(SaveProfile()));
    QObject::connect(ui.FilterFiles, SIGNAL(clicked()), this, SLOT(FilterFilesSlot()));
    QObject::connect(ui.SortAscending, SIGNAL(clicked()), this, SLOT(SortAscendingSlot()));
    QObject::connect(ui.SortDescending, SIGNAL(clicked()), this, SLOT(SortDescendingSlot()));
    QObject::connect(ui.ImageFiles, SIGNAL(filesDropped(const QStringList &)), this,
                     SLOT(AddFilesSlot(const QStringList &)));

    setImageSize(512, 512, 0);
    ui.StepX->setValue(step.x());
    ui.StepY->setValue(step.y());
    ui.StepZ->setValue(step.z());
    ui.Brightness->setValue(brightness);
    ui.ImageFiles->clear();

    if(dlg.exec() == QDialog::Accepted) {
      // Change parms
      setParm("X Step", QString::number(ui.StepX->value()));
      setParm("Y Step", QString::number(ui.StepY->value()));
      setParm("Z Step", QString::number(ui.StepZ->value()));
      setParm("Brightness", QString::number(ui.Brightness->value()));
      setParm("Profile File", loadedFile);

      // Save image files
      for(int i = 0; i < ui.ImageFiles->count(); ++i)
        imageFiles << ui.ImageFiles->item(i)->text();

      res = true;
    }

    this->dlg = 0;
    this->ui = 0;
    return res;
  }

  bool StackImport::LoadProfile(QString filename, Point3u& size, Point3f& step,
                                                  float& brightness, QStringList& files)
  {
    if(QFile::exists(filename)) {
      Parms parms(filename);

      bool ok = true;
      ok &= parms("Stack", "SizeX", size.x());
      ok &= parms("Stack", "SizeY", size.y());
      // New version: StepX and StepY and separate
      if(parms("Stack", "StepX", step.x())) {
        parms("Stack", "StepY", step.y());
        parms("Stack", "StepZ", step.z());
      } else {
        // This is for backward compatibility
        if(!parms("Stack", "StepXY", step.x()) or !parms("Stack", "StepZ", step.z())) {
          float zScale;
          if(parms("Stack", "ZScale", zScale)) {
            step.z() = .5;
            step.x() = step.z() / zScale;
          } else
            ok = false;
        }
        step.y() = step.x();
      }
      ok &= parms("Stack", "Brightness", brightness);
      if(!ok) {
        SETSTATUS("loadStack::Error:Cannot read parameter file:" << filename);
        return false;
      }
      parms.all("Stack", "ImageFile", files);
      size.z() = files.size();
      txtFile = filename;
      return true;
    }
    return false;
  }

  void StackImport::LoadProfile(QString filename)
  {
    if(filename.isEmpty()) {
      filename = QFileDialog::getOpenFileName(dlg, QString("Select profile file to open"), filename,
                                              QString("Text files (*.txt)"), 0, FileDialogOptions);
      if(filename.isEmpty())
        return;
    }

    QStringList ImageFiles;
    Point3u size;
    Point3f step;
    float brightness;
    if(LoadProfile(filename, size, step, brightness, imageFiles)) {
      setImageSize(size.x(), size.y(), size.z());
      ui->StepX->setValue(step.x());
      ui->StepY->setValue(step.y());
      ui->StepZ->setValue(step.z());
      ui->Brightness->setValue(brightness);
      QListWidget* lst = ui->ImageFiles;
      lst->clear();
      forall(const QString& s, imageFiles)
        lst->addItem(s);

      loadedFile = filename;
    }
  }

  void StackImport::SaveProfile()
  {
    // Write to file
    QString filename = QFileDialog::getSaveFileName(dlg, QString("Select file to save profile for "), loadedFile,
                                                    QString("Text files (*.txt)"), 0, FileDialogOptions);
    if(filename.isEmpty())
      return;

    // Check ending, add suffix if required
    if(filename.right(4) != ".txt")
      filename.append(".txt");

    QFile file(filename);
    if(!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
      SETSTATUS("loadStackSaveProfile::Error:Cannot open output file:" << filename);
      return;
    }
    QString StackFile = stripCurrentDir(filename);
    actingFile(filename);

    QTextStream out(&file);
    out << "[Stack]" << endl;
    out << "SizeX: " << imageSize.x() << endl;
    out << "SizeY: " << imageSize.y() << endl;
    out << "StepX: " << ui->StepX->value() << endl;
    out << "StepY: " << ui->StepY->value() << endl;
    out << "StepZ: " << ui->StepZ->value() << endl;
    out << "Brightness: " << ui->Brightness->value() << endl;
    QListWidget* lst = ui->ImageFiles;
    for(int i = 0; i < lst->count(); i++) {
      QListWidgetItem* item = lst->item(i);
      out << "ImageFile: " << stripCurrentDir(item->text()) << endl;
    }
    file.close();
    loadedFile = filename;
    SETSTATUS("Saving profile, file:" << filename);
    txtFile = filename;
  }

  void StackImport::AddFilesSlot()
  {
    // RSS How to get a complete list from CImg? (there are over 100)
    QStringList files = QFileDialog::getOpenFileNames(dlg, QString("Select stack image files "), currentPath(),
                                                      QString("Any files (*.*)"), 0);
    if(files.empty())
      return;

    AddFilesSlot(files);
  }

  void StackImport::AddFilesSlot(const QStringList& files)
  {
    QListWidget* lst = ui->ImageFiles;
    if(lst->count() == 0) {
      QString first = files[0];
      Image3D image;
      image.step = imageResolution();
      if(!loadImage(first, image)) {
        QMessageBox::critical(dlg, "Error reading image", QString("Error, cannot read image from '%1'").arg(first));
        return;
      }
      SETSTATUS(QString("Loaded first image: size = %1x%2").arg(image.size.x()).arg(image.size.y()));
      // CImgUS image(qPrintable(first));
      setImageSize(image.size.x(), image.size.y(), 0);
      setImageResolution(image.step);
    }
    lst->addItems(files);
    setImageSize(imageSize.x(), imageSize.y(), lst->count());
  }

  Point3f StackImport::imageResolution() {
    return Point3f(ui->StepX->value(), ui->StepY->value(), ui->StepZ->value());
  }

  void StackImport::setImageResolution(Point3f step)
  {
    ui->StepX->setValue(step.x());
    ui->StepY->setValue(step.y());
    ui->StepZ->setValue(step.z());
  }

  void StackImport::setImageSize(Point3u size)
  {
    imageSize = size;
    ui->SizeXY->setText(QString("(%1,%2,%3)").arg(imageSize.x()).arg(imageSize.y()).arg(imageSize.z()));
  }

  void StackImport::RemoveFilesSlot()
  {
    QListWidget* lst = ui->ImageFiles;
    QList<QListWidgetItem*> items = lst->selectedItems();
    for(QList<QListWidgetItem*>::iterator i = items.begin(); i != items.end(); i++) {
      QListWidgetItem* item = lst->takeItem(lst->row(*i));
      delete item;
    }
    setImageSize(imageSize.x(), imageSize.y(), lst->count());
  }

  void StackImport::FilterFilesSlot()
  {
    QListWidget* lst = ui->ImageFiles;
    QString text = ui->FilterText->text();

    QList<QListWidgetItem*> items = lst->findItems(".*", Qt::MatchRegExp);
    for(QList<QListWidgetItem*>::iterator i = items.begin(); i != items.end(); i++) {
      QString itemtext = (*i)->text();
      if(itemtext.indexOf(text) < 0) {
        QListWidgetItem* item = lst->takeItem(lst->row(*i));
        delete item;
      }
    }
  }

  void StackImport::SortAscendingSlot(bool val)
  {
    QListWidget* lst = ui->ImageFiles;
    lst->sortItems((val ? Qt::AscendingOrder : Qt::DescendingOrder));
  }

  void StackImport::SortDescendingSlot() {
    SortAscendingSlot(false);
  }

  bool StackImport::run(Stack* stack, Store* store, Point3f step, float brightness, QString filename)
  {
    Point3u size;
    if(filename.size() > 0) {
      LoadProfile(filename, size, step, brightness, imageFiles);
      actingFile(filename);
    }

    HVecUS& data = store->data();
    {
      CImgUS first_image(qPrintable(imageFiles.at(0)));
      size = Point3u(first_image.width(), first_image.height(), imageFiles.size());
    }
    stack->setSize(size);
    stack->setStep(step);
    size_t SizeXYZ = size_t(size.x()) * size.y() * size.z();
    store->setFile(filename);
    uint filecount = 0;
    SETSTATUS("Loading " << ((store == stack->main()) ? "main" : "work") << " stack " << stack->userId());
    progressStart(
      QString("Loading %1 Stack %2").arg((store == stack->main()) ? "main" : "work").arg(stack->userId()), size.z());
    memset(&data[0], 0, SizeXYZ * sizeof(ushort));

    Image3D image(data, size, step);
    image.brightness = brightness;
    image.minc = 65535;
    image.maxc = 0;
    for(uint z = 0; z < size.z(); z++) {
      image.plane = filecount++;
      if(!loadImage(imageFiles.at(z), image)) {
        setErrorMessage(QString("Cannot read image from file '%1'").arg(imageFiles.at(z)));
        return false;
      }
      SETSTATUS("Read File:" << imageFiles.at(z));
      if(!progressAdvance(z + 1))
        userCancel();
    }
    if(image.maxc > 0xFFFF)
      SETSTATUS("WARNING: max intensity:" << image.maxc << ", clipped at:" << 0xFFFF);
    else
      SETSTATUS(filecount << " of " << size.z() << " files,read intensity range:" << image.minc << "-" << image.maxc);
    stack->center();
    store->changed();
    return true;
  }

  bool StackOpen::initialize(QWidget* parent)
  {
    bool is_work_store = stringToWorkStore(parm("Store"));
    int stackId = parm("Stack Number").toInt();
    if(!checkState().store((is_work_store ? STORE_WORK : STORE_MAIN), stackId))
      return false;
    QString allFilters("All Stack files (*.mgxs *.inr *.tif *.tiff");// *.hdf5 *.h5 *.hdf)");
    QString mgxsFilter("MorphoGraphX Stack files (*.mgxs)"), inrFilter("Inria Stack files (*.inr)"),
    tifFilter("TIFF Image Directory (*.tif *.tiff)")/*, h5Filter("HDF5 (*.h5 *.hdf5 *.hdf)")*/, filter; // h5
    QString filename = parm("Filename");

    if(filename.isEmpty()) {
      QString currentFile;
      if(is_work_store)
        currentFile = stack(stackId)->work()->file();
      else
        currentFile = stack(stackId)->main()->file();
      if(!currentFile.isEmpty())
        filename = currentFile;
    }

    // If we loaded a list of images before, strip of the ending and default type
    if(filename.right(4) == ".txt") {
      filename = filename.left(filename.length() - 4);
      filename += ".mgxs";
    }

    if(filename.right(4) == ".inr")
      filter = inrFilter;
    else if(filename.endsWith(".tif", Qt::CaseInsensitive) or filename.endsWith(".tiff", Qt::CaseInsensitive))
      filter = tifFilter;
    else if(filename.endsWith(".mgx", Qt::CaseInsensitive))
      filter = mgxsFilter;
    else
      filter = allFilters;

    // Get the file name
    if(parent)
      filename = QFileDialog::getOpenFileName(
        parent, QString("Select stack file"), filename,
        QString(allFilters + ";;" + mgxsFilter + ";;" + inrFilter + ";;" + tifFilter/* + ";;" + h5Filter*/), &filter, FileDialogOptions);
    if(filename.isEmpty())
      return false;

    setParm("Filename",filename);
    return true;
  }

  // /**
  //   * Operator function for H5Ovisit. This function prints the
  //   * name and type of the object passed to it.
  //   */
  // herr_t h5_op_func(hid_t loc_id, const char *name, const H5O_info_t *info,
  //         void *operator_data) {
  //     printf("/");               /* Print root group in object path */
  //     /*
  //     * Check if the current object is the root group, and if not print
  //     * the full path name and type.
  //     */
  //     if (name[0] == '.') {
  //         /* Root group, do not print '.' */
  //         printf("  (Group)\n");
  //     } else {
  //         switch (info->type) {
  //             case H5O_TYPE_GROUP:
  //                 printf("%s  (Group)\n", name);
  //                 break;
  //             case H5O_TYPE_DATASET:
  //                 printf("%s  (Dataset)\n", name);
  //                 break;
  //             case H5O_TYPE_NAMED_DATATYPE:
  //                 printf("%s  (Datatype)\n", name);
  //                 break;
  //             default:
  //                 printf("%s  (Unknown)\n", name);
  //         }
  //     }
  //     return 0;
  // }


  // *
  //   * Operator function for H5Lvisit.  This function simply
  //   * retrieves the info for the object the current link points
  //   * to, and calls the operator function for H5Ovisit.
    
  // herr_t h5_op_func_L(hid_t loc_id, const char *name, const H5L_info_t *info,
  //         void *operator_data) {
  //     herr_t status;
  //     H5O_info_t infobuf;
  //     /*
  //     * Get type of the object and display its name and type.
  //     * The name of the object is passed to this function by
  //     * the Library.
  //     */
  //     status = H5Oget_info_by_name(loc_id, name, &infobuf, H5P_DEFAULT);
  //     return h5_op_func(loc_id, name, &infobuf, operator_data);
  // }

  // /**
  //   * Traverses the H5 file and prints the name and type of every encountered object.
  //   *
  //   * \param fileNameH5 path to the H5 file
  //   */
  // void traverse_h5_file(const char *fileNameH5) {
  //     herr_t status;
  //     hid_t h5_file_id;
  //     h5_file_id = H5Fopen(fileNameH5, H5F_ACC_RDWR, H5P_DEFAULT);
  //     status = H5Lvisit(h5_file_id, H5_INDEX_NAME, H5_ITER_NATIVE, h5_op_func_L, NULL);
  // }


  bool StackOpen::run()
  {
    int stackId = parm("Stack Number").toInt();
    if(!checkState().stack(STACK_ANY, stackId))
      return false;
    Stack* stack = this->stack(stackId);
    Store* store = (stringToWorkStore(parm("Store")) ? stack->work() : stack->main());
    bool res = run(stack, store, parm("Filename"));
    if(res) {
      store->show();
    }
    return res;
  }

  bool StackOpen::loadMGXS_0(QIODevice& file, Stack* stack, Store* store)
  {
    HVecUS& data = store->data();
    QByteArray stkdata = file.readAll();
    file.close();
    stkdata = qUncompress(stkdata);

    struct StackHdr stkhdr;
    memcpy(&stkhdr, stkdata.constData(), sizeof(stkhdr));
    Point3u size(stkhdr.xsz, stkhdr.ysz, stkhdr.zsz);
    Point3f step(stkhdr.xum, stkhdr.yum, stkhdr.zum);
    stack->setSize(size);
    stack->setStep(step);
    if(stkhdr.datasz / 2 != stack->storeSize())
      throw QString("Datasize does not match dimensions in .mgxs file");
    memcpy(data.data(), stkdata.constData() + stkhdr.hdrsz, stkhdr.datasz);
    return true;
  }

  bool StackOpen::loadMGXS_1_3(QIODevice& file, Stack* stack, Store* store)
  {
    bool isLabel;
    float sxum, syum, szum;
    file.read((char*)&isLabel, sizeof(bool));
    file.read((char*)&sxum, sizeof(float));
    file.read((char*)&syum, sizeof(float));
    file.read((char*)&szum, sizeof(float));
    bool result = loadMGXS_1_0(file, stack, store);
    if(result) {
      store->setLabels(isLabel);
      stack->setOrigin(Point3f(sxum, syum, szum));
      SETSTATUS("Set stack step to " << stack->origin());
    }
    return result;
  }

  bool StackOpen::loadMGXS_1_2(QIODevice& file, Stack* stack, Store* store)
  {
    bool result = loadMGXS_1_3(file, stack, store);
    if(result and store->labels()) {
      StackSwapBytes swap(*this);
      return swap.run(store, store);
    }
    return result;
  }

  bool StackOpen::loadMGXS_1_1(QIODevice& file, Stack* stack, Store* store)
  {
    bool isLabel;
    file.read((char*)&isLabel, sizeof(bool));
    bool result = loadMGXS_1_0(file, stack, store);
    if(result) {
      store->setLabels(isLabel);
      if(store->labels()) {
        StackSwapBytes swap(*this);
        return swap.run(store, store);
      }
    }
    return result;
  }

  bool StackOpen::loadMGXS_1_0(QIODevice& file, Stack* stack, Store* store)
  {
    HVecUS& data = store->data();
    quint32 xsz;
    quint32 ysz;
    quint32 zsz;
    float xum;
    float yum;
    float zum;
    quint64 datasz;
    quint8 cl;
    file.read((char*)&xsz, sizeof(quint32));
    file.read((char*)&ysz, sizeof(quint32));
    file.read((char*)&zsz, sizeof(quint32));
    Information::out << "Size: " << xsz << " " << ysz << " " << zsz << endl;
    file.read((char*)&xum, sizeof(float));
    file.read((char*)&yum, sizeof(float));
    file.read((char*)&zum, sizeof(float));
    file.read((char*)&datasz, sizeof(quint64));
    file.read((char*)&cl, sizeof(quint8));
    quint64 expected_size = 2ul * (quint64(xsz) * quint64(ysz) * quint64(zsz));
    if(datasz != expected_size)
      throw QString("Datasize does not match dimensions in MGXS file (size = %1, expected from dimension = %2)")
            .arg(datasz)
            .arg(expected_size);
    Point3u size(xsz, ysz, zsz);
    Point3f step(xum, yum, zum);
    stack->setSize(size);
    stack->setStep(step);
    if(cl > 0) {
      progressStart(QString("Loading Stack %1").arg(stack->userId()), expected_size);
      quint64 cur_size = 0;
      char* d = (char*)data.data();
      while(cur_size < 2 * data.size()) {
        if(!progressAdvance(cur_size))
          userCancel();
        // Information::out << "advance to " << cur_size << endl;
        quint32 sz;
        qint64 qty = file.read((char*)&sz, sizeof(quint32));
        if(qty != sizeof(quint32)) {
          throw QString("Could not read enough data from MGXS file (read = %1, expected = %2)").arg(qty).arg(
                  sizeof(quint32));
        }
        QByteArray stkdata = file.read(sz);
        if(quint32(stkdata.size()) != sz) {
          throw QString("Could not read enough data from MGXS file (read = %1, expected = %2)")
                .arg(stkdata.size()).arg(sz);
        }
        stkdata = qUncompress(stkdata);
        if(stkdata.isEmpty()) {
          throw QString("Compressed data in MGXS file is corrupted");
        }
        memcpy(d, stkdata.data(), stkdata.size());
        d += stkdata.size();
        cur_size += stkdata.size();
      }
      if(!progressAdvance(cur_size))
        userCancel();
    } else {
      // Ok, if greater than 64MB, read in bits
      quint64 nb_slices = expected_size >> 26;
      quint64 slice_size = 1ul << 26;
      progressStart(QString("Loading Stack %1").arg(stack->userId()), nb_slices);
      Information::out << "Nb of slices = " << nb_slices << endl;
      for(quint64 i = 0; i < nb_slices; ++i) {
        qint64 qty = file.read((char*)data.data() + i * slice_size, slice_size);
        if(qty != (int)slice_size)
          throw QString("Could not read enough data from MGXS file (read = %1, expected = %2)")
                  .arg(qty).arg(slice_size);
        if(!progressAdvance(i))
          userCancel();
        // Information::out << "advance to " << i << endl;
      }
      quint64 left_size = expected_size - (nb_slices << 26);
      qint64 qty = file.read((char*)data.data() + (nb_slices << 26), left_size);
      if(qty != (int)left_size) {
        throw QString("Could not read enough data from MGXS file (read = %1, expected = %2)")
                .arg(qty).arg(left_size);
      }
      if(!progressAdvance(nb_slices))
        userCancel();
    }
    return true;
  }

    static const char *const ELEMENT_SIZE_UM = "element_size_um";

    bool StackOpen::run(Stack* stack, Store* store, QString filename)
  {
    if(filename.isEmpty())
      throw QString("StackOpen::Error:Error, trying to load a stack from an empty filename.");

    actingFile(filename);
    store->setFile(filename);
    QFile file(filename);
    if(!file.open(QIODevice::ReadOnly))
      throw QString("StackOpen::Error:Cannot open input file - '%1'").arg(filename);

    progressStart(QString("Loading %1 Stack %2")
                .arg((store == stack->main()) ? "main" : "work").arg(stack->userId()), 0, false);

    if(filename.endsWith(".mgxs", Qt::CaseInsensitive)) {
      static const char version[] = "MGXS ";
      static const int size_version = sizeof(version) - 1;
      char header_version[size_version];
      file.read(header_version, size_version);
      if(memcmp(header_version, version, size_version) == 0) {
        QList<int> version = extractVersion(file);
        if(version.size() != 2) {
          QStringList str;
          foreach(int i, version)
          str << QString::number(i);
          throw QString("StackOpen::Error:Invalid file version format (%1), expected: MAJOR.MINOR")
                .arg(str.join("."));
        }
        bool success = false;
        switch(version[0]) {
        case 1:
          switch(version[1]) {
          case 0:
            success = loadMGXS_1_0(file, stack, store);
            stack->center();
            break;
          case 1:
            success = loadMGXS_1_1(file, stack, store);
            stack->center();
            break;
          case 2:
            success = loadMGXS_1_2(file, stack, store);
            break;
          case 3:
            success = loadMGXS_1_3(file, stack, store);
            break;
          default:
            throw QString("StackOpen::Error:Unknown file version %1.%2, this version of "
              "MorphoGraphX only loads up to version 1.4") .arg(version[0]) .arg(version[1]);
          }
          break;
        default:
          throw QString("StackOpen::Error:Unknown file version %1.%2, this version of "
            "MorphoGraphX only loads up to version 1.4") .arg(version[0]) .arg(version[1]);
        }
        if(!success)
          return false;
      } else {
        file.seek(0);
        if(!loadMGXS_0(file, stack, store))
          return false;
        stack->center();
      }
    } else if(filename.endsWith(".inr", Qt::CaseInsensitive)) {
      HVecUS& data = store->data();
      QByteArray stkdata = file.readAll();
      file.close();
      CImgUS image;
      Point3f voxelsz;
      image.load_inr(filename.toLocal8Bit(), voxelsz.data());
      Point3u size(image.width(), image.height(), image.depth());
      Point3f step(voxelsz.x(), voxelsz.y(), voxelsz.z());
      stack->setSize(size);
      stack->setStep(step);
      memcpy(data.data(), image.data(), stack->storeSize() * 2);
      stack->center();
    } else if(filename.endsWith(".tif", Qt::CaseInsensitive) or filename.endsWith(".tiff", Qt::CaseInsensitive)) {
      Image3D data(store->data(), stack->size(), stack->step());
      if(!loadTIFFImage(filename, data, true)) {
        stack->setSize(Point3u(0, 0, 0));
        stack->main()->changed();
        stack->work()->changed();
        setErrorMessage(
          QString("Error, cannot load TIFF file %1. Check standard output for exact error.").arg(filename));
        return false;
      }
      stack->setSize(data.size);
      stack->setStep(data.step);
      stack->center();
      store->setLabels(data.labels);
    // } else if(filename.endsWith(".h5", Qt::CaseInsensitive) or filename.endsWith(".hdf5", Qt::CaseInsensitive)
    //   or filename.endsWith(".hdf", Qt::CaseInsensitive)) { // h5 import

    //     QString qDataSetName = parm("HDF5 DataSetName");

    //     std::string datasetNameString = qDataSetName.toStdString();
    //     const char* datasetName = datasetNameString.c_str();

    //     std::string filenameString = filename.toStdString();
    //     const char* fileNameH5 = filenameString.c_str();

    //     traverse_h5_file(fileNameH5);

    //     if (qDataSetName.isEmpty()) {
    //         throw QString("StackOpen::Error: H5 dataset name cannot be empty");
    //     }

    //     // open H5 file
    //     H5File fileH5(fileNameH5, H5F_ACC_RDONLY);
    //     // open dataset
    //     DataSet dataset = fileH5.openDataSet(datasetName);
    //     // get data type
    //     H5T_class_t type_class = dataset.getTypeClass();

    //     if(type_class == H5T_INTEGER) {
    //       IntType intype = dataset.getIntType();
    //     } else if(type_class == H5T_FLOAT){
    //       FloatType fltype = dataset.getFloatType();
    //     } else {
    //       throw QString("StackOpen::Error:Unsupported dataset type - '%1'").arg(type_class);
    //     }

    //     DataSpace dataspace = dataset.getSpace();
    //     const int rank = dataspace.getSimpleExtentNdims();
    //     hsize_t* dims = new hsize_t[rank];
    //     dataspace.getSimpleExtentDims(dims, NULL);

    //     // get output vector size
    //     hsize_t dims_1d = 1;
    //     for(int i=0; i < rank; i++) {
    //         dims_1d *= dims[i];
    //     }

    //     std::vector<short> buf(dims_1d);
    //     // read depending on type
    //     if(type_class == H5T_INTEGER) {
    //       dataset.read(buf.data(), PredType::NATIVE_UINT16, dataspace, dataspace);
    //     } else if(type_class == H5T_FLOAT){
    //       std::vector<float> bufF(dims_1d);
    //       dataset.read(bufF.data(), PredType::NATIVE_FLOAT, dataspace, dataspace);

    //       // convert float (0...1) to short (0...65535)
    //       for(uint i = 0; i < bufF.size(); i++){
    //         buf[i] = (ushort)(bufF[i]*USHRT_MAX);
    //       }
    //     }

    //     // read element_size_um if present
    //     float* element_size_um = new float[3];
    //     std::fill_n(element_size_um, rank, 1.);
    //     if (dataset.attrExists(ELEMENT_SIZE_UM)) {
    //       const Attribute attribute = dataset.openAttribute(ELEMENT_SIZE_UM);
    //       const DataType dataType = attribute.getDataType();
    //       attribute.read(dataType, element_size_um);
    //     } else {
    //       std::cout << "No Element Size attribute found! Set voxel size to 1um x 1um x 1um!" << std::endl;
    //     }

    //     Point3u size(dims[2], dims[1], dims[0]);
    //     if(rank == 4) size = Point3u(dims[3], dims[2], dims[1]);
    //     // set voxel size
    //     Point3f step(element_size_um[2], element_size_um[1], element_size_um[0]);
    //     stack->setSize(size);
    //     stack->setStep(step);
    //     HVecUS &data = store->data();
    //     Point3i imgSize(stack->size());
    //     int xDim = imgSize.x();
    //     int yDim = imgSize.y();
    //     int zDim = imgSize.z();
    //     for (int x = 0; x <= xDim; x++) {
    //       for (int y = 0; y <= yDim; y++) {
    //         for (int z = 0; z <= zDim; z++) {
    //           Point3i pOut(x, y, z);
    //           if(stack->boundsOK(x,y,z))
    //             data[stack->offset(pOut)] = buf[stack->offset(pOut)];
    //         }
    //       }
    //     }
    //   fileH5.close();
    } else
      throw QString("StackOpen::Error:Invalid file type - %1").arg(filename);

    if(store->labels()) {
      ushort max_label = 0;
      const HVecUS& data = store->data();
      forall(const ushort& u, data) {
        ushort label = u;
        if(label > max_label)
          max_label = label;
      }
      stack->setLabel(max_label);
    }

    store->setFile(filename);
    store->changed();
    SETSTATUS("Loaded stack " << stack->userId() << ", file:" << store->file() << " size: " << stack->size());
    return true;
  }


  bool MeshImport::initialize(QWidget* parent)
  {
    int meshId = parm("Stack Number").toInt();
    if(!checkState().mesh(MESH_ANY, int(meshId)))
      return false;

    Mesh* mesh = this->mesh(meshId);
    QString filename = mesh->file();
    if(filename.isEmpty())
      filename = parm("Filename");

    if(!parent)
      return true;

    QDialog dlg(parent);
    Ui_ImportMeshDialog ui;
    ui.setupUi(&dlg);

    this->ui = &ui;
    this->dlg = &dlg;

    QObject::connect(ui.SelectMeshFile, SIGNAL(clicked()), this, SLOT(selectMeshFile()));
    QObject::connect(ui.MeshType, SIGNAL(currentIndexChanged(const QString &)), this,
                     SLOT(selectMeshType(const QString &)));

    setMeshFile(properFile(filename, parm("Kind")), parm("Kind"));
    ui.ScaleUm->setChecked(stringToBool(parm("Scale")));
    ui.Transform->setChecked(stringToBool(parm("Transform")));
    ui.Add->setChecked(stringToBool(parm("Add")));

    bool res = false;
    if(dlg.exec() == QDialog::Accepted) {
      setParm("Filename", ui.MeshFile->text());
      setParm("Kind", ui.MeshType->currentText());
      setParm("Scale", (ui.ScaleUm->isChecked() ? "yes" : "no"));
      setParm("Transform", (ui.Transform->isChecked() ? "yes" : "no"));
      setParm("Add", (ui.Add->isChecked()) ? "yes" : "no");
      res = true;
    }
    this->ui = 0;
    this->dlg = 0;
    return res;
  }

  bool MeshImport::run()
  {
    int meshId = parm("Stack Number").toInt();
    if(!checkState().mesh(MESH_ANY, meshId))
      return false;
    Mesh* mesh = this->mesh(meshId);
    QString type = parm("Kind");
    bool scale = stringToBool(parm("Scale"));
    bool transform = stringToBool(parm("Transform"));
    bool add = stringToBool(parm("Add"));
    bool res = run(mesh, parm("Filename"), type, scale, transform, add);
    if(res) {
      // Show mesh if nothing visible
      if(!(mesh->showMesh() or mesh->showSurface())) {
        mesh->setShowSurface();
        mesh->setShowMesh();
      }
      if(mesh->hasImgTex())
        mesh->setShowVertex("Image Texture");
      else
        mesh->setShowVertex("Signal");
      setNormals(mesh->graph());
    }
    return res;
  }

  bool MeshImport::run(Mesh* mesh, QString filename, QString type, bool scale, bool transform, bool add)
  {
    progressStart(QString("Loading Mesh %1").arg(mesh->userId()), 0, false);
    actingFile(filename);
    QFileInfo fi(filename);
    QString suf = fi.suffix();
    bool success = false;
    if(type.isEmpty()) {
      QString low_fn = filename.toLower();
      if(low_fn.endsWith(".ply", Qt::CaseInsensitive))
        type = "PLY Mesh";
      else if(low_fn.endsWith(".vtu", Qt::CaseInsensitive))
        type = "VTK Mesh";
      else if(low_fn.endsWith(".txt", Qt::CaseInsensitive))
        type = "Text";
      else if(low_fn.endsWith(".jpg", Qt::CaseInsensitive) or low_fn.endsWith(".jpeg", Qt::CaseInsensitive))
        type = "Keyence";
      else if(low_fn.endsWith(".mesh", Qt::CaseInsensitive))
        type = "MeshEdit";
      else if(low_fn.endsWith(".obj", Qt::CaseInsensitive))
        type = "OBJ";
      else {
        setErrorMessage(QString("Cannot find the type of the file '%1'").arg(filename));
        return false;
      }
    }
    if(type == "PLY")
      success = loadMeshPLY(mesh, filename, scale, transform, add);
    else if(type == "VTK Mesh")
      success = loadMeshVTK(mesh, filename, scale, transform, add);
    else if(type == "Text")
      success = loadText(mesh, filename, scale, transform, add);
    else if(type == "Cells")
      success = loadCells(mesh, filename, scale, transform, add);
    else if(type == "Keyence")
      success = loadKeyence(mesh, filename, scale, transform, add);
    else if(type == "MeshEdit")
      success = loadMeshEdit(mesh, filename, scale, transform, add);
    else if(type == "OBJ")
      success = loadMeshOBJ(mesh, filename, scale, transform, add);
    else {
      setErrorMessage(QString("Unknown file type '%1'").arg(type));
      return false;
    }
    if(success) {
      mesh->setFile(properFile(filename, "Mesh"));
      if(mesh->stack()->empty())
        mesh->setScaled(scale);
      else
        mesh->setScaled(true);
      mesh->setTransformed(transform);
      mesh->updateAll();
    }
    return success;
  }

  static Point3d realPos(Point3d pos, bool scale, bool transform, const Mesh* mesh)
  {
    if(!scale)
      pos = Point3d(mesh->stack()->abstractToWorld(Point3f(pos)));
    if(transform)
      pos = Point3d(mesh->stack()->frame().coordinatesOf(Vec(pos)));
    return pos;
  }

  bool MeshImport::loadText(Mesh* mesh, const QString& filename, bool scale, bool transform, bool add)
  {
    QFile file(filename);
    if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
      setErrorMessage(QString("loadText::Error:Cannot open input file: %1").arg(filename));
      return false;
    }

    vvGraph& S = mesh->graph();

    if(!add)
      mesh->reset();

    IntVtxMap vMap;

    QTextStream in(&file);
    QString line = in.readLine();
    QStringList fields = line.split(" ");
    if(fields.size() < 1)
      throw QString("%1::loadText Error reading vertex count").arg(name());
    int vCnt;
    vCnt = fields[0].toInt();
    if(vCnt == 0)
      throw QString("%1::loadText Vertex count zero").arg(name());
    progressStart(QString("Loading Mesh %1").arg(mesh->userId()), vCnt * 2);
    S.reserve(vCnt);
    double minSignal = std::numeric_limits<double>::max(), maxSignal = std::numeric_limits<double>::lowest();
    for(int i = 0; i < vCnt; i++) {
      if(in.atEnd())
        throw QString("Premature end of file reading vertices");
      vertex v;
      S.insert(v);
      QString line = in.readLine();
      QStringList fields = line.split(" ");
      if(fields.size() < 5)
        throw QString("%1::loadText Error reading vertex on line %2").arg(name()).arg(i);
      v->saveId = fields[0].toInt();
      v->pos.x() = fields[1].toDouble();
      v->pos.y() = fields[2].toDouble();
      v->pos.z() = fields[3].toDouble();
      v->label = fields[4].toInt();
      if(fields.size() > 5) {
        double signal = fields[5].toDouble();
        v->signal = signal;
        if(minSignal > signal)
          minSignal = signal;
        if(maxSignal < signal)
          maxSignal = signal;
      }

      v->pos = realPos(v->pos, scale, transform, mesh);
      vMap[v->saveId] = v;
      if(v->label > mesh->viewLabel())
        mesh->setLabel(v->label);
      if(!progressAdvance(i))
        userCancel();
    }
    for(int i = 0; i < vCnt; i++) {
      if(in.atEnd())
        throw QString("Premature end of file reading neighborhoods");
      uint vId, nCnt;
      in >> vId >> nCnt;
      IntVtxMap::const_iterator vIt = vMap.find(vId);
      if(vIt == vMap.end())
        throw QString("Invalid vertex id: %1").arg(vId);
      vertex v = vIt->second;
      vertex pn(0);
      for(uint j = 0; j < nCnt; j++) {
        uint nId;
        in >> nId;
        IntVtxMap::const_iterator nIt = vMap.find(nId);
        if(nIt == vMap.end())
          throw QString("Invalid vertex id: %1").arg(nId);
        vertex n = nIt->second;
        if(S.valence(v) == 0)
          S.insertEdge(v, n);
        else
          S.spliceAfter(v, pn, n);
        pn = n;
      }
      if(!progressAdvance(vCnt + i))
        userCancel();
    }
    if(!progressAdvance(2 * vCnt))
      userCancel();
    file.close();
    mesh->clearImgTex();
    setNormals(mesh->graph());
    mesh->setMeshType("MGXM");
    if(minSignal < maxSignal)
      mesh->signalBounds() = Point2f(minSignal, maxSignal);

    SETSTATUS("Loaded mesh, file:" << mesh->file() << ", vertices:" << S.size());
    return true;
  }

  void showHeader(PlyFile& ply)
  {
    Information::out << "PLY file content";
    Information::out << "format = " << PlyFile::formatNames[ply.format()] << endl;
    Information::out << "version = " << ply.version() << endl;
    Information::out << "content position = " << ply.contentPosition() << endl;
    Information::out << endl;
    Information::out << "# elements = " << ply.nbElements() << endl;
    for(size_t i = 0; i < ply.nbElements(); ++i) {
      const PlyFile::Element& element = *ply.element(i);
      Information::out << "Element '" << element.name() << "' - " << element.size()
                       << " # properties = " << element.nbProperties() << endl;
      for(size_t j = 0; j < element.nbProperties(); ++j) {
        const PlyFile::Property& prop = *element.property(j);
        Information::out << "Property '" << prop.name() << "'";
        if(prop.kind() == PlyFile::Property::LIST)
          Information::out << " list " << PlyFile::typeNames[prop.sizeType()][0];
        Information::out << " " << PlyFile::typeNames[prop.fileType()][0] << " / "
                         << PlyFile::typeNames[prop.memType()][0] << endl;
      }
    }
  }

  bool MeshImport::loadMeshPLY(Mesh* mesh, const QString& filename, bool scale,
                                                                bool transform, bool add)
  {
    vvGraph &S = mesh->graph();

    progressStart(QString("Loading PLY Mesh %1").arg(mesh->userId()), 0);

    if(!add)
      mesh->reset();

    // Define the reader and read header
    PlyFile ply;
    if(!ply.parseHeader(filename))
      throw(QString("Cannot parse PLY file header"));

    // Get vertices and properties
    PlyFile::Element* vtx = ply.element("vertex");
    if(!vtx)
      throw(QString("No vertex element in PLY file"));

    PlyFile::Property* vx = vtx->property("x");   // vertex positions
    PlyFile::Property* vy = vtx->property("y");
    PlyFile::Property* vz = vtx->property("z");
    if(!vx or !vy or !vz)
      throw(QString("x, y or z missing from vertex element in PLY file"));

    vx->setMemType(PlyFile::FLOAT);
    vy->setMemType(PlyFile::FLOAT);
    vz->setMemType(PlyFile::FLOAT);

    PlyFile::Property* vlabel = vtx->property("label");   // vertex labels
    if(vlabel)
      vlabel->setMemType(PlyFile::INT);

    // Vertex signal, use color if no signal present
    bool hasSignal = false;
    bool hasRGB = false;
    PlyFile::Property* vSignal = vtx->property("signal");
    PlyFile::Property* vRed = vtx->property("red");
    PlyFile::Property* vGreen = vtx->property("green");
    PlyFile::Property* vBlue = vtx->property("blue");
    if(vSignal) {
      hasSignal = true;
      vSignal->setMemType(PlyFile::FLOAT);
    } else if(vRed and vGreen and vBlue) {
      hasRGB = true;
      vRed->setMemType(PlyFile::UCHAR);
      vGreen->setMemType(PlyFile::UCHAR);
      vBlue->setMemType(PlyFile::UCHAR);
    }

    // Get faces and properties
    PlyFile::Element* faces = ply.element("face");   // faces
    if(!faces)
      throw(QString("No face element in PLY file"));

    PlyFile::Property* vi = faces->property("vertex_index");
    if(!vi) {
      vi = faces->property("vertex_indices");
      if(!vi)
        throw(QString("Neither vertex_index or vertex_indices are present in face element"));
    }
    vi->setMemType(PlyFile::INT);

    PlyFile::Property* flabel = faces->property("label");   // face labels
    if(flabel)
      flabel->setMemType(PlyFile::INT);

    // Parse file
    if(!ply.parseContent())
      throw(QString("Unable to parse contents of PLY file"));

    // Get vertex data
    std::vector<vertex> vertices(vtx->size(), vertex(0));   // vertex positions
    const std::vector<float>& px = *vx->value<float>();
    const std::vector<float>& py = *vy->value<float>();
    const std::vector<float>& pz = *vz->value<float>();

    std::vector<int>* vlabelp = 0;   // vertex labels
    if(vlabel)
      vlabelp = vlabel->value<int>();

    std::vector<float>* vSignalp = 0;   // vertex signal
    std::vector<uchar>* vRedp = 0;   // or vertex colors
    std::vector<uchar>* vGreenp = 0;
    std::vector<uchar>* vBluep = 0;
    if(hasSignal) {
      Information::out << "Has signal information" << endl;
      vSignalp = vSignal->value<float>();
    } else if(hasRGB) {
      Information::out << "Has RGB information" << endl;
      vRedp = vRed->value<uchar>();
      vGreenp = vGreen->value<uchar>();
      vBluep = vBlue->value<uchar>();
    }

    // Get face data
    std::vector<Point3i> triangles;   // faces
    const std::vector<std::vector<int32_t> >& vertex_index = *vi->list<int32_t>();

    std::vector<int>* flabelp = 0;   // face labels
    if(flabel)
      flabelp = flabel->value<int>();

    // See if cellular mesh
    bool cells = false;
    for(size_t i = 0; i < faces->size(); ++i)
      if(vertex_index[i].size() > 3)
        cells = true;

    // Process vertices
    Point2f signalBounds(std::numeric_limits<float>::max(), std::numeric_limits<float>::min());
    if(!hasRGB and !hasSignal)
      signalBounds = Point2f(0, 65535);
    progressStart(QString("Loading PLY Mesh %1 vertices").arg(mesh->userId()), vtx->size(), false);
    for(uint i = 0; i < vtx->size(); ++i) {
      progressAdvance(i);
      vertex v;
      v->pos = realPos(Point3d(px[i], py[i], pz[i]), scale, transform, mesh);
      v->saveId = i;
      v->type = 'j';
      vertices[i] = v;
      if(cells)
        v->label = -1;
      else if(vlabel)
        v->label = (*vlabelp)[i];

      if(hasSignal)
        v->signal = (*vSignalp)[i];
      else if(hasRGB)
        v->signal = (0.21 * (*vRedp)[i] + 0.71 * (*vGreenp)[i] + 0.07 * (*vBluep)[i]) / 256.0;

      if(signalBounds.x() > v->signal)
        signalBounds.x() = v->signal;
      if(signalBounds.y() < v->signal)
        signalBounds.y() = v->signal;

      if(v->label > mesh->viewLabel())
        mesh->setLabel(v->label);
    }
    mesh->signalBounds() = signalBounds;

    // Process faces
    int nextLabel = mesh->viewLabel();
    progressStart(QString("Loading PLY Mesh %1 faces").arg(mesh->userId()), faces->size(), false);
    for(size_t i = 0; i < faces->size(); ++i) {
      progressAdvance(i);
      if(cells) {
        // If a cellular mesh, make a triangle fan.
        vertex c;

        // Find center vertex position
        c->pos = Point3d(0, 0, 0);
        forall(int j, vertex_index[i])
          c->pos += vertices[j]->pos;
        c->pos /= vertex_index[i].size();
        c->type = 'c';

        // Put label on center vertex
        if(flabel)
          c->label = (*flabelp)[i];
        else
          c->label = ++nextLabel;

        int centerv = vertices.size();
        for(size_t j = 0; j < vertex_index[i].size(); ++j) {
          size_t prev = j - 1;
          if(j == 0)
            prev = vertex_index[i].size() - 1;

          triangles.push_back(Point3i(centerv, vertex_index[i][prev], vertex_index[i][j]));
        }
        vertices.push_back(c);

        if(c->label > mesh->viewLabel())
          mesh->setLabel(c->label);
      } else     // For simple triangle mesh, just add the triangle
        triangles.push_back(Point3i(vertex_index[i][0], vertex_index[i][1], vertex_index[i][2]));
    }

    // Create connectivity from triangle list
    progressStart(QString("Creating Mesh %1 from triangle list").arg(mesh->userId()), 0, false);
    if(!Mesh::meshFromTriangles(S, vertices, triangles, false))
      throw(QString("Cannot add all the triangles"));

    mesh->setMeshType(cells ? "MGXC" : "MGXM");
    mesh->clearImgTex();
    setNormals(mesh->graph());
    if(cells)
      SETSTATUS("Loaded cellular mesh, file:" << mesh->file() << ", vertices:" << S.size());
    else
      SETSTATUS("Loaded triangle mesh, file:" << mesh->file() << ", vertices:" << S.size());
    return true;
  }

  bool MeshImport::loadCells(Mesh* mesh, const QString& filename, bool scale, bool transform, bool add)
  {
    QFile file(filename);
    if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
      setErrorMessage(QString("loadMesh::Error:Cannot open input file: %1").arg(filename));
      return false;
    }

    vvGraph& S = mesh->graph();

    if(!add)
      mesh->reset();

    IntVtxMap vMap;

    QTextStream in(&file);
    int vCnt;
    in >> vCnt;
    progressStart(QString("Loading Cellular Mesh %1").arg(mesh->userId()), vCnt * 2);
    S.reserve(vCnt);
    for(int i = 0; i < vCnt; i++) {
      if(in.atEnd())
        throw QString("Premature end of file reading vertices");
      vertex v;
      S.insert(v);
      char tchar;
      in >> v->saveId >> v->pos >> v->label >> tchar >> v->type;
      v->pos = realPos(v->pos, scale, transform, mesh);
      vMap[v->saveId] = v;
      if(v->label > mesh->viewLabel())
        mesh->setLabel(v->label);
      if(!progressAdvance(i))
        userCancel();
    }
    for(int i = 0; i < vCnt; i++) {
      if(in.atEnd())
        throw QString("Premature end of file reading neighborhoods");
      uint vId, nCnt;
      in >> vId >> nCnt;
      IntVtxMap::const_iterator vIt = vMap.find(vId);
      if(vIt == vMap.end())
        throw QString("Invalid vertex id: %1").arg(vId);
      vertex v = vIt->second;
      vertex pn(0);
      for(uint j = 0; j < nCnt; j++) {
        uint nId;
        in >> nId;
        IntVtxMap::const_iterator nIt = vMap.find(nId);
        if(nIt == vMap.end())
          throw QString("Invalid vertex id: %1").arg(nId);
        vertex n = nIt->second;
        if(S.valence(v) == 0)
          S.insertEdge(v, n);
        else
          S.spliceAfter(v, pn, n);
        pn = n;
      }
      if(!progressAdvance(vCnt + i))
        userCancel();
    }
    if(!progressAdvance(2 * vCnt))
      userCancel();
    file.close();
    mesh->setMeshType("MGXC");
    mesh->clearImgTex();
    setNormals(mesh->graph());
    SETSTATUS("Loaded cell mesh, file:" << mesh->file() << ", vertices:" << S.size());
    return true;
  }

  bool MeshImport::loadKeyence(Mesh* mesh, const QString& filename, bool scale, bool transform, bool /*add*/)
  {
    QFile file(filename);
    if(!file.open(QIODevice::ReadOnly)) {
      setErrorMessage(QString("loadMesh::Error:Cannot open input file: %1").arg(filename));
      return false;
    }

    vvGraph& S = mesh->graph();

    mesh->reset();

    // First read in the entire file
    QByteArray buff = file.readAll();
    if(buff.size() != file.size())
      throw QString("Could not read the whole file");
    file.close();
    // Grab the image size
    int xsize = *(short*)(buff.data() + 47);
    int ysize = *(short*)(buff.data() + 49);
    // Height map uses every 4th pixel
    int xsz = xsize / 4;
    int ysz = ysize / 4;
    progressStart(QString("Loading Height Map %1").arg(mesh->userId()), ysz);

    // Find second image file
    int imgpos = buff.indexOf(QByteArray("JFIF"), 16);
    if(imgpos < 0)
      throw QString("Can't find color image");
    imgpos -= 6;

    QByteArray imgba(buff.data() + imgpos, buff.size() - imgpos - xsz * ysz * 4);
    QImage image;
    image.loadFromData(imgba);

    if(xsize != image.width() or ysize != image.height())
      throw QString("Invalid image size");
    mesh->setImgTex(image);

    // Find max/min z values to center image in z
    float maxz = -1e10, minz = 1e10;
    float* htp = (float*)(buff.data() + buff.size() - xsz * ysz * 4);
    S.reserve(xsz * ysz);
    for(int i = 0; i < xsz * ysz; i++) {
      float z = *htp++;
      if(z > maxz)
        maxz = z;
      if(z < minz)
        minz = z;
    }
    float zshift = (minz + maxz) / 2.0;

    // Save the vertex ids for connecting
    // vertex vtx[xsz][ysz];
    std::vector<std::vector<vertex> > vtx(xsz, std::vector<vertex>(ysz, vertex(0)));

    // Create the vertices and fill in height and texture coords
    VtxPoint2fAttr& txPos = mesh->texCoord2d();
    htp = (float*)(buff.data() + buff.size() - xsz * ysz * 4);
    for(int y = ysz - 1; y >= 0; y--) {
      for(int x = xsz - 1; x >= 0; x--) {
        float z = *htp++;
        vertex v;
        S.insert(v);
        vtx[x][y] = v;
        v->pos.x() = (double(x) / double(xsz) - .5) * 4 / 3;
        v->pos.y() = double(y) / double(ysz) - .5;
        v->pos.z() = (z - zshift) * .03;
        v->pos = realPos(v->pos, scale, transform, mesh);
        v->*txPos = Point2f(float(x * 4) / float(xsize), float(y * 4) / float(ysize));
      }
    }

    // Connect neighborhoods
    for(int y = 0; y < ysz; y++) {
      if(!progressAdvance(y + 1))
        throw(QString("Keyence load cancelled"));
      for(int x = 0; x < xsz; x++) {
        std::vector<vertex> nhbs;
        nhbs.push_back(vtx[x][y]);
        if(x == 0 and y == ysz - 1) {       // top left corner;
          nhbs.push_back(vtx[x][y - 1]);
          nhbs.push_back(vtx[x + 1][y - 1]);
          nhbs.push_back(vtx[x + 1][y]);
        } else if(x == 0 and y == 0) {       // bottom left corner
          nhbs.push_back(vtx[x + 1][y]);
          nhbs.push_back(vtx[x][y + 1]);
        } else if(x == xsz - 1 and y == 0) {       // bottom right corner
          nhbs.push_back(vtx[x][y + 1]);
          nhbs.push_back(vtx[x - 1][y + 1]);
          nhbs.push_back(vtx[x - 1][y]);
        } else if(x == xsz - 1 and y == ysz - 1) {       // top right corner
          nhbs.push_back(vtx[x - 1][y]);
          nhbs.push_back(vtx[x][y - 1]);
        } else if(y == ysz - 1) {       // top edge
          nhbs.push_back(vtx[x - 1][y]);
          nhbs.push_back(vtx[x][y - 1]);
          nhbs.push_back(vtx[x + 1][y - 1]);
          nhbs.push_back(vtx[x + 1][y]);
        } else if(x == 0) {       // left edge
          nhbs.push_back(vtx[x][y - 1]);
          nhbs.push_back(vtx[x + 1][y - 1]);
          nhbs.push_back(vtx[x + 1][y]);
          nhbs.push_back(vtx[x][y + 1]);
        } else if(y == 0) {       // bottom edge
          nhbs.push_back(vtx[x + 1][y]);
          nhbs.push_back(vtx[x][y + 1]);
          nhbs.push_back(vtx[x - 1][y + 1]);
          nhbs.push_back(vtx[x - 1][y]);
        } else if(x == xsz - 1) {       // right edge
          nhbs.push_back(vtx[x][y - 1]);
          nhbs.push_back(vtx[x - 1][y + 1]);
          nhbs.push_back(vtx[x - 1][y]);
          nhbs.push_back(vtx[x][y + 1]);
        } else {       // Interior vertex
          nhbs.push_back(vtx[x + 1][y - 1]);
          nhbs.push_back(vtx[x + 1][y]);
          nhbs.push_back(vtx[x][y + 1]);
          nhbs.push_back(vtx[x - 1][y + 1]);
          nhbs.push_back(vtx[x - 1][y]);
          nhbs.push_back(vtx[x][y - 1]);
        }
        vertex v = nhbs[0];
        vertex pn(0);
        for(uint i = 1; i < nhbs.size(); i++) {
          vertex n = nhbs[i];
          if(i == 1)
            S.insertEdge(v, n);
          else
            S.spliceAfter(v, pn, n);
          pn = n;
        }
      }
    }
    mesh->setMeshType("MGXM");
    setNormals(mesh->graph());
    SETSTATUS("Loaded Keyence mesh, file:" << mesh->file() << ", vertices:" << S.size());
    return true;
  }

  bool MeshImport::loadMeshOBJ(Mesh* mesh, const QString& filename, bool scale, bool transform, bool add)
  {
    QFile file(filename);
    if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
      setErrorMessage(QString("loadMesh::Error:Cannot open input file: %1").arg(filename));
      return false;
    }

    std::vector<Point3f> points;
    std::vector<float> signal;
    std::vector<int> labels;
    IntFloatAttr& label_heat = mesh->labelHeat();
    label_heat.clear();
    std::vector<Point3i> triangles;
    float minSignal = FLT_MAX, maxSignal = -FLT_MAX;
    float minHeat = FLT_MAX, maxHeat = -FLT_MAX;

    QTextStream ts(&file);
    int line_count = 0;
    bool fixedBound = false;

    Information::out << "Reading the file" << endl;
    QString signalDescription;
    QString heatDescription;

    while(!ts.atEnd()) {
      ++line_count;
      QString line = ts.readLine().trimmed();
      int idx = line.indexOf('#');
      if(idx != -1)
        line = line.left(idx).trimmed();
      if(!line.isEmpty()) {
        QStringList fields = line.split(QRegExp("[ \t]"), QString::SkipEmptyParts);
        if(fields[0] == "v") {
          //if(fields.size() != 4 and fields.size() != 5) {
          if(fields.size() < 4)
            throw QString("Error reading file '%1' on line %2: vertex must have at least 3 (x y z [w] ...), got %3")
                                                                 .arg(filename).arg(line_count).arg(fields.size() - 1);
          Point3f pos;
          bool ok;
          pos.x() = fields[1].toFloat(&ok);
          if(!ok) {
            setErrorMessage(QString("Error reading file '%1' on line %2: invalid value for vertex x: '%3'")
                            .arg(filename)
                            .arg(line_count)
                            .arg(fields[1]));
            return false;
          }
          pos.y() = fields[2].toFloat(&ok);
          if(!ok) {
            setErrorMessage(QString("Error reading file '%1' on line %2: invalid value for vertex y: '%3'")
                            .arg(filename)
                            .arg(line_count)
                            .arg(fields[2]));
            return false;
          }
          pos.z() = fields[3].toFloat(&ok);
          if(!ok) {
            setErrorMessage(QString("Error reading file '%1' on line %2: invalid value for vertex z: '%3'")
                            .arg(filename)
                            .arg(line_count)
                            .arg(fields[3]));
            return false;
          }
          points.push_back(pos);

          // If there is a 5th field, this is w, so we need to normalize the position with this
          if(fields.size() == 5) {
            float w = fields[4].toFloat(&ok);
            if(!ok) {
              setErrorMessage(QString("Error reading file '%1' on line %2: invalid value for vertex w: '%3'")
                              .arg(filename)
                              .arg(line_count)
                              .arg(fields[4]));
              return false;
            }
            if(w != 0)
              pos /= w;
            else {
              setErrorMessage(
                QString("Error reading file '%1' on line %2: MorphoGraphX cannot interpret a vertex w of 0")
                .arg(filename)
                .arg(line_count));
              return false;
            }
          }
        } else if(fields[0] == "f") {
          std::vector<int> poly(fields.size() - 1);
          bool ok;
          int psz = points.size();
          for(int i = 0; i < fields.size() - 1; ++i) {
            poly[i] = fields[i + 1].toInt(&ok) - 1;
            if(!ok or poly[i] < 0 or poly[i] >= psz) {
              setErrorMessage(
                QString("Error reading file '%1' on line %2: the first vertex id is not valid: '%3'")
                .arg(filename)
                .arg(line_count)
                .arg(fields[i + 1]));
              return false;
            }
          }
          // If a triangle, just add it
          if(poly.size() == 3)
            triangles.push_back(Point3i(poly[0], poly[1], poly[2]));
          else {         // Otherwise make a triangle fan
                         // Find center
            Point3f c(0, 0, 0);
            for(size_t i = 0; i < poly.size(); ++i)
              c += points[poly[i]];
            c /= poly.size();
            points.push_back(c);

            // Add tris
            for(size_t i = 0; i < poly.size(); ++i)
              if(i == poly.size() - 1)
                triangles.push_back(Point3i(psz, poly[i], poly[0]));
              else
                triangles.push_back(Point3i(psz, poly[i], poly[i + 1]));
          }
        } else if(fields[0] == "label") {
          if(fields.size() != 2) {
            setErrorMessage(
              QString("Error reading file '%1' on line %2: the label field must have 1 value, got %3")
              .arg(filename)
              .arg(line_count)
              .arg(fields.size() - 1));
            return false;
          }
          int lab;
          bool ok;
          lab = fields[1].toInt(&ok);
          if(!ok) {
            setErrorMessage(
              QString("Error reading file '%1' on line %2: the vertex label has invalid value: '%3'")
              .arg(filename)
              .arg(line_count)
              .arg(fields[1]));
            return false;
          }
          labels.push_back(lab);
        } else if(fields[0] == "vv") {
          if(fields.size() != 2) {
            setErrorMessage(
              QString("Error reading file '%1' on line %2: the vv field must have 1 value, got %3")
              .arg(filename)
              .arg(line_count)
              .arg(fields.size() - 1));
            return false;
          }
          float value;
          bool ok;
          value = fields[1].toFloat(&ok);
          if(!ok) {
            setErrorMessage(
              QString("Error reading file '%1' on line %2: the vertex value has invalid value: '%3'")
              .arg(filename)
              .arg(line_count)
              .arg(fields[1]));
            return false;
          }
          signal.push_back(value);
          if(!fixedBound) {
            if(value > maxSignal)
              maxSignal = value;
            if(value < minSignal)
              minSignal = value;
          }
        } else if(fields[0] == "vv_desc") {
          fields.pop_front();
          signalDescription = fields.join(" ");
        } else if(fields[0] == "vv_range") {
          if(fields.size() != 3) {
            return setErrorMessage(
              QString("Error reading file '%1' on line %2: the vv_range needs 2 values, not %3")
              .arg(filename)
              .arg(line_count)
              .arg(fields.size() - 1));
          }
          float vmin, vmax;
          bool ok;
          vmin = fields[1].toFloat(&ok);
          if(!ok) {
            return setErrorMessage(
              QString("Error reading file '%1' on line %2: min value for vv_range is not a value float: %3")
              .arg(filename)
              .arg(line_count)
              .arg(fields[1]));
          }
          vmax = fields[2].toFloat(&ok);
          if(!ok) {
            return setErrorMessage(
              QString("Error reading file '%1' on line %2: max value for vv_range is not a value float: %3")
              .arg(filename)
              .arg(line_count)
              .arg(fields[2]));
          }
          minSignal = vmin;
          maxSignal = vmax;
          Information::out << "vv_range = " << minSignal << " - " << maxSignal << endl;
          fixedBound = true;
        } else if(fields[0] == "heat") {
          if(fields.size() != 3) {
            setErrorMessage(
              QString("Error reading file '%1' on line %2: the vv field must have 2 value, got %3")
              .arg(filename)
              .arg(line_count)
              .arg(fields.size() - 1));
            return false;
          }
          int lab;
          float value;
          bool ok;
          lab = fields[1].toInt(&ok);
          if(!ok) {
            setErrorMessage(
              QString("Error reading file '%1' on line %2: the heat label has invalid value: '%3'")
              .arg(filename)
              .arg(line_count)
              .arg(fields[1]));
            return false;
          }
          value = fields[2].toFloat(&ok);
          if(!ok) {
            setErrorMessage(
              QString("Error reading file '%1' on line %2: the heat value has invalid value: '%3'")
              .arg(filename)
              .arg(line_count)
              .arg(fields[2]));
            return false;
          }
          label_heat[lab] = value;
          if(value < minHeat)
            minHeat = value;
          if(value > maxHeat)
            maxHeat = value;
        } else if(fields[0] == "heat_desc") {
          fields.pop_front();
          heatDescription = fields.join(" ");
        }
        // Ignore anything else
      }
    }

    if(!signal.empty() and signal.size() != points.size()) {
      setErrorMessage(
        QString(
          "Error reading file '%1', signal array size mismatch. Current file has %2 vs fields and %3 vertices")
        .arg(filename)
        .arg(signal.size())
        .arg(points.size()));
      return false;
    }
    if(!labels.empty() and labels.size() != points.size()) {
      setErrorMessage(QString("Error reading file '%1', labels array size mismatch. Current file has %2 labels "
                              "fields and %3 vertices")
                      .arg(filename)
                      .arg(labels.size())
                      .arg(points.size()));
      return false;
    }

    Information::out << "Extracted " << points.size() << " points and " << triangles.size() << " triangles" << endl;

    vvGraph& S = mesh->graph();

    if(!add)
      mesh->reset();
    else
      forall(const vertex& v, S)
        v->saveId = -1;

    if(signal.empty() and not fixedBound) {
      minSignal = 0.0;
      maxSignal = 1.0;
    } else if(maxSignal == minSignal)
      minSignal -= 1.0f;

    // First add vertices
    std::vector<vertex> vtx(points.size(), vertex(0));
    for(size_t i = 0; i < points.size(); ++i) {
      vertex v;
      v->pos = realPos(Point3d(points[i]), scale, transform, mesh);
      vtx[i] = v;
      if(signal.empty())
        v->signal = 1.0;
      else {
        v->signal = signal[i];
      }
      if(!labels.empty())
        v->label = labels[i];
      // Check for NaN
      v->signal = v->signal == v->signal ? v->signal : 0;
    }

    if(!Mesh::meshFromTriangles(S, vtx, triangles, false)) {
      setErrorMessage("Error, cannot add all the triangles");
      return false;
    }

    std::cout << "MinSignal:" << minSignal << ", MaxSignal:" << maxSignal << std::endl;

    if(!label_heat.empty()) {
      if(minHeat >= maxHeat)
        maxHeat += 1;
      mesh->heatMapBounds() = Point2f(minHeat, maxHeat);
      Information::out << "heatMapBounds = " << mesh->heatMapBounds() << endl;
      mesh->setShowLabel("Label Heat");
    }
    if(!heatDescription.isEmpty())
      mesh->heatMapUnit() = heatDescription;

    mesh->signalBounds() = Point2f(minSignal, maxSignal);
    mesh->signalUnit() = signalDescription;
    mesh->setMeshType("MGXM");
    mesh->clearImgTex();
    setNormals(mesh->graph());
    mesh->updateBBox();
    mesh->setCulling(false);
    mesh->updateAll();

    return true;
  }

  bool MeshImport::loadMeshEdit(Mesh* mesh, const QString& filename, bool scale, bool transform, bool add)
  {
    QFile file(filename);
    if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
      setErrorMessage(QString("loadMesh::Error:Cannot open input file: %1").arg(filename));
      return false;
    }

    vvGraph& S = mesh->graph();

    if(!add)
      mesh->reset();
    else
      forall(const vertex& v, S)
        v->saveId = -1;

    std::vector<vertex> vertices;
    std::vector<Point3i> triangles;

    QTextStream in(&file);
    QString tmp;
    tmp = in.readLine();
    tmp = in.readLine();
    tmp = in.readLine();
    int vCnt;
    in >> vCnt;
    progressStart(QString("Loading Triangle Mesh %1").arg(mesh->userId()), vCnt);
    int prog = 1;
    uint saveId = 0;
    S.reserve(vCnt);
    for(int i = 0; i < vCnt; i++) {
      if(in.atEnd())
        throw(QString("Premature end of file reading vertices"));
      vertex v;
      in >> v->pos >> v->label;
      v->pos = realPos(v->pos, scale, transform, mesh);
      v->saveId = saveId++;
      vertices.push_back(v);
      if(v->label > mesh->viewLabel())
        mesh->setLabel(v->label);
      if(!progressAdvance(prog++))
        userCancel();
    }
    tmp = in.readLine();
    tmp = in.readLine();
    int tCnt, t;

    in >> tCnt;
    progressStart(QString("Loading Triangle Mesh %1").arg(mesh->userId()), tCnt);
    for(int i = 0; i < tCnt; i++) {
      if(in.atEnd())
        throw QString("Premature end of file reading triangles");
      Point3i tri;
      in >> tri.x() >> tri.y() >> tri.z() >> t;
      tri -= Point3i(1, 1, 1);
      triangles.push_back(tri);
      if(!progressAdvance(prog++))
        userCancel();
    }
    file.close();

    if(!Mesh::meshFromTriangles(S, vertices, triangles, false)) {
      setErrorMessage("Error, cannot add all the triangles");
      return false;
    }
    mesh->setMeshType("MGXM");
    mesh->clearImgTex();
    setNormals(mesh->graph());
    SETSTATUS("Loaded mesh, file:" << mesh->file() << ", vertices:" << S.size());
    return true;
  }

  void MeshImport::selectMeshType(const QString& type)
  {
    QString filename = ui->MeshFile->text();
    if(filename.isEmpty())
      return;
    QFileInfo fi(filename);
    QString suf = fi.suffix();
    if(!suf.isEmpty())
      filename = filename.left(filename.size() - suf.size() - 1);
    if(type == "Mesh")
      filename += ".mgxm";
    else if(type == "PLY")
      filename += ".ply";
    else if(type == "Text" or type == "Cells")
      filename += ".txt";
    else if(type == "Keyence")
      filename += ".jpg";
    else if(type == "MeshEdit")
      filename += ".mesh";
    else if(type == "OBJ")
      filename += ".obj";
    ui->MeshFile->setText(filename);
  }

  void MeshImport::selectMeshFile()
  {
    QString filename = ui->MeshFile->text();
    QStringList filters;
    filters << "Stanford Polygon files (*.ply)"
            << "VTK Mesh files (*.vtu)"
            << "Text or cell files (*.txt)"
            << "Keyence files (*.jpg)"
            << "MeshEdit files (*.mesh)"
            << "OBJ files (*.obj)"
            << "All Mesh Files (*.ply *.vtu *.txt *.jpg *.mesh *.obj)";
    QString filter;
    {
      QFileInfo fi(filename);
      QString suf = fi.suffix();
      if(suf == "vtu")
        filter = filters[0];
      else if(suf == "vtu")
        filter = filters[1];
      else if(suf == "txt")
        filter = filters[2];
      else if(suf == "jpg")
        filter = filters[3];
      else if(suf == "mesh")
        filter = filters[4];
      else if(suf == "obj")
        filter = filters[5];
      else
        filter = filters[6];
    }
    filename = QFileDialog::getOpenFileName(dlg, QString("Select mesh file"), filename, filters.join(";;"), &filter,
                                            FileDialogOptions);
    if(!filename.isEmpty()) {
      if(!QFile::exists(filename)) {
        QMessageBox::information(dlg, "Error selecting file",
                                 "You must select an existing file. Your selection is ignored.");
        return;
      }
      setMeshFile(filename);
    }
  }

  QString MeshImport::properFile(QString filename, const QString& type) const
  {
    QFileInfo fi(filename);
    QString suf = fi.suffix();
    if(!suf.isEmpty())
      filename = filename.left(filename.size() - suf.size() - 1);
    if(type == "PLY Mesh")
      filename += ".ply";
    else if(type == "VTK Mesh")
      filename += ".vtu";
    else if(type == "Text" or type == "Cells")
      filename += ".txt";
    else if(type == "MeshEdit")
      filename += ".mesh";
    else if(type == "Keyence")
      filename += ".jpg";
    else if(type == "OBJ")
      filename += ".obj";
    return filename;
  }

  void MeshImport::setMeshFile(const QString& filename, const QString& type)
  {
    QFileInfo fi(filename);
    int tid = 0;
    // Note: these ids need to match those in importmesh.ui
    if(type.isEmpty()) {
      QString suf = fi.suffix();
      if(suf == "ply")
        tid = 0;
      else if(suf == "vtu")
        tid = 1;
      else if(suf == "txt")
        tid = 2;
      else if(suf == "jpg")
        tid = 4;
      else if(suf == "mesh")
        tid = 5;
      else if(suf == "obj")
        tid = 6;
      ui->MeshFile->setText(filename);
    } else {
      if(type == "PLY")
        tid = 0;
      else if(type == "VTK Mesh")
        tid = 1;
      else if(type == "Text")
        tid = 2;
      else if(type == "Cells")
        tid = 3;
      else if(type == "Keyence")
        tid = 4;
      else if(type == "MeshEdit")
        tid = 5;
      else if(type == "OBJ")
        tid = 6;
      ui->MeshFile->setText(properFile(filename, type));
    }
    ui->MeshType->setCurrentIndex(tid);
  }

  bool MeshLoad::initialize(QWidget* parent)
  {
    int meshId = parm("Stack Number").toInt();
    if(!checkState().mesh(MESH_ANY, meshId))
      return false;

    Mesh* mesh = this->mesh(meshId);
    QString filename = mesh->file();
    if(filename.isEmpty())
      filename = parm("Filename");

    if(!parent)
      return true;

    QDialog dlg(parent);
    Ui_LoadMeshDialog ui;
    ui.setupUi(&dlg);

    this->ui = &ui;
    this->dlg = &dlg;

    QObject::connect(ui.SelectMeshFile, SIGNAL(clicked()), this, SLOT(selectMeshFile()));

    setMeshFile(filename);
    ui.Transform->setChecked(stringToBool(parm("Transform")));
    ui.Add->setChecked(stringToBool(parm("Add")));

    bool res = false;
    if(dlg.exec() == QDialog::Accepted) {
      setParm("Filename", ui.MeshFile->text());
      setParm("Transform", (ui.Transform->isChecked()) ? "yes" : "no");
      setParm("Add", (ui.Add->isChecked()) ? "yes" : "no");
      res = true;
    }
    this->ui = 0;
    this->dlg = 0;
    return res;
  }

  void MeshLoad::selectMeshFile()
  {
    QString filename = ui->MeshFile->text();
    filename = QFileDialog::getOpenFileName(dlg, QString("Select mesh file"), filename,
                                            "Mesh files (*.mgxm);;All files (*.*)", 0, FileDialogOptions);
    if(!filename.isEmpty()) {
      if(!QFile::exists(filename)) {
        QMessageBox::information(dlg, "Error selecting file",
                                 "You must select an existing file. Your selection is ignored.");
        return;
      }
      setMeshFile(filename);
    }
  }

  void MeshLoad::setMeshFile(const QString& filename) {
    ui->MeshFile->setText(filename);
  }

  bool MeshLoad::run()
  {
    int meshId = parm("Stack Number").toInt();
    if(!checkState().mesh(MESH_ANY, meshId))
      return false;
    Mesh* mesh = this->mesh(meshId);
    bool transform = stringToBool(parm("Transform"));
    bool add = stringToBool(parm("Add"));
    bool res = run(mesh, parm("Filename"), transform, add);
    if(res) {
      // Show mesh if nothing visible
      if(!(mesh->showMesh() or mesh->showSurface())) {
        mesh->setShowSurface();
        mesh->setShowMesh();
      }
      if(mesh->hasImgTex())
        mesh->setShowVertex("Image Texture");
      else
        mesh->setShowMesh();
      setNormals(mesh->graph());
    }
    return res;
  }

  bool MeshLoad::loadMGXM_2_1(QIODevice& file,
                              const QList<int> &version, Mesh *mesh, bool& scale, bool& transform)
  {
    scale = true;
    return mesh->read(file, transform);
  }

  bool MeshLoad::loadMGXM_2_0(QIODevice& file,
                              const QList<int> &version, Mesh *mesh, bool& scale, bool& transform)
  {
    // Set the scale
    scale = true;

    // Read in the mesh.
    return mesh->read(file, transform);
  }

  bool MeshLoad::loadMGXM_1_3(QIODevice& file, Mesh* mesh, bool& scale, bool& transform)
  {
    return loadMGXM_1_2(file, mesh, scale, transform, false);
  }

  bool MeshLoad::loadMGXM_1_2(QIODevice& file, Mesh* mesh, bool& scale, bool& transform, bool has_color)
  {
    bool isCells;
    file.read((char*)&isCells, sizeof(bool));
    mesh->setMeshType(isCells ? "MGXC" : "MGXM");
    return loadMGXM_1_1(file, mesh, scale, transform, has_color);
  }

  bool MeshLoad::loadMGXM_1_1(QIODevice& file, Mesh* mesh, bool& scale, bool& transform, bool has_color)
  {
    file.read((char*)&scale, sizeof(bool));
    file.read((char*)&transform, sizeof(bool));
    Point2f signalBounds;
    file.read((char*)&signalBounds[0], sizeof(float));
    file.read((char*)&signalBounds[1], sizeof(float));
    if(signalBounds[0] == signalBounds[1]) {
      signalBounds[0] = 0;
      signalBounds[1] = 65535;
    }
    mesh->signalBounds() = signalBounds;
    uint stringSize;
    file.read((char*)&stringSize, sizeof(uint));
    QByteArray string(stringSize, 0);
    file.read(string.data(), stringSize);
    mesh->signalUnit() = QString::fromUtf8(string);
    bool res = loadMGXM_0(file, mesh, scale, transform, has_color);
    if(res and has_color) {
      float deltaSignal = signalBounds[1] - signalBounds[0];
      forall(const vertex& v, mesh->graph())
        v->signal = signalBounds[0] + v->signal * deltaSignal;
    }
    return res;
  }

  bool MeshLoad::loadMGXM_1_0(QIODevice& file, Mesh* mesh, bool& scale, bool& transform, bool )
  {
    file.read((char*)&scale, sizeof(bool));
    file.read((char*)&transform, sizeof(bool));
    return loadMGXM_0(file, mesh, scale, transform);
  }

  bool MeshLoad::loadMGXM_0(QIODevice& file, Mesh* mesh, bool scale, bool transform, bool has_color)
  {
    vvGraph& S = mesh->graph();
    IntVtxMap vMap;

    uint hSz, vCnt, vSz, eSz;
    file.read((char*)&hSz, 4);
    file.read((char*)&vCnt, 4);
    file.read((char*)&vSz, 4);
    file.read((char*)&eSz, 4);
    bool has_label = vSz >= 4;
    if(has_label)
      vSz -= 4;
    if(has_color) {
      has_color = vSz >= 4;
      if(has_color)
        vSz -= 4;
    }
    bool has_signal = vSz >= 4;
    if(has_signal)
      vSz -= 4;
    bool has_type = vSz >= 1;
    if(has_type)
      vSz -= 1;
    bool has_selected = vSz >= 1;
    if(has_selected)
      vSz -= 1;

    if(hSz > 0) {
      std::vector<char> t(hSz);
      file.read(&t[0], hSz);
    }
    float tmp;

    progressStart(QString("Loading Mesh %1").arg(mesh->userId()), vCnt * 2);
    S.reserve(vCnt);
    for(uint i = 0; i < vCnt; i++) {
      if(file.atEnd())
        throw(QString("Premature end of file reading vertices"));
      vertex v;
      S.insert(v);
      file.read((char*)&v->saveId, 4);
      Point3f pos;
      file.read((char*)&pos, 12);
      v->pos = realPos(Point3d(pos), scale, transform, mesh);
      if(has_label)
        file.read((char*)&v->label, 4);
      if(has_color or has_signal)
        file.read((char*)&v->signal, 4);
      if(has_color and has_signal)
        file.read((char*)&tmp, 4);
      if(has_type)
        file.read((char*)&v->type, 1);
      if(has_selected)
        file.read((char*)&v->selected, 1);
      if(vSz > 0) {
        std::vector<char> t(vSz);
        file.read(&t[0], vSz);
      }
      vMap[v->saveId] = v;
      if(v->label > mesh->viewLabel())
        mesh->setLabel(v->label);
      if(!progressAdvance(i))
        userCancel();
    }
    for(uint i = 0; i < vCnt; i++) {
      if(file.atEnd())
        throw(QString("Premature end of file reading neighborhoods"));
      uint vId, nCnt;
      file.read((char*)&vId, 4);
      file.read((char*)&nCnt, 4);
      if(eSz > 0) {
        std::vector<char> t(eSz);
        file.read(&t[0], eSz);
      }
      IntVtxMap::const_iterator vIt = vMap.find(vId);
      if(vIt == vMap.end())
        throw QString("Invalid vertex id: %1").arg(vId);
      vertex v = vIt->second;
      vertex pn(0);
      for(uint j = 0; j < nCnt; j++) {
        uint nId;
        file.read((char*)&nId, 4);
        IntVtxMap::const_iterator nIt = vMap.find(nId);
        if(vIt == vMap.end())
          throw QString("Invalid vertex id: %1").arg(nId);
        vertex n = nIt->second;
        if(S.valence(v) == 0)
          S.insertEdge(v, n);
        else
          S.spliceAfter(v, pn, n);
        pn = n;
      }
      if(!progressAdvance(vCnt + i))
        userCancel();
    }
    if(!progressAdvance(2 * vCnt))
      userCancel();
    return true;
  }

  namespace
  {
    // Returns true if the mesh is a cell mesh
    bool checkCells(Mesh* mesh)
    {
      const vvGraph& S = mesh->graph();
      forall(const vertex& v, S) {
        if(v->type == 'j') {
          char prev = S.prevTo(v, S.anyIn(v))->type;
          bool has_cell = false;
          forall(const vertex& n, S.neighbors(v)) {
            if(n->type == 'c') {
              if(prev == 'c')
                return false;
              has_cell = true;
            }
            prev = n->type;
          }
          if(not has_cell)
            return false;
        } else if(v->type == 'c') {
          forall(const vertex& n, S.neighbors(v))
            if(n->type != 'j')
              return false;
        } else
          return false;
      }
      return true;
    }
  } // namespace

  void MeshLoad::findSignalBounds(Mesh* mesh)
  {
    vvGraph& S = mesh->graph();
    float minSignal = FLT_MAX, maxSignal = -FLT_MAX;
    forall(const vertex& v, S) {
      float s = v->signal;
      if(s > maxSignal)
        maxSignal = s;
      if(s < minSignal)
        minSignal = s;
    }
    if(minSignal == maxSignal)
      minSignal -= 1;
    mesh->signalBounds() = Point2f(minSignal, maxSignal);
  }

  bool MeshLoad::run(Mesh* mesh, QString filename, bool transform, bool add)
  {
    QFile file(filename);
    if(!file.open(QIODevice::ReadOnly)) {
      setErrorMessage(QString("loadMesh::Error:Cannot open input file: %1").arg(filename));
      return false;
    }
    char magic[9];
    char magic_check[9] = "MGX MESH";
    file.read(magic, 8);
    magic[8] = 0;

    bool scale;

    // If we are not adding to it, reset the mesh
    // RSS This is now broken, how to fix?
    //if(!add)
      mesh->reset();

    QList<int> version;
    int major = 0, minor = 0;
    if(strcmp(magic, magic_check) == 0) {
      scale = false;
      if(!loadMGXM_0(file, mesh, scale, transform))
        return false;
      findSignalBounds(mesh);
    } else {
      file.seek(0);
      file.read(magic, 5);
      if(magic[4] == ' ')
        magic[4] = 0;
      if(strcmp(magic, "MGXM") == 0) {
        version = extractVersion(file);
        if(version.size() != 2) {
          QStringList str;
          foreach(int i, version)
          str << QString::number(i);
          throw QString("MeshLoad::Error:Invalid file version format (%1), expected: MAJOR.MINOR")
                .arg(str.join("."));
        }
        bool success = false;
        major = version[0];
        minor = version[1];
        if(major == 1 and minor == 0) {
          success = loadMGXM_1_0(file, mesh, scale, transform);
          mesh->setMeshType(checkCells(mesh) ? "MGXC" : "MGXM");
          findSignalBounds(mesh);
        } else if(major == 1 and minor == 1) {
          success = loadMGXM_1_1(file, mesh, scale, transform);
          mesh->setMeshType(checkCells(mesh) ? "MGXC" : "MGXM");
        } else if(major == 1 and minor == 2) {
          success = loadMGXM_1_2(file, mesh, scale, transform);
        } else if(major == 1 and minor == 3) {
          success = loadMGXM_1_3(file, mesh, scale, transform);
        } else if(major == 2 and minor == 0) {
          file.seek(0);
          success = loadMGXM_2_0(file, version, mesh, scale, transform);
        } else if(major == 2 and minor == 1) {
          file.seek(0);
          success = loadMGXM_2_1(file, version, mesh, scale, transform);
        } else {
           throw QString( "MeshLoad::Error:Unknown file version %1.%2, please upgrade "
             "to a newer version of MorphGraphX").arg(version[0]).arg(version[1]);
        }
        if(!success)
          return false;
      } else {
        setErrorMessage(QString("The file '%1' is not a mgx mesh file").arg(filename));
        return false;
      }
    }
    file.close();
    actingFile(filename);
    mesh->setFile(filename);
    if(major < 2)
      mesh->clearImgTex();
    if(mesh->stack()->empty())
      mesh->setScaled(scale);
    else
      mesh->setScaled(true);
    mesh->setTransformed(transform);
    mesh->updateAll();
    // SETSTATUS("Loaded mesh, file:" << mesh->file() << ", vertices:" << mesh->graph().size());
    setStatus(QString("Loaded mesh, file: %1, vertices: %2").arg(mesh->file()).arg(mesh->graph().size()));
    return true;
  }

  bool LoadAllData::run()
  {
    QStringList errors;
    for(int i = 0; i < stackCount(); ++i) {
      Stack* s = stack(i);
      Store* main = s->main();
      QString mainFilename = main->file();
      Store* work = s->work();
      QString workFilename = work->file();
      try {
        main->setFile(mainFilename);
        if(!loadStore(s, main, errors))
          main->reset();
      }
      catch(UserCancelException&) {
        main->reset();
        return true;
      }
      try {
        work->setFile(workFilename);
        if(!loadStore(s, work, errors))
          work->reset();
      }
      catch(UserCancelException&) {
        work->reset();
        return true;
      }
      if(main->file().isEmpty() and work->file().isEmpty()) {
        s->setSize(Point3u(0, 0, 0));
        main->reset();
        work->reset();
      }

      if(s->empty()) {
        s->main()->hide();
        s->work()->hide();
      }
    }
    MeshLoad loadMesh(*this);
    MeshImport importMesh(*this);
    for(int i = 0; i < meshCount(); ++i) {
      Mesh* m = mesh(i);
      if(!m->file().isEmpty()) {
        try {
          QString low_fn = m->file().toLower();
          if(low_fn.endsWith(".mgxm", Qt::CaseInsensitive)) {
            if(!loadMesh.run(m, m->file(), m->transformed(), false)) {
              m->reset();
              errors << loadMesh.errorMessage();
            }
          } else if(!importMesh.run(m, m->file(), "", m->scaled(), m->transformed(), false)) {
            m->reset();
            errors << importMesh.errorMessage();
          }
        }
        catch(UserCancelException&) {
          m->graph().clear();
          return true;
        }
      }

      if(m->graph().empty()) {
        m->setShowSurface(false);
        m->setShowMesh(false);
      } else if(m->showHeat())
        m->setShowVertex("Signal");
    }

    if(!errors.empty())
      setWarningMessage(QString("Warnings:\n  *%1").arg(errors.join("\n  *")));

    return true;
  }

  bool LoadAllData::loadStore(Stack* stack, Store* store, QStringList& errors)
  {
    if(!store->file().isEmpty()) {
      if(store->file().endsWith(".txt", Qt::CaseInsensitive)) {
        StackImport importStack(*this);
        Point3f step;
        float brightness = 1.f;
        if(!importStack.run(stack, store, step, brightness, store->file())) {
          errors << importStack.errorMessage();
          return false;
        }
      } else {
        StackOpen openStack(*this);
        if(!openStack.run(stack, store, store->file())) {
          errors << openStack.errorMessage();
          return false;
        }
      }
    } else
      store->reset();
    return true;
  }

  bool LoadViewFile::run()
  {
    return run(parm("Filename"));
  }

  bool LoadViewFile::run(QString fileName)
  {
    actingFile(fileName, true);
    bool res = loadView(fileName);
    if(!res) {
      setErrorMessage("Error while loading view file.");
      return false;
    }
    LoadAllData loaddata(*this);
    res = loaddata.run();
    if(!res)
      setErrorMessage(loaddata.errorMessage());
    else if(fileName.size() > 0)
      setStatus(QString("Loaded view file: %1.").arg(fileName));

    return res;
  }

  bool LoadViewFile::initialize(QWidget* parent)
  {
    QString filename = parm("Filename");

    // Get the file name
    if(parent) {
      filename = QFileDialog::getOpenFileName(parent, QString("Select view file"), filename,
                       "MorphoGraphX View files (*.mgxv);;All files (*.*)", 0, FileDialogOptions);

      if(filename.isEmpty())
        return false;

      setParm("Filename", filename);
    }
    return true;
  }

  bool ResetMeshProcess::run()
  {
    int meshId = parm("Mesh").toInt();
    if(!checkState().mesh(MESH_ANY, meshId))
      return false;
    return run(this->mesh(meshId));
  }

  bool ResetMeshProcess::run(Mesh* m)
  {
    m->reset();
    m->updateAll();
    return true;
  }

  bool SaveParents::initialize(QWidget* parent)
  {
    QString filename(parm("Filename"));
    if(parent)
      filename = QFileDialog::getSaveFileName(parent, "Select file to save labels in",
                                              parm("Filename"), "CSV files (*.csv)");
    // check filename
    if(filename.isEmpty())
      return false;
    // check ending, add suffix if required
    QString suffix = ".csv";
    if(filename.right(suffix.size()) != suffix)
      filename.append(suffix);

    setParm("Filename",filename);
    return true;
  }

  bool SaveParents::run(Mesh* mesh, const QString& filename, bool saveOnlyExisting)
  {
    const IntIntAttr& parentLabel = mesh->parents();
    if(parentLabel.size() == 0) {
      setErrorMessage(QString("There are no parent labels").arg(filename));
      return false;
    }

    // Open output file
    QFile file(filename);
    if(!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
      setErrorMessage(QString("File '%1' cannot be opened for writing").arg(filename));
      return false;
    }
    QTextStream out(&file);
    // Write header
    out << "Label, Parent Label" << endl;

    std::set<int> allExistingLabels = findAllLabelsSet(mesh->graph());

    // Write data
    forall(const IntIntPair& p, parentLabel) {
      int label = p.first;
      if(saveOnlyExisting and allExistingLabels.find(label) == allExistingLabels.end()) continue;
      int parent = p.second;
      out << label << "," << parent << endl;
    }
    file.close();

    SETSTATUS("Storing " << parentLabel.size() << " parent labels to file " << filename);

    return true;
  }

  bool LoadParents::initialize(QWidget* parent)
  {
    QString fileName(parm("Filename"));
    if(parent)
      fileName = QFileDialog::getOpenFileName(parent, "Choose parent label file to load",
                                              parm("Filename"), "Parent files (*.csv *.txt)");

    // check file
    if(fileName.isEmpty())
      throw(QString("File name is empty"));

    // Get the type
    QString fileType = fileName.split(".").last().toUpper();
    if(fileType != "CSV" and fileType != "TXT")
      throw(QString("Invalid file type '%1', must be .csv(label, parent) or .txt(parent: label, label ...)").arg(fileType));

    setParm("Filename",fileName);
    setParm("FileType",fileType);

    return true;
  }

  bool LoadParents::run(Mesh* mesh, const QString &fileName, const QString &fileType, bool keep)
  {
    QFile file(fileName);
    if(!file.open(QIODevice::ReadOnly))
      throw(QString("File '%1' cannot be opened for reading").arg(fileName));

    // Set up the attributes
    IntIntAttr parentLabel;
    IntFloatAttr labelHeat;
    if(keep) {
      parentLabel = mesh->parents();
      labelHeat = mesh->labelHeat();
    }

    // Read the file
    QTextStream ss(&file);
    if(fileType == "CSV") {
      QString line = ss.readLine();
      QStringList fields = line.split(",");
      if(fields.size() != 2)
        throw(QString("File '%1' should be in the format 'label, parent'").arg(fileName));

      int lineNum = 1;
      while(ss.status() == QTextStream::Ok) {
        ++lineNum;

        // Get the line and split it
        fields = ss.readLine().split(",");
        if(fields.size() != 2)
          break;

        // Get the data
        bool ok;
        int label = fields[0].toInt(&ok);
        if(!ok)
          throw(QString("Error line %1: the first column is not an integer number but '%2'").arg(fileName).arg(lineNum));

        int parent = fields[1].toInt(&ok);
        if(!ok)
          throw(QString("Error line %1: the second column is not an integer number but '%2'").arg(fileName).arg(lineNum));

        parentLabel[label] = parent;
        labelHeat[label] = 1;
      }
    } else if(fileType == "TXT") {

      // No header in MARS-ALT format
      int lineNum = 1;
      while(ss.status() == QTextStream::Ok) {
        ++lineNum;

        // Get the line and split it
        QString line = ss.readLine();
        QStringList fields = line.split(":");
        if(fields.size() != 2)
          break; // End of file?

        // Get the data
        bool ok;
        int parent = fields[0].toInt(&ok);
        if(!ok)
          throw(QString("File %1, line %2: should be in the format 'parent: label, label ...'").arg(fileName).arg(lineNum));

        QStringList labels = fields[1].split(",");
        for(int i = 0; i < labels.size(); i++) {
          int label = labels[i].toInt(&ok);
          if(!ok)
            throw(QString("File %1, line %2, label %3: should be in the format 'parent: label, label ...'")
                .arg(fileName).arg(lineNum).arg(i));

          if(label == 0 or parent == 0)
            continue;
          parentLabel[label] = parent;
          labelHeat[label] = 1;
        }
      }
    } else
      throw(QString("Invalid file type %1").arg(fileType));

    // clear tables related to parents
    mesh->parentCenter().clear();
    mesh->parentCenterVis().clear();
    mesh->parentNormal().clear();
    mesh->parentNormalVis().clear();

    // update the parent map
    mesh->parents() = parentLabel;
    mesh->labelHeat() = labelHeat;
    mesh->heatMapUnit() = "ON/OFF";
    mesh->heatMapBounds() = Point2f(0, 1);
    mesh->updateTriangles();

    SETSTATUS(parentLabel.size() << " parent labels loaded from file " << fileName);

    return true;
  }

  bool ResetParents::run(Mesh* mesh)
  {
    mesh->parents().clear();
    mesh->parentCenter().clear();
    mesh->parentCenterVis().clear();
    mesh->parentNormal().clear();
    mesh->parentNormalVis().clear();
    mesh->labelHeat().clear();
    mesh->updateTriangles();
    return true;
  }
}
