diff --git a/Common/ContrastXML.cxx b/Common/ContrastXML.cxx new file mode 100644 index 00000000..86a3e00e --- /dev/null +++ b/Common/ContrastXML.cxx @@ -0,0 +1,150 @@ +/*========================================================================= + + Program: ITK-SNAP + Language: C++ + + This file was created as part of the added functionality of the custom windowing. + We have added the option to set a custom contrast window preset using Contrast Menu. + + This file is mostly based on Common\Registry.cxx + +=========================================================================*/ +#include "ContrastXML.h" +#include "itkXMLFile.h" + +#include "itksys/SystemTools.hxx" +#include "IRISException.h" + +#if defined(_MSC_VER) +#pragma warning ( disable : 4786 ) +#endif +#include + +using namespace std; + + +int ContrastXMLFileReader::CanReadFile(const char* name) +{ + return GenericXMLFileReader::CanReadFile(name); +} + +std::vector> ContrastXMLFileReader::getPresetStack() const +{ + return m_PresetStack; +} + +void ContrastXMLFileReader::StartElement(const char* name, const char** atts) +{ + // If this is the root-level element , it can be ignored + if (strcmpi(name, "contrast") == 0) + { + return; + } + + // Read the attributes into a map + typedef std::map AttributeMap; + typedef AttributeMap::const_iterator AttrIter; + AttributeMap attrmap; + + for (int i = 0; atts[i] != nullptr && atts[i + 1] != nullptr; i += 2) + attrmap[itksys::SystemTools::LowerCase(atts[i])] = atts[i + 1]; + + // Process tags + if (strcmpi(name, "preset") == 0) + { + const AttrIter itKey = attrmap.find("key"); + if (itKey == attrmap.end()) + throw IRISException("Missing 'key' attribute to element"); + + const auto preset_name = itKey->second; + + // Create a new preset and place it on the stack + m_PresetStack.push_back(std::make_shared(PresetStruct{preset_name})); + } + else if (strcmpi(name, "contrastProperty") == 0) + { + const AttrIter itKey = attrmap.find("key"); + if (itKey == attrmap.end()) + throw IRISException("Missing 'key' attribute to element"); + + const AttrIter itValue = attrmap.find("value"); + if (itValue == attrmap.end()) + throw IRISException("Missing 'value' attribute to element"); + + const auto property_name = itKey->second; + const auto property_value = itValue->second; + + if(property_name == "Window") + { + m_PresetStack.back()->window = stod(property_value); + } + if(property_name == "Level") + { + m_PresetStack.back()->level = stod(property_value); + } + } + else + throw IRISException("Unknown XML element <%s>", name); +} + +void ContrastXMLFileReader::EndElement(const char* name) +{ +} + +void ContrastXMLFileReader::CharacterDataHandler(const char* inData, int inLength) +{ +} + + +void +Contrast +::Clear() +{ + m_PresetMap.clear(); +} + +std::vector +Contrast::GetPresetNames() +{ + // get all keys from m_PresetMap and sort if needed + std::vector presets; + for (const auto& map_entry : m_PresetMap) + { + presets.push_back(map_entry.first); // have to get all keys + } + + return presets; +} + +double Contrast::GetLevel(const std::string& tissue) +{ + return m_PresetMap.at(tissue).level; +} + +double Contrast::GetWindow(const std::string& tissue) +{ + return m_PresetMap.at(tissue).window; +} + + +Contrast +::Contrast() += default; + +Contrast:: +~Contrast() += default; + +void Contrast::ReadFromCustomContrastXMLFile(const char* pathname, SmartPtr& reader) +{ + reader->SetOutputObject(this); + reader->SetFilename(pathname); + + reader->GenerateOutputInformation(); + + // check if presets are populated + for (const auto& preset : reader->getPresetStack()) + { + m_PresetMap.insert(std::pair(preset->preset_name, *preset)); + } +} diff --git a/Common/ContrastXML.h b/Common/ContrastXML.h new file mode 100644 index 00000000..e04ad879 --- /dev/null +++ b/Common/ContrastXML.h @@ -0,0 +1,101 @@ +/*========================================================================= + + Program: ITK-SNAP + Language: C++ + + This file was created as part of the added functionality of the custom windowing. + We have added the option to set a custom contrast window preset using Contrast Menu. + + This file is mostly based on Common\Registry.h + +=========================================================================*/ +#ifndef __Contrast_h_ +#define __Contrast_h_ + +#ifdef _MSC_VER +# pragma warning(disable:4786) // '255' characters in the debug information +#endif //_MSC_VER + +#include +#include +#include +#include +#include + +#include +#include "itkXMLFile.h" + +#include "IRISException.h" + +struct PresetStruct +{ + std::string preset_name; + double level; + double window; +}; + +class Contrast; + +/** Reader for XML files */ +class ContrastXMLFileReader : public itk::XMLReader +{ +public: + + irisITKObjectMacro(ContrastXMLFileReader, itk::XMLReader) + int CanReadFile(const char* name) ITK_OVERRIDE; + + std::vector> getPresetStack() const; + +protected: + + ContrastXMLFileReader() + = default; + + void StartElement(const char* name, const char** atts) ITK_OVERRIDE; + void EndElement(const char* name) ITK_OVERRIDE; + void CharacterDataHandler(const char* inData, int inLength) ITK_OVERRIDE; + + static int strcmpi(const char* str1, const char* str2) + { + return itksys::SystemTools::Strucmp(str1, str2); + } + + // The preset stack + std::vector> m_PresetStack; +}; + +/** + * \class Contrast + * \brief A tree of key-value pair maps + */ +class Contrast final +{ +public: + /** Constructor initializes an empty contrast */ + Contrast(); + + /** Destructor */ + virtual ~Contrast(); + + /** Get a list of preset names*/ + std::vector GetPresetNames(); + + /** Get a level value*/ + double GetLevel(const std::string& tissue); + + /** Get a window value*/ + double GetWindow(const std::string& tissue); + + /** Empty the contents of the contrast */ + void Clear(); + + /** Read from XML file */ + void ReadFromCustomContrastXMLFile(const char* pathname, SmartPtr& reader); + +private: + + /** A hash table for the presets */ + std::map m_PresetMap; +}; + +#endif diff --git a/Common/Registry.cxx b/Common/Registry.cxx index a74714a1..0cbab5ce 100644 --- a/Common/Registry.cxx +++ b/Common/Registry.cxx @@ -50,6 +50,20 @@ using namespace std; +namespace GenericXMLFileReader +{ + static int CanReadFile(const char *name) + { + if ( !itksys::SystemTools::FileExists(name) + || itksys::SystemTools::FileIsDirectory(name) + || itksys::SystemTools::FileLength(name) == 0 ) + { + return 0; + } + + return 1; + } +} /** Reader for XML files */ @@ -79,14 +93,7 @@ class RegistryXMLFileReader : public itk::XMLReader int RegistryXMLFileReader::CanReadFile(const char *name) { - if ( !itksys::SystemTools::FileExists(name) - || itksys::SystemTools::FileIsDirectory(name) - || itksys::SystemTools::FileLength(name) == 0 ) - { - return 0; - } - - return 1; + return GenericXMLFileReader::CanReadFile(name); } void RegistryXMLFileReader::StartElement(const char *name, const char **atts) diff --git a/ContrastSettings.xml b/ContrastSettings.xml new file mode 100644 index 00000000..e4bc4b7b --- /dev/null +++ b/ContrastSettings.xml @@ -0,0 +1,38 @@ + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/GUI/Model/GlobalUIModel.cxx b/GUI/Model/GlobalUIModel.cxx index df3d6e2c..a1487cd0 100644 --- a/GUI/Model/GlobalUIModel.cxx +++ b/GUI/Model/GlobalUIModel.cxx @@ -399,6 +399,12 @@ void GlobalUIModel::ResetContrastAllLayers() } } +void GlobalUIModel::AdjustContrastAllLayers(double level, double window) +{ + IntensityCurveModel *curve_model = GetIntensityCurveModel(); + curve_model->OnAdjustCustomContrast(level, window); +} + void GlobalUIModel::ToggleOverlayVisibility() { // Are we in tiled mode or in stack mode? @@ -510,6 +516,7 @@ void GlobalUIModel::LoadUserPreferences() m_AppearanceSettings->LoadFromRegistry( si->Folder("UserInterface.Appearance")); + // Read the default behaviors dbs->ReadFromRegistry( si->Folder("UserInterface.DefaultBehavior")); diff --git a/GUI/Model/GlobalUIModel.h b/GUI/Model/GlobalUIModel.h index 2a85c247..f69a866d 100644 --- a/GUI/Model/GlobalUIModel.h +++ b/GUI/Model/GlobalUIModel.h @@ -326,9 +326,12 @@ class GlobalUIModel : public AbstractModel /** Auto-adjust contrast in all image layers */ void AutoContrastAllLayers(); - /** Auto-adjust contrast in all image layers */ + /** Reset contrast in all image layers */ void ResetContrastAllLayers(); + /** Adjust custom contrast in all image layers */ + void AdjustContrastAllLayers(double level, double window); + /** A function for switching between segmentation layers when there multiple */ void CycleSelectedSegmentationLayer(int direction); diff --git a/GUI/Model/IntensityCurveModel.cxx b/GUI/Model/IntensityCurveModel.cxx index 4f869a6c..20bb7b21 100644 --- a/GUI/Model/IntensityCurveModel.cxx +++ b/GUI/Model/IntensityCurveModel.cxx @@ -637,6 +637,15 @@ void IntensityCurveModel::OnAutoFitWindow() dmp->AutoFitContrast(); } +void IntensityCurveModel::OnAdjustCustomContrast(double custom_level, double custom_window) +{ + int index_level = 2; + int index_window = 3; + + SetIntensityRangeIndexedValue(index_level, custom_level); + SetIntensityRangeIndexedValue(index_window, custom_window); +} + bool IntensityCurveModel ::GetHistogramBinSizeValueAndRange( diff --git a/GUI/Model/IntensityCurveModel.h b/GUI/Model/IntensityCurveModel.h index a6284f4d..ae49cf07 100644 --- a/GUI/Model/IntensityCurveModel.h +++ b/GUI/Model/IntensityCurveModel.h @@ -172,6 +172,7 @@ class IntensityCurveModel : public IntensityCurveModelBase IntensityRangePropertyType index) const; void OnAutoFitWindow(); + void OnAdjustCustomContrast(double custom_level, double custom_window); protected: IntensityCurveModel(); diff --git a/GUI/Qt/Windows/MainControlPanel.cxx b/GUI/Qt/Windows/MainControlPanel.cxx index 5cea36d5..3a162715 100644 --- a/GUI/Qt/Windows/MainControlPanel.cxx +++ b/GUI/Qt/Windows/MainControlPanel.cxx @@ -184,6 +184,7 @@ MainControlPanel::MainControlPanel(MainImageWindow *parent) : void MainControlPanel::SetModel(GlobalUIModel *model) { m_Model = model; + ui->pageCursorInspector->SetModel(m_Model->GetCursorInspectionModel()); ui->pageZoomInspector->SetModel(m_Model); ui->pageDisplayInspector->SetModel(m_Model->GetDisplayLayoutModel()); diff --git a/GUI/Qt/Windows/MainControlPanel.h b/GUI/Qt/Windows/MainControlPanel.h index e57408bc..81fb9f96 100644 --- a/GUI/Qt/Windows/MainControlPanel.h +++ b/GUI/Qt/Windows/MainControlPanel.h @@ -5,6 +5,7 @@ class LabelSelectionButton; class LabelSelectionPopup; +class IntensityCurveModel; namespace Ui { class MainControlPanel; diff --git a/GUI/Qt/Windows/MainImageWindow.cxx b/GUI/Qt/Windows/MainImageWindow.cxx index d9656419..067625cb 100644 --- a/GUI/Qt/Windows/MainImageWindow.cxx +++ b/GUI/Qt/Windows/MainImageWindow.cxx @@ -72,6 +72,7 @@ #include #include "RegistrationDialog.h" #include "DistributedSegmentationDialog.h" +#include "ContrastXML.h" #include #include @@ -89,6 +90,7 @@ #include #include +#include QString read_tooltip_qt(const QString &filename) { @@ -96,7 +98,7 @@ QString read_tooltip_qt(const QString &filename) file.open(QFile::ReadOnly); QTextStream ts(&file); QString result = ts.readAll(); - file.close();; + file.close(); return result; } @@ -170,6 +172,7 @@ MainImageWindow::MainImageWindow(QWidget *parent) : m_Model(NULL) { ui->setupUi(this); + m_CustomContrast = new Contrast(); // Group mutually exclusive actions into action groups QActionGroup *grpToolbarMain = new QActionGroup(this); @@ -646,6 +649,7 @@ void MainImageWindow::ShowFirstTime() // Also make sure the other elements look right before showing the window this->UpdateRecentMenu(); + this->UpdateContrastMenu(); this->UpdateRecentProjectsMenu(); this->UpdateWindowTitle(); this->UpdateLayerLayoutActions(); @@ -938,6 +942,38 @@ void MainImageWindow::CreateRecentMenu( submenu->menuAction()->setVisible(recent.size() > 0); } +std::string MainImageWindow::GetContrastXMLFilePath() +{ + char rawPath[MAX_PATH]; + GetModuleFileNameA(nullptr, rawPath, MAX_PATH); + auto exePath = std::string(rawPath); + auto filenameDir = exePath.substr(0, exePath.find_last_of("\\/")); + auto filePath = filenameDir + "\\ContrastSettings.xml"; + return filePath; +} + +void MainImageWindow::CreateContrastMenu(QMenu *submenu, + const char *slot) +{ + const auto filePath = GetContrastXMLFilePath(); + + SmartPtr reader = ContrastXMLFileReader::New(); + if (reader->CanReadFile(filePath.c_str())) + { + // Read the XML with custom contrast settings + m_CustomContrast->ReadFromCustomContrastXMLFile(filePath.c_str(), reader); + + auto presetNames = m_CustomContrast->GetPresetNames(); + + for (auto presetName : presetNames) + { + QAction* action = submenu->addAction(from_utf8(presetName)); + activateOnFlag(action, m_Model, UIF_BASEIMG_LOADED); + connect(action, SIGNAL(triggered(bool)), this, slot); + } + } +} + void MainImageWindow::UpdateRecentMenu() { // Create recent menus for various history categories @@ -954,6 +990,12 @@ void MainImageWindow::UpdateRecentMenu() SLOT(LoadAnotherRecentSegmentationActionTriggered())); } +void MainImageWindow::UpdateContrastMenu() +{ + // Create contrast menu for various preset categories + this->CreateContrastMenu(ui->menuContrast, SLOT(LoadContrastActionTriggered())); +} + void MainImageWindow::UpdateRecentProjectsMenu() { this->CreateRecentMenu(ui->menuRecentWorkspaces, "Project", true, 5, @@ -1406,6 +1448,17 @@ void MainImageWindow::LoadRecentSegmentationActionTriggered() LoadRecentSegmentation(file, false); } +void MainImageWindow::LoadContrastActionTriggered() +{ + // Get the filename that wants to be loaded + QAction *action = qobject_cast(sender()); + QString preset = action->text(); + auto level = m_CustomContrast->GetLevel(preset.toStdString()); + auto window = m_CustomContrast->GetWindow(preset.toStdString()); + + m_Model->AdjustContrastAllLayers(level, window); +} + void MainImageWindow::LoadAnotherRecentSegmentationActionTriggered() { // Get the filename that wants to be loaded @@ -2131,6 +2184,7 @@ void MainImageWindow::on_actionResetContrastGlobal_triggered() m_Model->ResetContrastAllLayers(); } + void MainImageWindow::DoUpdateCheck(bool quiet) { std::string nver; diff --git a/GUI/Qt/Windows/MainImageWindow.h b/GUI/Qt/Windows/MainImageWindow.h index 484a73cf..91c54917 100644 --- a/GUI/Qt/Windows/MainImageWindow.h +++ b/GUI/Qt/Windows/MainImageWindow.h @@ -28,6 +28,8 @@ #define MAINIMAGEWINDOW_H #include + +#include "ContrastXML.h" #include "GlobalState.h" #include "SNAPCommon.h" @@ -134,6 +136,7 @@ public slots: void LoadRecentActionTriggered(); void LoadRecentOverlayActionTriggered(); void LoadRecentSegmentationActionTriggered(); + void LoadContrastActionTriggered(); void LoadAnotherRecentSegmentationActionTriggered(); void LoadRecentProjectActionTriggered(); void LoadAnotherDicomActionTriggered(); @@ -336,13 +339,18 @@ private slots: private: + static std::string GetContrastXMLFilePath(); + void CreateRecentMenu(QMenu *submenu, const char *history_category, bool use_global_history, int max_items, const char *slot, bool use_shortcut = false, int shortcut_modifier = 0); + void CreateContrastMenu(QMenu *submenu, const char *slot); + void UpdateRecentMenu(); + void UpdateContrastMenu(); void UpdateWindowTitle(); void UpdateProjectMenuItems(); void UpdateRecentProjectsMenu(); @@ -414,6 +422,8 @@ private slots: DistributedSegmentationDialog *m_DSSDialog; + Contrast *m_CustomContrast; + // A timer used to animate components QTimer *m_AnimateTimer; }; diff --git a/GUI/Qt/Windows/MainImageWindow.ui b/GUI/Qt/Windows/MainImageWindow.ui index 9d06ddca..0181e159 100644 --- a/GUI/Qt/Windows/MainImageWindow.ui +++ b/GUI/Qt/Windows/MainImageWindow.ui @@ -535,6 +535,7 @@ color:rgb(103, 103, 103); +