Isis 3 Programmer Reference
ImportImagesWorkOrder.cpp
Go to the documentation of this file.
1 
23 #include "ImportImagesWorkOrder.h"
24 
25 #include <QDebug>
26 #include <QFileDialog>
27 #include <QMessageBox>
28 #include <QtConcurrentMap>
29 
30 #include "Camera.h"
31 #include "Cube.h"
32 #include "CubeAttribute.h"
33 #include "FileName.h"
34 #include "Project.h"
35 #include "ProjectItem.h"
36 #include "ProjectItemModel.h"
37 #include "SaveProjectWorkOrder.h"
38 #include "Target.h"
39 #include "TextFile.h"
40 
41 namespace Isis {
42 
49  WorkOrder(project) {
50  // This is an asynchronous work order
51  m_isSynchronous = false;
52  m_newImages = NULL;
53  m_list = NULL;
54 
55  QAction::setText(tr("Import &Images..."));
56  QUndoCommand::setText(tr("Import Images"));
58  }
59 
60 
67  WorkOrder(other) {
68  m_newImages = NULL;
69  m_list = other.m_list;
70  }
71 
72 
79  delete m_newImages;
80  m_newImages = NULL;
81 
82  m_list = NULL;
83  }
84 
85 
94  return new ImportImagesWorkOrder(*this);
95  }
96 
97 
108 
109  if (item) {
110  return (item->text() == "Images");
111  }
112 
113  return false;
114  }
115 
116 
133  try {
135 
136  QStringList fileNames = QFileDialog::getOpenFileNames(
137  qobject_cast<QWidget *>(parent()),
138  tr("Import Images"), "",
139  tr("Isis cubes and list files (*.cub *.lis);;All Files (*)"));
140 
141  QStringList* stateToSave = new QStringList();
142 
143  if (!fileNames.isEmpty()) {
144  foreach (FileName fileName, fileNames) {
145  if (fileName.extension() == "lis") {
146  TextFile listFile(fileName.expanded());
147  QString path = fileName.path();
148  QString lineOfListFile;
149 
150  while (listFile.GetLine(lineOfListFile)) {
151  FileName relFileName(path + "/" + lineOfListFile);
152  if (relFileName.fileExists() ) {
153  stateToSave->append(path + "/" + lineOfListFile);
154  }
155  else {
156  FileName absFileName(lineOfListFile);
157  if ( absFileName.fileExists() && lineOfListFile.startsWith("/") ) {
158  stateToSave->append(lineOfListFile);
159  }
160 
161  else {
162  project()->warn("File " + lineOfListFile + " not found");
163  }
164  }
165  }
166  }
167  else {
168  stateToSave->append(fileName.original());
169  }
170  }
171 
172  QMessageBox::StandardButton saveProjectAnswer = QMessageBox::No;
173  if (stateToSave->count() >= 100 && project()->isTemporaryProject()) {
174  saveProjectAnswer = QMessageBox::question(qobject_cast<QWidget *>(parent()),
175  tr("Save Project Before Importing Images"),
176  tr("Would you like to save your project <b>before</b> importing images? It can be "
177  "slow to save your project after these images have been loaded if you do not "
178  "save now. <br><br>IMPORTANT: WHEN IMPORTING LARGE DATA SETS, SAVING YOUR "
179  "PROJECT BEFORE IMPORTING IS HIGHLY RECOMMENDED."),
180  QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
181  QMessageBox::Yes);
182  }
183 
184  if (saveProjectAnswer == QMessageBox::Yes) {
185  SaveProjectWorkOrder saveWorkOrder(project());
186  saveWorkOrder.trigger();
187  }
188 
189  QMessageBox::StandardButton copyImagesAnswer = QMessageBox::No;
190  if (!stateToSave->isEmpty() && saveProjectAnswer != QMessageBox::Cancel) {
191  copyImagesAnswer = QMessageBox::question(qobject_cast<QWidget *>(parent()),
192  tr("Copy Images into Project"),
193  tr("Should images (DN data) be copied into project?"),
194  QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
195  QMessageBox::No);
196  }
197 
198  bool copyDnData = (copyImagesAnswer == QMessageBox::Yes);
199 
200  stateToSave->prepend(copyDnData? "copy" : "nocopy");
201 
202  if (fileNames.count() > 1) {
203  QUndoCommand::setText(tr("Import %1 Images").arg(stateToSave->count() - 1));
204  }
205  else if (fileNames.count() == 1 && stateToSave->count() > 2) {
206  QUndoCommand::setText(tr("Import %1 Images from %2").arg(
207  QString::number(stateToSave->count() - 1), fileNames.first()));
208  }
209  else {
210  QUndoCommand::setText(tr("Import %1").arg(fileNames.first()));
211  }
212 
213  // The internal data will look like: [ copy|nocopy, img1, img2, ... ]
214  setInternalData(*stateToSave);
215 
216  bool doImport = stateToSave->count() > 1 && saveProjectAnswer != QMessageBox::Cancel &&
217  copyImagesAnswer != QMessageBox::Cancel;
218 
219  return doImport;
220  }
221 
222  }
223  catch (IException &e) {
224  QMessageBox::critical(NULL, tr("Error"), tr(e.what()));
225  }
226  return false;
227 
228  }
229 
230 
241  if (m_list && project()->images().size() > 0 ) {
243  // Remove the images from disk.
245  // Remove the images from the model, which updates the tree view.
246  ProjectItem *currentItem =
247  project()->directory()->model()->findItemData( QVariant::fromValue(m_list) );
248  project()->directory()->model()->removeItem(currentItem);
249  }
250  }
251 
252 
263  if (m_list && project()->images().size() > 0 ) {
264  foreach (Image *image, *m_list) {
265  delete image;
266  }
267  delete m_list;
268  }
269  }
270 
271 
283  try {
284  QObject tmpObj;
285  if (internalData().count() > 0) {
286  // Recall in setupExecution() that first element in internal data is copy|nocopy,
287  // and rest of elements are the expanded names of images to import.
288  importConfirmedImages(internalData().mid(1), (internalData()[0] == "copy"));
289  project()->setClean(false);
290  }
291  }
292  catch (IException &e) {
293  QMessageBox::critical(NULL, tr("Error"), tr(e.what()));
294  }
295  }
296 
297 
309  try {
310  if (!m_newImages->isEmpty()) {
312  m_list = project()->images().last();
313 
314  delete m_newImages;
315  m_newImages = NULL;
316  }
317  }
318  catch (IException &e) {
319  m_status = WorkOrderFinished;
320  m_warning.append(e.what());
321  }
322  if (m_warning != "") {
323  project()->warn(m_warning);
324  }
325  }
326 
327 
339  QThread *guiThread, QDir destinationFolder, bool copyDnData) : m_errors(new IException),
340  m_numErrors(new int(0)) {
341  m_destinationFolder = destinationFolder;
342  m_copyDnData = copyDnData;
343  m_guiThread = guiThread;
344  }
345 
346 
353  const OriginalFileToProjectCubeFunctor &other) : m_errors(other.m_errors),
354  m_numErrors(other.m_numErrors) {
356  m_copyDnData = other.m_copyDnData;
357  m_guiThread = other.m_guiThread;
358  }
359 
360 
367  m_destinationFolder = QDir();
368  m_copyDnData = false;
369  m_guiThread = NULL;
370  }
371 
372 
388  const FileName &original) {
389  Cube *result = NULL;
390 
391  // As long as we haven't encountered 20 errors related to importing images, we can continue
392  // to import images.
393  if (*m_numErrors < 20) {
394  try {
395  QString destination = QFileInfo(m_destinationFolder, original.name())
396  .absoluteFilePath();
397  Cube *input = new Cube(original, "r");
398 
399  if (m_copyDnData) {
400  Cube *copiedCube = input->copy(destination, CubeAttributeOutput());
401  delete input;
402  input = copiedCube;
403  }
404 
405  FileName externalLabelFile(destination);
406  externalLabelFile = externalLabelFile.setExtension("ecub");
407 
408  Cube *projectImage = input->copy(externalLabelFile, CubeAttributeOutput("+External"));
409 
410  if (m_copyDnData) {
411  // Make sure the external label has a fully relative path to the DN data
412  projectImage->relocateDnData(FileName(destination).name());
413  }
414 
415  // Set new ecub to readOnly. When closing cube, the labels were being re-written because
416  // the cube was read/write. This caused a segfault when imported large number of images
417  // because of a label template file being opened too many times.
418  projectImage->reopen();
419 
420  delete input;
421 
422  result = projectImage;
423  }
424  // When we encounter an exception, update the m_errors and m_numErrors to with the exception
425  // that occurred.
426  catch (IException &e) {
427  m_errorsLock.lock();
428 
429  m_errors->append(e);
430  (*m_numErrors)++;
431 
432  m_errorsLock.unlock();
433  }
434  }
435 
436  return result;
437  }
438 
439 
450  IException result;
451 
452  result.append(*m_errors);
453 
454  if (*m_numErrors >= 20) {
455  result.append(
457  tr("Aborted import images due to a high number of errors"),
458  _FILEINFO_));
459  }
460  return result;
461  }
462 
463 
482  void ImportImagesWorkOrder::importConfirmedImages(QStringList confirmedImages, bool copyDnData) {
483  try {
484  if (!confirmedImages.isEmpty()) {
485  QDir folder = project()->addImageFolder("import");
486 
487  setProgressRange(0, confirmedImages.count());
488 
489  // We are creating a new QObject within an asynchronous execute(), which means that this
490  // variable, m_newImages, has thread affinity with a thread in the gloabal thread pool
491  // (i.e. m_newImages lives in a thread in the global thread pool).
492  // see WorkOrder::redo().
493  m_newImages = new ImageList;
494  m_newImages->reserve(confirmedImages.count());
495 
496  QStringList confirmedImagesFileNames;
497  QStringList confirmedImagesIds;
498 
499  foreach (QString confirmedImage, confirmedImages) {
500  QStringList fileNameAndId = confirmedImage.split(",");
501  confirmedImagesFileNames.append(fileNameAndId.first());
502 
503  // Determine if there was already a unique id provided for the file.
504  if (fileNameAndId.count() == 2) {
505  confirmedImagesIds.append(fileNameAndId.last());
506  }
507  else {
508  confirmedImagesIds.append(QString());
509  }
510  }
511 
512  OriginalFileToProjectCubeFunctor functor(thread(), folder, copyDnData);
513  // Start concurrently copying the images to import.
514  QFuture<Cube *> future = QtConcurrent::mapped(confirmedImagesFileNames, functor);
515 
516  // The new internal data will store the copied files as well as their associated unique id's.
517  QStringList newInternalData;
518  newInternalData.append(internalData().first());
519 
520  // By releasing a thread from the global thread pool, we are effectively temporarily
521  // increasing the max number of available threads. This is useful when a thread goes to sleep
522  // waiting for more work, so we can allow other threads to continue.
523  // See Qt's QThreadPool::releaseThread() documentation.
524  QThreadPool::globalInstance()->releaseThread();
525  for (int i = 0; i < confirmedImages.count(); i++) {
526  setProgressValue(i);
527 
528  // This will wait for the result at i to finish (the functor invocation finishes) and
529  // get the cube.
530  Cube *cube = future.resultAt(i);
531 
532  if (cube) {
533 
534  // Confirm that the target body and the gui camera do not exist before creating and
535  // and adding them for each image. Since a target may be covered by many cameras and a
536  // camera may cover many targets, have to get tricky with the checking.
537  QString instrumentId = cube->label()->findGroup("Instrument",
538  PvlObject::FindOptions::Traverse).findKeyword("InstrumentId")[0];
539  QString targetName = cube->label()->findGroup("Instrument",
540  PvlObject::FindOptions::Traverse).findKeyword("TargetName")[0];
541  if (!project()->hasTarget(targetName)) {
542  Camera *camera = cube->camera();
543  Target *target = camera->target();
544  project()->addTarget(target);
545 
546  if (!project()->hasCamera(instrumentId)) {
547  project()->addCamera(camera);
548  }
549  }
550  else if (!project()->hasCamera(instrumentId)) {
551  Camera *camera = cube->camera();
552  project()->addCamera(camera);
553  }
554 
555  // Create a new image from the result in the thread spawned in WorkOrder::redo().
556  Image *newImage = new Image(cube);
557  newImage->closeCube();
558  // Memory for cube is deleted in Image::closeCube()
559  cube = NULL;
560 
561  // Either use a unique id that was already provided or create one for the new image.
562  if (confirmedImagesIds[i].isEmpty()) {
563  confirmedImagesIds[i] = newImage->id();
564  }
565  else {
566  newImage->setId(confirmedImagesIds[i]);
567  }
568 
569  QStringList imageInternalData;
570  imageInternalData.append(confirmedImagesFileNames[i]);
571  imageInternalData.append(confirmedImagesIds[i]);
572 
573  newInternalData.append(imageInternalData.join(","));
574 
575  m_newImages->append(newImage);
576 
577  // Move the new image back and its display properities to the GUI thread.
578  // Note: thread() returns the GUI thread because this ImportImagesWorkOrder lives
579  // (was created) in the GUI thread.
580  newImage->moveToThread(thread());
581  newImage->displayProperties()->moveToThread(thread());
582  }
583  }
584  // Since we temporarily increased the max thread count (by releasing a thread), make sure
585  // to re-reserve the thread for the global thread pool's accounting.
586  // See Qt's QThreadPool::reserveThread().
587  QThreadPool::globalInstance()->reserveThread();
588 
589  m_warning = functor.errors().toString();
590 
591  // Recall that m_newImages has thread affinity with a thread in the global thread pool.
592  // Move it to the GUI-thread because these threads in the pool do not run in an event loop,
593  // so they cannot process events.
594  // See https://doc.qt.io/qt-5/threads-qobject.html#per-thread-event-loop
595  // See http://doc.qt.io/qt-5/threads-technologies.html#comparison-of-solutions
596  m_newImages->moveToThread(thread());
597 
598  if (m_newImages->isEmpty()) {
599  folder.removeRecursively();
600  }
601 
602  setInternalData(newInternalData);
603  }
604  }
605  catch (IException &e) {
606  QMessageBox::critical(NULL, tr("Error"), tr(e.what()));
607  }
608  }
609 }
void waitForImageReaderFinished()
Locks program if another spot in code is still running and called this function.
Definition: Project.cpp:1732
QString path() const
Returns the path of the file name.
Definition: FileName.cpp:119
const char * what() const
Returns a string representation of this exception in its current state.
Definition: IException.cpp:391
bool m_copyDnData
Indicates whether the cube data will be copied to the project.
Cube * operator()(const FileName &original)
Overloads the callable operator to invoke this functor.
QDir addImageFolder(QString prefix)
Create and return the name of a folder for placing images.
Definition: Project.cpp:1003
$Date$ $Revision$
void setProgressValue(int)
Sets the current progress value for the WorkOrder.
Definition: WorkOrder.cpp:1382
Internalizes a list of images and allows for operations on the entire list.
Definition: ImageList.h:55
The main project for ipce.
Definition: Project.h:289
IException errors() const
Indicates if any errors occurred during the import.
PvlGroupIterator findGroup(const QString &name, PvlGroupIterator beg, PvlGroupIterator end)
Find a group with the specified name, within these indexes.
Definition: PvlObject.h:141
File name manipulation and expansion.
Definition: FileName.h:116
Camera * camera()
Return a camera associated with the cube.
Definition: Cube.cpp:1166
void addCamera(Camera *camera)
Adds a new camera to the project.
Definition: Project.cpp:2761
void deleteFromDisk(Project *project)
Delete all of the contained Images from disk.
Definition: ImageList.cpp:747
virtual bool setupExecution()
This sets up the state for the work order.
Definition: WorkOrder.cpp:1275
void addTarget(Target *target)
Adds a new target to the project.
Definition: Project.cpp:2730
Add cubes to a project.
void append(const IException &exceptionSource)
Appends the given exception (and its list of previous exceptions) to this exception&#39;s causational exc...
Definition: IException.cpp:425
QString name() const
Returns the name of the file excluding the path and the attributes in the file name.
Definition: FileName.cpp:178
bool isTemporaryProject() const
Returns if the project is a temp project or not.
Definition: Project.cpp:1567
QThread * m_guiThread
Pointer to the GUI thread. Not used?
void setId(QString id)
Override the automatically generated ID with the given ID.
Definition: Image.cpp:383
virtual void postUndoExecution()
Cleans up memory (images) after the undo execution occurs.
static QStringList images(QStringList)
Verify that the input fileNames are image files.
Definition: Project.cpp:894
void reopen(QString access="r")
This method will reopen an isis sube for reading or reading/writing.
Definition: Cube.cpp:691
ProjectItem * findItemData(const QVariant &data, int role=Qt::UserRole+1)
Returns the first item found that contains the given data in the given role or a null pointer if no i...
virtual void postExecution()
Associates the imported images to the project.
Directory * directory() const
Returns the directory associated with this Project.
Definition: Project.cpp:1229
ProjectItemModel * model()
Gets the ProjectItemModel for this directory.
Definition: Directory.cpp:1105
Provide Undo/redo abilities, serialization, and history for an operation.
Definition: WorkOrder.h:322
Target * target() const
Returns a pointer to the target object.
Definition: Spice.cpp:1290
#define _FILEINFO_
Macro for the filename and line number.
Definition: IException.h:40
Manipulate and parse attributes of output cube filenames.
OriginalFileToProjectCubeFunctor(QThread *guiThread, QDir destinationFolder, bool copyDnData)
Creates the internal functor.
A type of error that cannot be classified as any of the other error types.
Definition: IException.h:134
virtual void removeItem(ProjectItem *item)
Removes an item and its children from the model.
QString expanded() const
Returns a QString of the full file name including the file path, excluding the attributes.
Definition: FileName.cpp:212
QString original() const
Returns the full file name including the file path.
Definition: FileName.cpp:228
ImageList * m_list
List of images that was succesfully imported into project.
This represents a cube in a project-based GUI interface.
Definition: Image.h:107
bool m_isSynchronous
This is defaulted to true.
Definition: WorkOrder.h:541
Cube * copy(FileName newFile, const CubeAttributeOutput &newFileAttributes)
Copies the cube to the new fileName.
Definition: Cube.cpp:193
ImageDisplayProperties * displayProperties()
Get the display (GUI) properties (information) associated with this image.
Definition: Image.cpp:320
void append(Image *const &value)
Appends an image to the image list.
Definition: ImageList.cpp:153
void addImages(QStringList imageFiles)
Read the given cube file names as Images and add them to the project.
Definition: Project.cpp:1032
This class is used to create and store valid Isis3 targets.
Definition: Target.h:76
void setProgressRange(int, int)
Sets the progress range of the WorkOrder.
Definition: WorkOrder.cpp:1372
virtual bool isExecutable(ProjectItem *item)
This method returns true if the user clicked on a project tree node with the text "Images"...
void importConfirmedImages(QStringList confirmedImages, bool copyDnData)
Imports the images.
void setModifiesDiskState(bool changesProjectOnDisk)
Definition: WorkOrder.cpp:1688
Provides access to sequential ASCII stream I/O.
Definition: TextFile.h:54
QDir m_destinationFolder
Directory where to import the images to.
QString toString() const
Returns a string representation of this exception.
Definition: IException.cpp:553
Unless noted otherwise, the portions of Isis written by the USGS are public domain.
Pvl * label() const
Returns a pointer to the IsisLabel object associated with the cube.
Definition: Cube.cpp:1346
QString id() const
Get a unique, identifying string associated with this image.
Definition: Image.cpp:445
ImageList * m_newImages
List of images that are being imported in this work order.
Represents an item of a ProjectItemModel in Qt&#39;s model-view framework.
Definition: ProjectItem.h:146
void relocateDnData(FileName dnDataFile)
Relocates the DN data for a cube to an external cube label file.
Definition: Cube.cpp:1081
Isis exception class.
Definition: IException.h:107
FileName setExtension(const QString &extension) const
Sets all current file extensions to a new extension in the file name.
Definition: FileName.cpp:281
Namespace for ISIS/Bullet specific routines.
Definition: Apollo.h:31
QString extension() const
Returns the last extension of the file name.
Definition: FileName.cpp:194
Saves a project to disk (File->Save Project...)
This is used for work orders that will not undo or redo (See createsCleanState()) ...
Definition: WorkOrder.h:342
This copies the given cube(s) into the project.
Project * project() const
Returns the Project this WorkOrder is attached to.
Definition: WorkOrder.cpp:1314
QString m_warning
String of any errors/warnings that occurred during import.
virtual bool setupExecution()
Sets up this work order before being executed.
virtual void execute()
Executes the work order.
QStringList internalData() const
Gets the internal data for this WorkOrder.
Definition: WorkOrder.cpp:1391
bool fileExists() const
Returns true if the file exists; false otherwise.
Definition: FileName.cpp:465
void setInternalData(QStringList data)
Sets the internal data for this WorkOrder.
Definition: WorkOrder.cpp:1332
void closeCube()
Cleans up the Cube pointer.
Definition: Image.cpp:307
ImportImagesWorkOrder(Project *project)
Creates an asynchronous WorkOrder for importing images to the project.
virtual ImportImagesWorkOrder * clone() const
Creates a clone of this work order.
void setClean(bool value)
Function to change the clean state of the project.
Definition: Project.cpp:1595
IO Handler for Isis Cubes.
Definition: Cube.h:170
virtual void undoExecution()
Undoes the work order&#39;s execute.