diff --git a/CMakeLists.txt b/CMakeLists.txt index 13bf0b3e..d73ed0ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,8 +99,8 @@ SET(SNAP_VERSION_PATCH 2) SET(SNAP_VERSION_QUALIFIER "-beta") # These fields should also be modified each time -SET(SNAP_VERSION_RELEASE_DATE "20240926") -SET(SNAP_VERSION_RELEASE_DATE_FORMATTED "September 26, 2024") +SET(SNAP_VERSION_RELEASE_DATE "20241111") +SET(SNAP_VERSION_RELEASE_DATE_FORMATTED "November 11, 2024") # This field should only change when the format of the settings files changes # in a non backwards-compatible way diff --git a/Common/ITKBinaryWeightedAverage/itkBWAfilter.hxx b/Common/ITKBinaryWeightedAverage/itkBWAfilter.hxx index efa51fab..8b849ef5 100644 --- a/Common/ITKBinaryWeightedAverage/itkBWAfilter.hxx +++ b/Common/ITKBinaryWeightedAverage/itkBWAfilter.hxx @@ -179,7 +179,7 @@ void BinaryWeightedAveragingFilter< TLabelImage, TMainImage > throw itk::ExceptionObject( "Error: consecutive segmentations must be intersecting for " "Binary Weighted Averaging. Manually specifying the axis " - "of interploation may resolve this error."); + "of interpolation may resolve this error."); } //Compute the Signed Distance Map of the intersecting portion diff --git a/GUI/Qt/Components/PaintbrushToolPanel.ui b/GUI/Qt/Components/PaintbrushToolPanel.ui index d6701623..4b0a0bd5 100644 --- a/GUI/Qt/Components/PaintbrushToolPanel.ui +++ b/GUI/Qt/Components/PaintbrushToolPanel.ui @@ -206,7 +206,7 @@ font-size:11px; - <html><head/><body><p>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Brush Size (+, - keys)&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Adjust the radius of the paintbrush.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</p></body></html> + <html><head/><body><p><span style=" font-weight:600;">Brush Size (+, - keys)</span></p><p>Adjust the radius of the paintbrush.</p></body></html> diff --git a/GUI/Qt/Windows/MainImageWindow.cxx b/GUI/Qt/Windows/MainImageWindow.cxx index 83eb1df7..3acebe9a 100644 --- a/GUI/Qt/Windows/MainImageWindow.cxx +++ b/GUI/Qt/Windows/MainImageWindow.cxx @@ -1260,43 +1260,52 @@ void MainImageWindow::dragEnterEvent(QDragEnterEvent *event) void MainImageWindow::LoadDroppedFile(QString file) { - std::string filename = to_utf8(file); - // Check if the dropped file is a project - if(m_Model->GetDriver()->IsProjectFile(filename.c_str())) - { - // For the time being, the feature of opening the workspace in a new - // window is not implemented. Instead, we just prompt the user for - // unsaved changes. - if(!SaveModifiedLayersDialog::PromptForUnsavedChanges(m_Model)) - return; - - // Load the project - LoadProject(file); - } - - else + try { - if(m_Model->GetDriver()->IsMainImageLoaded()) + std::string filename = to_utf8(file); + // Check if the dropped file is a project + if(m_Model->GetDriver()->IsProjectFile(filename.c_str())) { - // check if it's a label description file - if (m_Model->GetDriver()->GetColorLabelTable()->ValidateFile(filename.c_str())) - { - m_Model->GetDriver()->LoadLabelDescriptions(filename.c_str()); + // For the time being, the feature of opening the workspace in a new + // window is not implemented. Instead, we just prompt the user for + // unsaved changes. + if(!SaveModifiedLayersDialog::PromptForUnsavedChanges(m_Model)) return; - } - - // If an image is already loaded, we show the dialog - m_DropDialog->SetDroppedFilename(file); - m_DropDialog->setModal(true); - RaiseDialog(m_DropDialog); + // Load the project + LoadProject(file); } + else { - // Otherwise, load the main image directly - m_DropDialog->InitialLoad(file); + if(m_Model->GetDriver()->IsMainImageLoaded()) + { + // check if it's a label description file + if (m_Model->GetDriver()->GetColorLabelTable()->ValidateFile(filename.c_str())) + { + m_Model->GetDriver()->LoadLabelDescriptions(filename.c_str()); + return; + } + + // If an image is already loaded, we show the dialog + m_DropDialog->SetDroppedFilename(file); + m_DropDialog->setModal(true); + + RaiseDialog(m_DropDialog); + } + else + { + // Otherwise, load the main image directly + m_DropDialog->InitialLoad(file); + } } } + catch (exception &exc) // for minor exceptions, no need to crash the entire program + { + ReportNonLethalException(this, exc, "File Dropping Error", + QString("Failed to load file %1").arg(file)); + } + } #ifdef __APPLE__ diff --git a/GUI/Qt/main.cxx b/GUI/Qt/main.cxx index 59604960..fc392ed8 100644 --- a/GUI/Qt/main.cxx +++ b/GUI/Qt/main.cxx @@ -143,10 +143,10 @@ void usage(const char *progname) cout << "Image Options:" << endl; cout << " -g FILE : Load the main image from FILE" << endl; cout << " -s FILE [FILE+] : Load the segmentation image from FILE" << endl; - cout << " : (multiple files may be provided)" << endl; + cout << " : (multiple space separated files may be provided)" << endl; cout << " -l FILE : Load label descriptions from FILE" << endl; cout << " -o FILE [FILE+] : Load additional images from FILE" << endl; - cout << " : (multiple files may be provided)" << endl; + cout << " : (multiple space separated files may be provided)" << endl; cout << " -w FILE : Load workspace from FILE" << endl; cout << " : (-w cannot be mixed with -g,-s,-l,-o options)" << endl; cout << "Additional Options:" << endl; diff --git a/Logic/Mesh/GuidedMeshIO.cxx b/Logic/Mesh/GuidedMeshIO.cxx index d365cebc..02413090 100644 --- a/Logic/Mesh/GuidedMeshIO.cxx +++ b/Logic/Mesh/GuidedMeshIO.cxx @@ -51,6 +51,9 @@ GuidedMeshIO::FileFormat GuidedMeshIO:: GetFormatByExtension(std::string extension) { + if (extension.empty()) // possible when reading dicom series + return FileFormat::FORMAT_COUNT; + // All format string in the map include '.' prefix if (extension.at(0) != '.') extension.insert(0, 1, '.'); @@ -187,9 +190,18 @@ bool GuidedMeshIO ::IsFilePolyData(const char *filename) { - auto reader = vtkNew(); - reader->SetFileName(filename); - return reader->IsFilePolyData(); + auto fmt = GetFormatByFilename(filename); + if (fmt == FORMAT_COUNT) + return false; + + AbstractMeshIODelegate *ioDelegate = AbstractMeshIODelegate::GetDelegate(fmt); + + if (!ioDelegate) + return false; + + bool ret = ioDelegate->IsFilePolyData(filename); + delete ioDelegate; + return ret; } GuidedMeshIO::FileFormat diff --git a/Logic/Mesh/MeshIODelegates.cxx b/Logic/Mesh/MeshIODelegates.cxx index 4d495845..339166e1 100644 --- a/Logic/Mesh/MeshIODelegates.cxx +++ b/Logic/Mesh/MeshIODelegates.cxx @@ -24,11 +24,28 @@ VTKMeshIODelegate::ReadPolyData(const char *filename) return polyData; } +bool +VTKMeshIODelegate::IsFilePolyData(const char *filename) +{ + // VTK file format support 5 different types of data + // So we need to use the reader to check whether the underlying data is polydata or not + vtkNew reader; + reader->SetFileName(filename); + return reader->IsFilePolyData(); +} + + VTPMeshIODelegate::VTPMeshIODelegate() { } +bool +VTPMeshIODelegate::IsFilePolyData(const char *) +{ + return true; // VTP file format is always polydata +} + vtkSmartPointer VTPMeshIODelegate::ReadPolyData(const char *filename) { diff --git a/Logic/Mesh/MeshIODelegates.h b/Logic/Mesh/MeshIODelegates.h index b7a6f380..c4625291 100644 --- a/Logic/Mesh/MeshIODelegates.h +++ b/Logic/Mesh/MeshIODelegates.h @@ -18,6 +18,8 @@ class AbstractMeshIODelegate static void GetDelegate(GuidedMeshIO::FileFormat fmt, AbstractMeshIODelegate *delegate); + virtual bool IsFilePolyData(const char *filename) = 0; + }; @@ -28,6 +30,8 @@ class VTKMeshIODelegate : public AbstractMeshIODelegate ~VTKMeshIODelegate() = default; vtkSmartPointer ReadPolyData(const char *filename) override; + + bool IsFilePolyData(const char *filename) override; }; class VTPMeshIODelegate : public AbstractMeshIODelegate @@ -37,6 +41,8 @@ class VTPMeshIODelegate : public AbstractMeshIODelegate virtual ~VTPMeshIODelegate() = default; vtkSmartPointer ReadPolyData(const char *filename) override; + + bool IsFilePolyData(const char *filename) override; }; #endif // MESHIODELEGATES_H