diff --git a/Modules/DICOM/include/mitkDICOMTagBasedSorter.h b/Modules/DICOM/include/mitkDICOMTagBasedSorter.h index 8cc2b2be5d..b3068a617d 100644 --- a/Modules/DICOM/include/mitkDICOMTagBasedSorter.h +++ b/Modules/DICOM/include/mitkDICOMTagBasedSorter.h @@ -1,206 +1,207 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkDICOMTagBasedSorter_h #define mitkDICOMTagBasedSorter_h #include "mitkDICOMDatasetSorter.h" #include "mitkDICOMSortCriterion.h" namespace mitk { /** \ingroup DICOMModule \brief Sort DICOM datasets based on configurable tags. This class implements sorting of input DICOM datasets into multiple outputs as described in \ref DICOMITKSeriesGDCMReader_LoadingStrategy. The logic of sorting and splitting is most simple and most generic: 1. Datasets will be put into different groups, if they differ in their value of specific tags (defined by AddDistinguishingTag()) - there might be multiple distinguishing tags defined - tag values might be processed before comparison by means of TagValueProcessor (e.g. round to a number of decimal places) 2. Each of the groups will be sorted by comparing their tag values using multiple DICOMSortCriterion - DICOMSortCriterion might evaluate a single tag (e.g. Instance Number) or multiple values (as in SortByImagePositionPatient) - only a single DICOMSortCriterion is defined for DICOMTagBasedSorter, because each DICOMSortCriterion holds a "secondary sort criterion", i.e. an application can define multiple tags for sorting by chaining \link DICOMSortCriterion DICOMSortCriteria \endlink - applications should make sure that sorting is always defined (to avoid problems with standard containers), e.g. by adding a comparison of filenames or instance UIDs as a last sorting fallback. */ class MITKDICOM_EXPORT DICOMTagBasedSorter : public DICOMDatasetSorter { public: /** \brief Processes tag values before they are compared. These classes could do some kind of normalization such as rounding, lower case formatting, etc. */ class MITKDICOM_EXPORT TagValueProcessor { public: /// \brief Implements the "processing". virtual std::string operator()(const std::string&) const = 0; virtual TagValueProcessor* Clone() const = 0; virtual ~TagValueProcessor() {} }; /** \brief Cuts a number after configured number of decimal places. An instance of this class can be used to avoid errors when comparing minimally different image orientations. */ class MITKDICOM_EXPORT CutDecimalPlaces : public TagValueProcessor { public: CutDecimalPlaces(unsigned int precision); CutDecimalPlaces(const CutDecimalPlaces& other); unsigned int GetPrecision() const; std::string operator()(const std::string&) const override; TagValueProcessor* Clone() const override; private: unsigned int m_Precision; }; mitkClassMacro( DICOMTagBasedSorter, DICOMDatasetSorter ); itkNewMacro( DICOMTagBasedSorter ); /** \brief Datasets that differ in given tag's value will be sorted into separate outputs. */ void AddDistinguishingTag( const DICOMTag&, TagValueProcessor* tagValueProcessor = nullptr ); DICOMTagList GetDistinguishingTags() const; const TagValueProcessor* GetTagValueProcessorForDistinguishingTag(const DICOMTag&) const; /** \brief Define the sorting criterion (which holds seconardy criteria) */ void SetSortCriterion( DICOMSortCriterion::ConstPointer criterion ); DICOMSortCriterion::ConstPointer GetSortCriterion() const; /** \brief A list of all the tags needed for processing (facilitates scanning). */ DICOMTagList GetTagsOfInterest() override; /** \brief Whether or not groups should be checked for consecutive tag values. When this flag is set (default in constructor=off), the sorter will not only sort in a way that the values of a configured tag are ascending BUT in addition the sorter will enforce a constant numerical distance between values. Having this flag is useful for handling of series with missing slices, e.g. Instance Numbers 1 2 3 5 6 7 8. With the flag set to true, the sorter would split this group into two, because the initial distance of 1 is not kept between Instance Numbers 3 and 5. A special case of this behavior can be configured by SetExpectDistanceOne(). When this additional flag is set to true, the sorter will expect distance 1 exactly. This can help if the second slice is missing already. Without this additional flag, we would "learn" about a wrong distance of 2 (or similar) and then sort completely wrong. */ void SetStrictSorting(bool strict); bool GetStrictSorting() const; /** \brief Flag for a special case in "strict sorting". Please see documentation of SetStrictSorting(). \sa SetStrictSorting */ void SetExpectDistanceOne(bool strict); bool GetExpectDistanceOne() const; /** \brief Actually sort as described in the Detailed Description. */ void Sort() override; /** \brief Print configuration details into given stream. */ void PrintConfiguration(std::ostream& os, const std::string& indent = "") const override; bool operator==(const DICOMDatasetSorter& other) const override; static bool GetDefaultStrictSorting() { return m_DefaultStrictSorting; } static bool GetDefaultExpectDistanceOne() { return m_DefaultExpectDistanceOne; } protected: /** \brief Helper struct to feed into std::sort, configured via DICOMSortCriterion. */ struct ParameterizedDatasetSort { ParameterizedDatasetSort(DICOMSortCriterion::ConstPointer); bool operator() (const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right); DICOMSortCriterion::ConstPointer m_SortCriterion; }; DICOMTagBasedSorter(); ~DICOMTagBasedSorter() override; DICOMTagBasedSorter(const DICOMTagBasedSorter& other); DICOMTagBasedSorter& operator=(const DICOMTagBasedSorter& other); /** \brief Helper for SplitInputGroups(). */ std::string BuildGroupID( DICOMDatasetAccess* dataset ); using GroupIDToListType = std::map; using SplitReasonListType = std::map; /** \brief Implements the "distiguishing tags". To sort datasets into different groups, a long string will be built for each dataset. The string concatenates all tags and their respective values. Datasets that match in all values will end up with the same string. @param splitReasons Reference to the split reasons vector. It will be also updated by the method to reflect the reasons for the returned groups. */ GroupIDToListType SplitInputGroups(SplitReasonListType& splitReasons); /** \brief Implements the sorting step. Relatively simple implementation thanks to std::sort and a parameterization via DICOMSortCriterion. + @param groups Argument that contains the groups that should be sorted. @param splitReasons Reference to the split reasons vector. It will be also updated by the method to reflect the reasons for the returned groups. */ GroupIDToListType& SortGroups(GroupIDToListType& groups, SplitReasonListType& splitReasons); DICOMTagList m_DistinguishingTags; typedef std::map TagValueProcessorMap; TagValueProcessorMap m_TagValueProcessor; DICOMSortCriterion::ConstPointer m_SortCriterion; bool m_StrictSorting; bool m_ExpectDistanceOne; const static bool m_DefaultStrictSorting = false; const static bool m_DefaultExpectDistanceOne = false; }; } #endif diff --git a/Modules/DICOM/include/mitkITKDICOMSeriesReaderHelper.h b/Modules/DICOM/include/mitkITKDICOMSeriesReaderHelper.h index 4bcae594c7..68816b35ac 100644 --- a/Modules/DICOM/include/mitkITKDICOMSeriesReaderHelper.h +++ b/Modules/DICOM/include/mitkITKDICOMSeriesReaderHelper.h @@ -1,105 +1,109 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkITKDICOMSeriesReaderHelper_h #define mitkITKDICOMSeriesReaderHelper_h #include "mitkImage.h" #include "mitkGantryTiltInformation.h" #include "mitkDICOMTag.h" #include /* Forward deceleration of an DCMTK class. Used in the txx but part of the interface.*/ class OFDateTime; namespace mitk { class ITKDICOMSeriesReaderHelper { public: static const DICOMTag AcquisitionDateTag; static const DICOMTag AcquisitionTimeTag; static const DICOMTag TriggerTimeTag; typedef std::vector StringContainer; typedef std::list StringContainerList; Image::Pointer Load( const StringContainer& filenames, bool correctTilt, const GantryTiltInformation& tiltInfo ); Image::Pointer Load3DnT( const StringContainerList& filenamesLists, bool correctTilt, const GantryTiltInformation& tiltInfo ); static bool CanHandleFile(const std::string& filename); private: typedef std::vector TimeBoundsList; typedef itk::FixedArray DateTimeBounds; /** Scans the given files for the acquisition time and returns the lowest and highest acquisition date time as date time bounds via bounds. @param bounds The acquisition date time bound extracted from the files. @param triggerBounds Time bounds for trigger information extracted from the files. If no trigger information was found than it returns trigger == [0.0, 0.0]. @return If no acquisition date times can be found the function return will be false. Otherwise it returns True. */ static bool ExtractDateTimeBoundsAndTriggerOfTimeStep( const StringContainer& filenamesOfTimeStep, DateTimeBounds& bounds, TimeBounds& triggerBounds); /* Determine the time bounds in ms respective to the baselineDateTime for the passed files. Additionally it regards the trigger time tag if set and acquisition date time - carries not enough information.*/ + carries not enough information. + @param [in, out] usedTriggerBounds Variable used to communicate if preceding time bounds extractions + used the trigger time (true). If true is passed to the method, it will use always trigger time + to extract the next bounds. Default is false. After the method call is returning the variable indicates + the last call used trigger time or not to extract the time bounds.*/ static bool ExtractTimeBoundsOfTimeStep(const StringContainer& filenamesOfTimeStep, TimeBounds& bounds, - const OFDateTime& baselineDateTime ); - + const OFDateTime& baselineDateTime, + bool& usedTriggerBounds); /** Returns the list of time bounds of all passed time step containers. (sa ExtractTimeBoundsOfTimeStep and ExtractDateTimeBoundsOfTimeStep). Time steps where no time bounds could be extracted are indicated by "null" time bounds (both values "0"). The order of the returned list equals of passed filenamesOfTimeSteps order. @remark The function regards acquisition date time tags and trigger time tags.*/ static TimeBoundsList ExtractTimeBoundsOfTimeSteps (const StringContainerList& filenamesOfTimeSteps); - /** Helper function that generates a time geometry using the template and the passed boundslist + /** Helper function that generates a time geometry using the template and the passed bounds list (which indicates the number of time steps). */ static TimeGeometry::Pointer GenerateTimeGeometry(const BaseGeometry* templateGeometry, const TimeBoundsList& boundsList); template typename ImageType::Pointer FixUpTiltedGeometry( ImageType* input, const GantryTiltInformation& tiltInfo ); template Image::Pointer LoadDICOMByITK( const StringContainer& filenames, bool correctTilt, const GantryTiltInformation& tiltInfo, itk::GDCMImageIO::Pointer& io); template Image::Pointer LoadDICOMByITK3DnT( const StringContainerList& filenames, bool correctTilt, const GantryTiltInformation& tiltInfo, itk::GDCMImageIO::Pointer& io); }; } #endif diff --git a/Modules/DICOM/src/mitkITKDICOMSeriesReaderHelper.cpp b/Modules/DICOM/src/mitkITKDICOMSeriesReaderHelper.cpp index 5ac21da5d4..ff93c00ab8 100644 --- a/Modules/DICOM/src/mitkITKDICOMSeriesReaderHelper.cpp +++ b/Modules/DICOM/src/mitkITKDICOMSeriesReaderHelper.cpp @@ -1,455 +1,464 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ //#define MBILOG_ENABLE_DEBUG #include #include #define BOOST_DATE_TIME_NO_LIB //Prevent unnecessary/unwanted auto link in this compilation when activating boost libraries in the MITK superbuild //It is necessary because BOOST_ALL_DYN_LINK overwrites BOOST_DATE_TIME_NO_LIB #if defined(BOOST_ALL_DYN_LINK) #undef BOOST_ALL_DYN_LINK #endif #include #include "mitkITKDICOMSeriesReaderHelper.h" #include "mitkITKDICOMSeriesReaderHelper.txx" #include "mitkDICOMGDCMTagScanner.h" #include "mitkArbitraryTimeGeometry.h" #include "dcmtk/dcmdata/dcvrda.h" const mitk::DICOMTag mitk::ITKDICOMSeriesReaderHelper::AcquisitionDateTag = mitk::DICOMTag( 0x0008, 0x0022 ); const mitk::DICOMTag mitk::ITKDICOMSeriesReaderHelper::AcquisitionTimeTag = mitk::DICOMTag( 0x0008, 0x0032 ); const mitk::DICOMTag mitk::ITKDICOMSeriesReaderHelper::TriggerTimeTag = mitk::DICOMTag( 0x0018, 0x1060 ); #define switch3DCase( IOType, T ) \ case IOType: \ return LoadDICOMByITK( filenames, correctTilt, tiltInfo, io ); bool mitk::ITKDICOMSeriesReaderHelper::CanHandleFile( const std::string& filename ) { MITK_DEBUG << "ITKDICOMSeriesReaderHelper::CanHandleFile " << filename; itk::GDCMImageIO::Pointer tester = itk::GDCMImageIO::New(); return tester->CanReadFile( filename.c_str() ); } mitk::Image::Pointer mitk::ITKDICOMSeriesReaderHelper::Load( const StringContainer& filenames, bool correctTilt, const GantryTiltInformation& tiltInfo ) { if ( filenames.empty() ) { MITK_DEBUG << "Calling LoadDicomSeries with empty filename string container. Probably invalid application logic."; return nullptr; // this is not actually an error but the result is very simple } typedef itk::GDCMImageIO DcmIoType; DcmIoType::Pointer io = DcmIoType::New(); try { if ( io->CanReadFile( filenames.front().c_str() ) ) { io->SetFileName( filenames.front().c_str() ); io->ReadImageInformation(); if ( io->GetPixelType() == itk::IOPixelEnum::SCALAR ) { switch ( io->GetComponentType() ) { switch3DCase(itk::IOComponentEnum::UCHAR, unsigned char) switch3DCase(itk::IOComponentEnum::CHAR, char) switch3DCase( itk::IOComponentEnum::USHORT, unsigned short) switch3DCase(itk::IOComponentEnum::SHORT, short) switch3DCase(itk::IOComponentEnum::UINT, unsigned int) switch3DCase(itk::IOComponentEnum::INT, int) switch3DCase( itk::IOComponentEnum::ULONG, long unsigned int) switch3DCase(itk::IOComponentEnum::LONG, long int) switch3DCase(itk::IOComponentEnum::FLOAT, float) switch3DCase(itk::IOComponentEnum::DOUBLE, double) default : MITK_ERROR << "Found unsupported DICOM scalar pixel type: (enum value) " << io->GetComponentType(); } } else if ( io->GetPixelType() == itk::IOPixelEnum::RGB ) { switch ( io->GetComponentType() ) { switch3DCase(itk::IOComponentEnum::UCHAR, itk::RGBPixel) switch3DCase( itk::IOComponentEnum::CHAR, itk::RGBPixel) switch3DCase(itk::IOComponentEnum::USHORT, itk::RGBPixel) switch3DCase(itk::IOComponentEnum::SHORT, itk::RGBPixel) switch3DCase( itk::IOComponentEnum::UINT, itk::RGBPixel) switch3DCase(itk::IOComponentEnum::INT, itk::RGBPixel) switch3DCase(itk::IOComponentEnum::ULONG, itk::RGBPixel) switch3DCase(itk::IOComponentEnum::LONG, itk::RGBPixel) switch3DCase( itk::IOComponentEnum::FLOAT, itk::RGBPixel) switch3DCase(itk::IOComponentEnum::DOUBLE, itk::RGBPixel) default : MITK_ERROR << "Found unsupported DICOM scalar pixel type: (enum value) " << io->GetComponentType(); } } MITK_ERROR << "Unsupported DICOM pixel type"; return nullptr; } } catch ( const itk::MemoryAllocationError& e ) { MITK_ERROR << "Out of memory. Cannot load DICOM series: " << e.what(); } catch ( const std::exception& e ) { MITK_ERROR << "Error encountered when loading DICOM series:" << e.what(); } catch ( ... ) { MITK_ERROR << "Unspecified error encountered when loading DICOM series."; } return nullptr; } #define switch3DnTCase( IOType, T ) \ case IOType: \ return LoadDICOMByITK3DnT( filenamesLists, correctTilt, tiltInfo, io ); mitk::Image::Pointer mitk::ITKDICOMSeriesReaderHelper::Load3DnT( const StringContainerList& filenamesLists, bool correctTilt, const GantryTiltInformation& tiltInfo ) { if ( filenamesLists.empty() || filenamesLists.front().empty() ) { MITK_DEBUG << "Calling LoadDicomSeries with empty filename string container. Probably invalid application logic."; return nullptr; // this is not actually an error but the result is very simple } typedef itk::GDCMImageIO DcmIoType; DcmIoType::Pointer io = DcmIoType::New(); try { if ( io->CanReadFile( filenamesLists.front().front().c_str() ) ) { io->SetFileName( filenamesLists.front().front().c_str() ); io->ReadImageInformation(); if ( io->GetPixelType() == itk::IOPixelEnum::SCALAR ) { switch ( io->GetComponentType() ) { switch3DnTCase(itk::IOComponentEnum::UCHAR, unsigned char) switch3DnTCase(itk::IOComponentEnum::CHAR, char) switch3DnTCase(itk::IOComponentEnum::USHORT, unsigned short) switch3DnTCase( itk::IOComponentEnum::SHORT, short) switch3DnTCase(itk::IOComponentEnum::UINT, unsigned int) switch3DnTCase(itk::IOComponentEnum::INT, int) switch3DnTCase(itk::IOComponentEnum::ULONG, long unsigned int) switch3DnTCase(itk::IOComponentEnum::LONG, long int) switch3DnTCase(itk::IOComponentEnum::FLOAT, float) switch3DnTCase(itk::IOComponentEnum::DOUBLE, double) default : MITK_ERROR << "Found unsupported DICOM scalar pixel type: (enum value) " << io->GetComponentType(); } } else if ( io->GetPixelType() == itk::IOPixelEnum::RGB ) { switch ( io->GetComponentType() ) { switch3DnTCase(itk::IOComponentEnum::UCHAR, itk::RGBPixel) switch3DnTCase(itk::IOComponentEnum::CHAR, itk::RGBPixel) switch3DnTCase( itk::IOComponentEnum::USHORT, itk::RGBPixel) switch3DnTCase(itk::IOComponentEnum::SHORT, itk::RGBPixel) switch3DnTCase(itk::IOComponentEnum::UINT, itk::RGBPixel) switch3DnTCase( itk::IOComponentEnum::INT, itk::RGBPixel) switch3DnTCase(itk::IOComponentEnum::ULONG, itk::RGBPixel) switch3DnTCase(itk::IOComponentEnum::LONG, itk::RGBPixel) switch3DnTCase( itk::IOComponentEnum::FLOAT, itk::RGBPixel) switch3DnTCase(itk::IOComponentEnum::DOUBLE, itk::RGBPixel) default : MITK_ERROR << "Found unsupported DICOM scalar pixel type: (enum value) " << io->GetComponentType(); } } MITK_ERROR << "Unsupported DICOM pixel type"; return nullptr; } } catch ( const itk::MemoryAllocationError& e ) { MITK_ERROR << "Out of memory. Cannot load DICOM series: " << e.what(); } catch ( const std::exception& e ) { MITK_ERROR << "Error encountered when loading DICOM series:" << e.what(); } catch ( ... ) { MITK_ERROR << "Unspecified error encountered when loading DICOM series."; } return nullptr; } bool ConvertDICOMDateTimeString( const std::string& dateString, const std::string& timeString, OFDateTime& time ) { OFString content( timeString.c_str() ); if ( !dateString.empty() ) { content = OFString( dateString.c_str() ).append( content ); } else { // This is a workaround for DICOM data that has an AquisitionTime but no AquisitionDate. // In this case, we use the current date. That's not really nice, but is absolutely OK // as we're only interested in the time anyways... OFString currentDate; DcmDate::getCurrentDate( currentDate ); content = currentDate.append( content ); } const OFCondition result = DcmDateTime::getOFDateTimeFromString( content, time ); return result.good(); } boost::posix_time::ptime ConvertOFDateTimeToPTime( const OFDateTime& time ) { const boost::gregorian::date boostDate( time.getDate().getYear(), time.getDate().getMonth(), time.getDate().getDay() ); const boost::posix_time::time_duration boostTime = boost::posix_time::hours( time.getTime().getHour() ) + boost::posix_time::minutes( time.getTime().getMinute() ) + boost::posix_time::seconds( static_cast(time.getTime().getSecond()) ) + boost::posix_time::milliseconds( time.getTime().getMilliSecond() ); boost::posix_time::ptime result( boostDate, boostTime ); return result; } OFDateTime GetLowerDateTime( const OFDateTime& time1, const OFDateTime& time2 ) { OFDateTime result = time1; if ( ( time2.getDate() < time1.getDate() ) || ( ( time2.getDate() == time1.getDate() ) && ( time2.getTime() < time1.getTime() ) ) ) { result = time2; } return result; } OFDateTime GetUpperDateTime( const OFDateTime& time1, const OFDateTime& time2 ) { OFDateTime result = time1; if ( ( time2.getDate() > time1.getDate() ) || ( ( time2.getDate() == time1.getDate() ) && ( time2.getTime() > time1.getTime() ) ) ) { result = time2; } return result; } double ComputeMiliSecDuration( const OFDateTime& start, const OFDateTime& stop ) { const boost::posix_time::ptime startTime = ConvertOFDateTimeToPTime( start ); const boost::posix_time::ptime stopTime = ConvertOFDateTimeToPTime( stop ); ::boost::posix_time::time_duration duration = stopTime - startTime; return duration.total_milliseconds(); } bool mitk::ITKDICOMSeriesReaderHelper::ExtractDateTimeBoundsAndTriggerOfTimeStep( const StringContainer& filenamesOfTimeStep, DateTimeBounds& bounds, TimeBounds& triggerBounds) { DICOMGDCMTagScanner::Pointer filescanner = DICOMGDCMTagScanner::New(); filescanner->SetInputFiles(filenamesOfTimeStep); filescanner->AddTag(AcquisitionDateTag); filescanner->AddTag(AcquisitionTimeTag); filescanner->AddTag(TriggerTimeTag); filescanner->Scan(); const DICOMDatasetAccessingImageFrameList frameList = filescanner->GetFrameInfoList(); bool result = false; bool firstAq = true; bool firstTr = true; triggerBounds = TimeBounds(0.0); for (auto pos = frameList.cbegin(); pos != frameList.cend(); ++pos) { const std::string aqDateStr = (*pos)->GetTagValueAsString(AcquisitionDateTag).value; const std::string aqTimeStr = (*pos)->GetTagValueAsString(AcquisitionTimeTag).value; const std::string triggerTimeStr = (*pos)->GetTagValueAsString(TriggerTimeTag).value; OFDateTime aqDateTime; const bool convertAqResult = ConvertDICOMDateTimeString(aqDateStr, aqTimeStr, aqDateTime); OFBool convertTriggerResult; mitk::ScalarType triggerTime = OFStandard::atof(triggerTimeStr.c_str(), &convertTriggerResult); if (convertAqResult) { if (firstAq) { bounds[0] = aqDateTime; bounds[1] = aqDateTime; firstAq = false; } else { bounds[0] = GetLowerDateTime(bounds[0], aqDateTime); bounds[1] = GetUpperDateTime(bounds[1], aqDateTime); } result = true; } if (convertTriggerResult) { if (firstTr) { triggerBounds[0] = triggerTime; triggerBounds[1] = triggerTime; firstTr = false; } else { triggerBounds[0] = std::min(triggerBounds[0], triggerTime); triggerBounds[1] = std::max(triggerBounds[1], triggerTime); } result = true; } } return result; }; bool mitk::ITKDICOMSeriesReaderHelper::ExtractTimeBoundsOfTimeStep( - const StringContainer& filenamesOfTimeStep, TimeBounds& bounds, const OFDateTime& baselineDateTime ) + const StringContainer& filenamesOfTimeStep, TimeBounds& bounds, const OFDateTime& baselineDateTime, bool& usedTriggerBounds ) { DateTimeBounds aqDTBounds; TimeBounds triggerBounds; bool result = ExtractDateTimeBoundsAndTriggerOfTimeStep(filenamesOfTimeStep, aqDTBounds, triggerBounds); mitk::ScalarType lowerBound = ComputeMiliSecDuration( baselineDateTime, aqDTBounds[0] ); mitk::ScalarType upperBound = ComputeMiliSecDuration( baselineDateTime, aqDTBounds[1] ); - if ( lowerBound < mitk::eps || upperBound < mitk::eps ) + if ( lowerBound < mitk::eps || upperBound < mitk::eps || usedTriggerBounds) { lowerBound = triggerBounds[0]; upperBound = triggerBounds[1]; + usedTriggerBounds = true; + } + else + { + usedTriggerBounds = false; } bounds[0] = lowerBound; bounds[1] = upperBound; return result; }; mitk::ITKDICOMSeriesReaderHelper::TimeBoundsList mitk::ITKDICOMSeriesReaderHelper::ExtractTimeBoundsOfTimeSteps( const StringContainerList& filenamesOfTimeSteps ) { TimeBoundsList result; OFDateTime baseLine; // extract the timebounds DateTimeBounds baselineDateTimeBounds; TimeBounds triggerBounds; auto pos = filenamesOfTimeSteps.cbegin(); ExtractDateTimeBoundsAndTriggerOfTimeStep(*pos, baselineDateTimeBounds, triggerBounds); baseLine = baselineDateTimeBounds[0]; // timebounds for baseline is 0 TimeBounds bounds( 0.0 ); result.push_back( bounds ); + //start with not using trigger time. Will be changed by ExtractTimeBoundsOfTimeStep if needed. + bool usedTriggerTime = false; // iterate over the remaining timesteps for ( ++pos; pos != filenamesOfTimeSteps.cend(); ++pos ) { TimeBounds bounds( 0.0 ); TimeBounds dateTimeBounds; // extract the timebounds relative to the baseline - if ( ExtractTimeBoundsOfTimeStep( *pos, dateTimeBounds, baseLine ) ) + if ( ExtractTimeBoundsOfTimeStep( *pos, dateTimeBounds, baseLine, usedTriggerTime) ) { - bounds[0] = dateTimeBounds[0]; bounds[1] = dateTimeBounds[1]; } result.push_back( bounds ); } + if (usedTriggerTime) + MITK_DEBUG << "Used trigger time to extract time bounds of passed files"; + return result; }; mitk::TimeGeometry::Pointer mitk::ITKDICOMSeriesReaderHelper::GenerateTimeGeometry( const BaseGeometry* templateGeometry, const TimeBoundsList& boundsList ) { TimeGeometry::Pointer timeGeometry; double check = 0.0; const auto boundListSize = boundsList.size(); for ( std::size_t pos = 0; pos < boundListSize; ++pos ) { check += boundsList[pos][0]; check += boundsList[pos][1]; } if ( check < mitk::eps ) { // if all bounds are zero we assume that the bounds could not be correctly determined // and as a fallback generate a time geometry in the old mitk style ProportionalTimeGeometry::Pointer newTimeGeometry = ProportionalTimeGeometry::New(); newTimeGeometry->Initialize( templateGeometry, boundListSize ); timeGeometry = newTimeGeometry.GetPointer(); } else { ArbitraryTimeGeometry::Pointer newTimeGeometry = ArbitraryTimeGeometry::New(); newTimeGeometry->ClearAllGeometries(); newTimeGeometry->ReserveSpaceForGeometries( boundListSize ); for ( std::size_t pos = 0; pos < boundListSize; ++pos ) { TimeBounds bounds = boundsList[pos]; if ( pos + 1 < boundListSize ) { //Currently we do not explicitly support "gaps" in the time coverage //thus we set the max time bound of a time step to the min time bound //of its successor. bounds[1] = boundsList[pos + 1][0]; } newTimeGeometry->AppendNewTimeStepClone(templateGeometry, bounds[0], bounds[1]); } timeGeometry = newTimeGeometry.GetPointer(); } return timeGeometry; }; diff --git a/Modules/Multilabel/mitkLabelSetImageConverter.h b/Modules/Multilabel/mitkLabelSetImageConverter.h index 6b712a3f80..43a1d5c261 100644 --- a/Modules/Multilabel/mitkLabelSetImageConverter.h +++ b/Modules/Multilabel/mitkLabelSetImageConverter.h @@ -1,76 +1,77 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkLabelSetImageConverter_h #define mitkLabelSetImageConverter_h #include namespace mitk { /** * \brief Convert mitk::LabelSetImage to mitk::Image (itk::VectorImage) */ MITKMULTILABEL_EXPORT Image::Pointer ConvertLabelSetImageToImage(LabelSetImage::ConstPointer labelSetImage); /** * \brief Convert mitk::Image to mitk::LabelSetImage, templating and differentation between itk::Image and * itk::VectorImage is internal */ MITKMULTILABEL_EXPORT LabelSetImage::Pointer ConvertImageToLabelSetImage(Image::Pointer image); MITKMULTILABEL_EXPORT LabelSetImage::Pointer ConvertImageVectorToLabelSetImage(const std::vector& images, const TimeGeometry* timeGeometry); MITKMULTILABEL_EXPORT std::vector SplitVectorImage(const Image* vecImage); /** Function takes a vector of labels and transfers all labels as clones with adapted label values to the result vector. The values will be adapted according to the provided mapping (key is the old value, value the new). @remark: Only labels will be transferred, nothing else. So things like message observers or m_ReservedLabelValuesFunctor must be copied explicitly.*/ MITKMULTILABEL_EXPORT LabelSetImage::LabelVectorType GenerateLabelSetWithMappedValues(const LabelSetImage::ConstLabelVectorType&, LabelValueMappingVector labelMapping); MITKMULTILABEL_EXPORT Image::Pointer ConvertImageToGroupImage(const Image* inputImage, mitk::LabelSetImage::LabelValueVectorType& foundLabels); MITKMULTILABEL_EXPORT bool CheckForLabelValueConflictsAndResolve(const mitk::LabelSetImage::LabelValueVectorType& newValues, mitk::LabelSetImage::LabelValueVectorType& usedLabelValues, mitk::LabelSetImage::LabelValueVectorType& correctedLabelValues); /** Function creates a binary mask representing only the specified label of the multi label segmentation. * @param segmentation Pointer to the segmentation that is the source for the mask. * @param labelValue the label that should be extracted. * @param createBinaryMap indicates if the label pixels should be indicated by the value 1 (createBinaryMap==true) or by the value of the label * (createBinaryMap==false). * @pre segmentation must point to a valid instance. * @pre labelValue must exist in segmentation.*/ MITKMULTILABEL_EXPORT Image::Pointer CreateLabelMask(const LabelSetImage* segmentation, LabelSetImage::LabelValueType labelValue, bool createBinaryMap = true); + using IDToLabelClassNameMapType = std::map; /** Function creates a map of all label classes in a specified group. * @param segmentation Pointer to the segmentation that is the source for the map. * @param groupID the group that should be used. * @param selectedLabels The selected labels that should be represented in the class map. This is meant as white list, therefore only * label values listed in the list are used. Invalid label values (not existing in the group) will be ignored. * @return Returns a pair where first is the pointer to the created map image and second is the look up table that indicated * the pixel value of each found class in the map. * @pre segmentation must point to a valid instance. * @pre groupID must exist in segmentation.*/ - using IDToLabelClassNameMapType = std::map; MITKMULTILABEL_EXPORT std::pair CreateLabelClassMap(const LabelSetImage* segmentation, LabelSetImage::GroupIndexType groupID, const LabelSetImage::LabelValueVectorType& selectedLabels); + /** Function creates a map of all label classes in a specified group. * @overload * This version always uses all labels of a group. * @param segmentation Pointer to the segmentation that is the source for the map. * @param groupID the group that should be used. * @return Returns a pair where first is the pointer to the created map image and second is the look up table that indicated * the pixel value of each found class in the map. * @pre segmentation must point to a valid instance. * @pre groupID must exist in segmentation.*/ MITKMULTILABEL_EXPORT std::pair CreateLabelClassMap(const LabelSetImage* segmentation, LabelSetImage::GroupIndexType groupID); } #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.h b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.h index 54b87bc561..7c3a4012cf 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.h +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.h @@ -1,198 +1,199 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkMultiLabelManager_h #define QmitkMultiLabelManager_h #include #include #include #include #include #include class QmitkDataStorageComboBox; class QCompleter; namespace Ui { class QmitkMultiLabelManagerControls; } namespace mitk { class DataStorage; } class MITKSEGMENTATIONUI_EXPORT QmitkMultiLabelManager : public QWidget { Q_OBJECT public: explicit QmitkMultiLabelManager(QWidget *parent = nullptr); ~QmitkMultiLabelManager() override; using LabelValueVectorType = mitk::LabelSetImage::LabelValueVectorType; /** * @brief Retrieve the currently selected labels (equals the last CurrentSelectionChanged values). */ LabelValueVectorType GetSelectedLabels() const; mitk::LabelSetImage* GetMultiLabelSegmentation() const; mitk::DataNode* GetMultiLabelNode() const; Q_SIGNALS: /** * @brief A signal that will be emitted if the selected labels change. * * @param labels A list of label values that are now selected. */ void CurrentSelectionChanged(const LabelValueVectorType& labels); /** * @brief A signal that will be emitted if the user has requested to "go to" a certain label. * * Going to a label would be e.g. to focus the render windows on the centroid of the label. * @param label The label that should be focused. * @param point in World coordinate that should be focused. */ void GoToLabel(mitk::LabelSetImage::LabelValueType label, const mitk::Point3D& point) const; /** @brief Signal that is emitted, if a label should be (re)named and default - * label naming is deactivated. - * - * The instance for which a new name is requested is passed with the signal. - * @param label Pointer to the instance that needs a (new) name. + * label naming is deactivated. + * + * The instance for which a new name is requested is passed with the signal. + * @param label Pointer to the instance that needs a (new) name. + * @param [in] rename Indicating if the signal was triggered for naming or renaming a label. * @param [out] canceled Indicating if the request was canceled by the used. */ void LabelRenameRequested(mitk::Label* label, bool rename, bool& canceled) const; public Q_SLOTS: /** * @brief Transform a list label values into a model selection and set this as a new selection of the view * * @param selectedLabels A list of data nodes that should be newly selected. */ void SetSelectedLabels(const LabelValueVectorType& selectedLabels); /** * @brief Selects the passed label instance and sets a new selection of the view * * @param selectedLabel Value of the label instance that should be selected. */ void SetSelectedLabel(mitk::LabelSetImage::LabelValueType selectedLabel); /** * @brief Sets the segmentation that will be used /monitored by the widget. * * @param segmentation A pointer to the segmentation to set. * @remark You cannot set the segmentation directly if a segmentation node is * also set. Reset the node (nullptr) if you want to change to direct segmentation * setting. * @pre Segmentation node is nullptr. */ void SetMultiLabelSegmentation(mitk::LabelSetImage* segmentation); /** * @brief Sets the segmentation node that will be used /monitored by the widget. * * @param node A pointer to the segmentation node. * @remark If not set some features of the manager are not active */ void SetMultiLabelNode(mitk::DataNode* node); void SetDataStorage(mitk::DataStorage *storage); void UpdateControls(); virtual void setEnabled(bool enabled); QStringList &GetLabelStringList(); void SetDefaultLabelNaming(bool defaultLabelNaming); private Q_SLOTS: // LabelSet dependent void OnRenameLabelShortcutActivated(); // reaction to "returnPressed" signal from ... void OnSearchLabel(); // reaction to the change of labels. If multiple labels are selected, it is ignored. void OnSelectedLabelChanged(const LabelValueVectorType& labels); // LabelSetImage Dependent void OnCreateDetailedSurface(bool); void OnCreateSmoothedSurface(bool); // reaction to the signal "createMask" from QmitkLabelSetTableWidget void OnCreateMask(bool); // reaction to the signal "createCroppedMask" from QmitkLabelSetTableWidget void OnCreateCroppedMask(bool); void OnSavePreset(); void OnLoadPreset(); void OnGoToLabel(mitk::LabelSetImage::LabelValueType label, const mitk::Point3D& position) const; void OnLabelRenameRequested(mitk::Label* label, bool rename, bool& canceled) const; void OnModelUpdated(); void OnSegmentationChanged(); private: enum TableColumns { NAME_COL = 0, LOCKED_COL, COLOR_COL, VISIBLE_COL }; void WaitCursorOn(); void WaitCursorOff(); void RestoreOverrideCursor(); void OnThreadedCalculationDone(); void AddSegmentationObserver(); void RemoveSegmentationObserver(); void OnLabelEvent(mitk::LabelSetImage::LabelValueType labelValue); void OnGroupEvent(mitk::LabelSetImage::GroupIndexType groupIndex); Ui::QmitkMultiLabelManagerControls* m_Controls; QCompleter *m_Completer; QStringList m_OrganColors; QStringList m_LabelStringList; bool m_ProcessingManualSelection; mitk::DataStorage* m_DataStorage; mitk::ITKEventObserverGuard m_LabelAddedObserver; mitk::ITKEventObserverGuard m_LabelModifiedObserver; mitk::ITKEventObserverGuard m_LabelRemovedObserver; mitk::ITKEventObserverGuard m_GroupAddedObserver; mitk::ITKEventObserverGuard m_GroupModifiedObserver; mitk::ITKEventObserverGuard m_GroupRemovedObserver; }; #endif diff --git a/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.h b/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.h index f6d4c1c083..b33b1937ce 100644 --- a/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.h +++ b/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.h @@ -1,262 +1,261 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkSurfaceInterpolationController_h #define mitkSurfaceInterpolationController_h #include #include #include #include #include namespace mitk { class ComputeContourSetNormalsFilter; class CreateDistanceImageFromSurfaceFilter; class LabelSetImage; class ReduceContourSetFilter; class MITKSURFACEINTERPOLATION_EXPORT SurfaceInterpolationController : public itk::Object { public: mitkClassMacroItkParent(SurfaceInterpolationController, itk::Object); itkFactorylessNewMacro(Self); itkCloneMacro(Self); struct MITKSURFACEINTERPOLATION_EXPORT ContourPositionInformation { Surface::ConstPointer Contour; PlaneGeometry::ConstPointer Plane; Label::PixelType LabelValue; TimeStepType TimeStep; ContourPositionInformation() : Plane(nullptr), LabelValue(std::numeric_limits::max()), TimeStep(std::numeric_limits::max()) { } ContourPositionInformation(Surface::ConstPointer contour, PlaneGeometry::ConstPointer plane, Label::PixelType labelValue, TimeStepType timeStep) : Contour(contour), Plane(plane), LabelValue(labelValue), TimeStep(timeStep) { } bool IsPlaceHolder() const { return Contour.IsNull(); } }; typedef std::vector CPIVector; static SurfaceInterpolationController *GetInstance(); /** * @brief Adds new extracted contours to the list. If one or more contours at a given position * already exist they will be updated respectively */ void AddNewContours(const std::vector& newCPIs, bool reinitializeAction = false, bool silent = false); /** * @brief Removes the contour for a given plane for the current selected segmentation * @param contourInfo the contour which should be removed * @param keepPlaceholderForUndo * @return true if a contour was found and removed, false if no contour was found */ bool RemoveContour(ContourPositionInformation contourInfo, bool keepPlaceholderForUndo = false); void RemoveObservers(); /** * @brief Performs the interpolation. * */ void Interpolate(const LabelSetImage* segmentationImage, LabelSetImage::LabelValueType labelValue, TimeStepType timeStep); /** * @brief Get the Result of the interpolation operation. * * @return mitk::Surface::Pointer */ mitk::Surface::Pointer GetInterpolationResult(const LabelSetImage* segmentationImage, LabelSetImage::LabelValueType labelValue, TimeStepType timeStep); /** * @brief Sets the minimum spacing of the current selected segmentation * This is needed since the contour points we reduced before they are used to interpolate the surface. * * @param minSpacing Parameter to set - * @param minSpacing Parameter to set */ void SetMinSpacing(double minSpacing); /** * @brief Sets the minimum spacing of the current selected segmentation * This is needed since the contour points we reduced before they are used to interpolate the surface * @param maxSpacing Set the max Spacing for interpolation */ void SetMaxSpacing(double maxSpacing); /** * Sets the volume i.e. the number of pixels that the distance image should have * By evaluation we found out that 50.000 pixel delivers a good result */ void SetDistanceImageVolume(unsigned int distImageVolume); /** * @brief Get the current selected segmentation for which the interpolation is performed * @return the current segmentation image */ mitk::LabelSetImage* GetCurrentSegmentation(); void SetDataStorage(DataStorage::Pointer ds); /** * Sets the current list of contourpoints which is used for the surface interpolation * @param currentSegmentationImage The current selected segmentation */ void SetCurrentInterpolationSession(LabelSetImage* currentSegmentationImage); /** * @brief Remove interpolation session * @param segmentationImage the session to be removed */ void RemoveInterpolationSession(const LabelSetImage* segmentationImage); /** * @brief Removes all sessions */ void RemoveAllInterpolationSessions(); /** * @brief Get the Contours at a certain timeStep and layerID. * * @param timeStep Time Step from which to get the contours. * @param labelValue label from which to get the contours. * @return std::vector Returns contours. */ CPIVector* GetContours(LabelSetImage::LabelValueType labelValue, TimeStepType timeStep); std::vector GetAffectedLabels(const LabelSetImage* seg, TimeStepType timeStep, const PlaneGeometry* plane) const; /** * @brief Triggered with the "Reinit Interpolation" action. The contours are used to repopulate the * @brief Triggered with the "Reinit Interpolation" action. The contours are used to repopulate the * surfaceInterpolator data structures so that interpolation can be performed after reloading data. */ void CompleteReinitialization(const std::vector& newCPIs); /** * @brief Removes contours of a particular label and at a given time step for the current session/segmentation. * * @param segmentationImage * @param label Label of contour to remove. * @param timeStep Time step in which to remove the contours. * @remark if the label or time step does not exist, nothing happens. */ void RemoveContours(const LabelSetImage* segmentationImage, mitk::Label::PixelType label, TimeStepType timeStep); /** * @brief Removes contours of a particular label and at a given time step for the current session/segmentation. * * @param segmentationImage * @param label Label of contour to remove. * @remark if the label or time step does not exist, nothing happens. */ void RemoveContours(const LabelSetImage* segmentationImage, mitk::Label::PixelType label); unsigned int GetNumberOfInterpolationSessions(); /** * @brief Get the Segmentation Image Node object * * @return DataNode* returns the DataNode containing the segmentation image. */ mitk::DataNode* GetSegmentationImageNode() const; protected: SurfaceInterpolationController(); ~SurfaceInterpolationController() override; template void GetImageBase(itk::Image *input, itk::ImageBase<3>::Pointer &result); private: /** * @brief * * @param caller * @param event */ void OnSegmentationDeleted(const itk::Object *caller, const itk::EventObject &event); /** * @brief Function that removes contours of a particular label when the "Remove Label" event is triggered in the labelSetImage. * @brief Function that removes contours of a particular label when the "Remove Label" event is triggered in the labelSetImage. * */ void OnRemoveLabel(const itk::Object* caller, const itk::EventObject& event); /** * @brief When a new contour is added to the pipeline or an existing contour is replaced, * the plane geometry information of that contour is added as a child node to the * current node of the segmentation image. This is useful in the retrieval of contour information * when data is reloaded after saving. * * @param contourInfo contourInfo struct to add to data storage. */ void AddPlaneGeometryNodeToDataStorage(const ContourPositionInformation& contourInfo) const; DataStorage::SetOfObjects::ConstPointer GetPlaneGeometryNodeFromDataStorage(const DataNode* segNode) const; DataStorage::SetOfObjects::ConstPointer GetPlaneGeometryNodeFromDataStorage(const DataNode* segNode, LabelSetImage::LabelValueType labelValue) const; DataStorage::SetOfObjects::ConstPointer GetPlaneGeometryNodeFromDataStorage(const DataNode* segNode, LabelSetImage::LabelValueType labelValue, TimeStepType timeStep) const; /** * Adds Contours from the active Label to the interpolation pipeline */ void AddActiveLabelContoursForInterpolation(ReduceContourSetFilter* reduceFilter, const LabelSetImage* segmentationImage, LabelSetImage::LabelValueType labelValue, TimeStepType timeStep); /** * @brief Clears the interpolation data structures. Called from CompleteReinitialization(). * */ void ClearInterpolationSession(); void RemoveObserversInternal(const mitk::LabelSetImage* segmentationImage); /** * @brief Add contour to the interpolation pipeline * * @param contourInfo Contour information to be added * @param reinitializationAction If the contour is coming from a reinitialization process or not */ void AddToCPIMap(ContourPositionInformation& contourInfo, bool reinitializationAction = false); unsigned int m_DistanceImageVolume; mitk::DataStorage::Pointer m_DataStorage; WeakPointer m_SelectedSegmentation; }; } #endif diff --git a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation.dox b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation.dox index 16a151964a..4769a83543 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation.dox +++ b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation.dox @@ -1,520 +1,520 @@ /** \page org_mitk_views_segmentation The Segmentation View \imageMacro{segmentation-dox.svg,"Icon of the Segmentation View",2.00} \tableofcontents \section org_mitk_views_segmentationoverview Overview Segmentation is the act of separating an image into foreground and background subsets by manual or automated delineation, while the foreground is defined to be part of the segmentation. Such a segmented image subset is also called a label as it typically labels a specific region of interest. A multilabel segmentation may contain multiple labels organized in distinct groups. You can create multiple labels for different regions of interest contained within a single segmentation image. Labels in the same group cannot overlap each other but labels from different groups may overlap. The MITK Segmentation Plugin allows you to create multilabel segmentations of anatomical and pathological structures in medical images. The plugin consists of three views:
  • Segmentation View: Manual and (semi-)automatic segmentation
  • \subpage org_mitk_views_segmentationutilities : Post-processing of segmentations
  • \subpage org_mitk_views_segmentationtasklist : Optimized workflow for batches of segmentation tasks based on a user-defined task list
In this user guide, the features of the Segmentation View are described. For an introduction to the Segmentation Utilities or Segmentation Task List, refer to the respective user guides. \imageMacro{QmitkSegmentationPlugin_Overview.png,"Segmentation View", 16.00} \section org_mitk_views_segmentationtechnicalissues Image and segmentation prerequisites The Segmentation View has a few prerequisites regarding segmentations and their reference image:
  • Images must be two or three-dimensional and may be either static or dynamic, e.g., are time-resolved resp. have different pixel values for different time steps.
  • Images must be single-valued, i.e. CT, MRI or ultrasound. Images from color doppler or photographic (RGB) images are only partially supported (please be aware that some tools might not be compatible with this image type).
  • Segmentations must be congruent to their reference images.
\section org_mitk_views_segmentationdataselection Image selection and creating new segmentations To select a reference image for a new segmentation, click on the Image widget in the Data selection section at the very top of the Segmentation View. Choose an image from the displayed list of Data Manager images. Once an image is selected, a new segmentation for this reference image can be created by clicking the button right next to the Segmentation widget in the Data selection section. A new multilabel segmentation with an initial, empty label is automatically generated if not set otherwise in the preferences. The new segmentation will be added to the Data Manager as a child node of its reference image node. It is automatically selected and can be edited in the Segmentation View right away. Instead of creating a new segmentation, an existing segmentation can be selected and edited as well. The selection list of existing segmentations for a certain reference image consists of matching/congruent segmentations only. \imageMacro{"QmitkSegmentation_DataSelection.png","Data selection and creating new segmentations",12} \section org_mitk_views_segmentationgroups Groups Segmentation images consist of at least a single group called "Group 0" in which the first default label is created. More groups can be added and removed but there will always be at least a single group. Labels of the same group cannot overlap each other. Labels of different groups may overlap each other. For example, you could segment the whole heart as "Heart" label in "Group 0", add "Group 1" and create multiple labels of the anatomical details of the heart in that group. Naturally, all these labels lie within the extents of the "Heart" label of "Group 0" but in principle they are completely independent of "Group 0". Some pixels are now labelled twice, e.g., as "Heart" and "Left ventricle". Since the labels of "Group 1" cannot overlap each other, it is impossible to accidentally label a pixel as both "Left ventricle" and "Right ventricle". If you would like to segment even more details you could create "Group 2" to have up to three labels per pixel. Nevertheless, groups are technically a flat data structure and cannot contain nested groups. It is all about possibly overlapping labels from distinct groups and spatially exclusive, non-overlapping labels within the same group. \imageMacro{"QmitkSegmentation_Groups.png","Groups",10} \section org_mitk_views_segmentationlabelinstances Labels vs. label instances The Segmentation View supports label instances. That is, segmenting multiple distributed entities of the same thing like metastases for example. A label, as we used the term before, is already a single instance of itself but it may consist of multiple label instances. If a label consists of multiple label instances, they each show their own distinct pixel value in square brackets as a clue for distinction and identification. It is important to understand that this number is not a separate, consecutive index for each label. It is just the plain pixel value of the label instance, which is unique across all label instances of the whole segmentation. \imageMacro{"QmitkSegmentation_LabelInstances.png","Label instances",10} \section org_mitk_views_segmentationlock_color_visibility Unlocking, changing color of, and hiding label instances Label instances are locked by default: label instances from the same group cannot accidentally override pixels from other label instances. Locked label instances behave like cookie cutters for other label instances of the same group. You can unlock label instances to remove that protection from other label instances of the same group. Their pixel contents can then be overridden by other label instances of the same group. Remember that label instances from distinct groups do not interact with each other. They can always overlap (not override) each other. You can also change the color of label instances as well as show (default) or hide their pixel contents. The icons at the right side of the rows of the groups and labels widget reflect their state in all these regards. Renaming of labels and label instances can be found in their content menu as shown further below. \imageMacro{"QmitkSegmentation_LockColorVisibility.png","Unlocking\, changing color of\, and hiding label instances",10} \section org_mitk_views_segmentationlabelhighlighting Label highlighting Especially if you have segmentations with many label instances or the label names are not telling, it can be nontrivial to identify the label instance in the label inspector of the segmentation view. To mitigate this problem MITK uses label highlighting in the render windows. As long as you hover with the mouse cursor over a group, label or label instance, the respective label instances will be highlighted in the render windows. Highlighted labels will visually pop out by being shown with full opacity (no transparency) while the opacity of all non-highlighted labels of the same segmentation will be reduced to 30% of the current opacity value (they become very transparent). By default, label instances that are set to be invisible are not shown while highlighted. By pressing the shift key, one can enforce also invisible label instances to be shown while highlighting. Remark: the highlighting is supported in all views that use the label inspector (e.g. also the segmentation utilities). \section org_mitk_views_segmentationcontextmenus Context menus Actions for organization of groups, labels, and label instances (as well as other operations) can be also found in their right-click context menus. \imageMacro{"QmitkSegmentation_ContextMenus.png","Context menus of groups\, labels\, and label instances",12} Most actions available in these context menus are self-explanatory or were already described above by other means of access like the tool button bar for adding and removing groups, labels, and label instances. Labels and label instances can be renamed, while groups have fixed names. Note that renaming a label instance will make a separate label out of it, since all instances of the same label share a single common name. Clear content only clears the pixels of a label instance but won't delete the actual label instance. Groups can be locked and unlocked as a whole from their context menu, while label instances can be directly locked and unlocked outside the context menu as described further below. \section org_mitk_views_segmentationlabelsuggestions Label name and color suggestions When renaming label instances or creating new label instances with enforced manual naming in the Segmentation preferences, entering names is supported by auto-completion for common label names. The list of predefined label names and colors for the auto-completion feature can be either extended or replaced by a custom list of label name and color suggestions. This custom list must be specified as a JSON file, just containing an array of objects, each with a mandatory "name" string and an optional "color" string. The JSON file can be set in the Segmentation preferences as well as a few options on how to apply these suggestions. \section org_mitk_views_segmentationlabelpresets Saving and loading label set presets Label set presets are useful to share a certain style or scheme between different segmentation sessions or to provide templates for new segmentation sessions. The properties of all label instances in all groups like their names, colors, and visibilities are saved as a label set preset by clicking on the 'Save label set preset' button. Label set presets are applied to any segmentation session by clicking on the 'Load label set preset' button. If a label instance for a certain value already exists, its properties are overridden by the preset. If a label instance for a certain value does not yet exist, an empty label instance with the label properties of the preset is created. The actual pixel contents of label instances are unaffected as label set presets only store label properties. \imageMacro{QmitkSegmentation_Preset.png,"Saving and loading label set presets", 10.00} If you work on a repetitive segmentation task, manually loading the same label set preset for each and every new segmentation can be tedious. To streamline your workflow, you can set a default label set preset in the Segmentation preferences (Ctrl+P). When set, this label set preset will be applied to all new segmentations instead of creating the default red "Label 1" label instance. If you work on a repetitive segmentation task, manually loading the same label set preset for each and every new segmentation can be tedious. To streamline your workflow, you can set a default label set preset in the Segmentation preferences (Ctrl+P). When set, this label set preset will be applied to all new segmentations instead of creating the default red "Label 1" label instance. \section org_mitk_views_segmentationpreferences Preferences The Segmentation Plugin offers a number of preferences which can be set via the MITK Workbench application preferences (Ctrl+P): \imageMacro{QmitkSegmentationPreferences.png,"Segmentation preferences", 10.00}
  • Compact view: Hide the tool button texts to save some space on screen (6 instead of 4 buttons per row)
  • 2D display: Draw segmentations as as outlines or transparent overlays
  • Data node selection mode: Hide everything but the selected segmentation and its reference image
  • Default label set preset: Start a new segmentation with this preset instead of a default label
  • Label creation: Assign default names and colors to new label instances or ask users for name and color
  • Label suggestions: Specify custom suggestions for label names and colors
\section org_mitk_views_segmentationtooloverview Segmentation tool overview MITK offers a comprehensive set of slice-based 2D and (semi-)automated 3D segmentation tools. The manual 2D tools require some user interaction and can only be applied to a single image slice whereas the 3D tools operate on the whole image. The 3D tools usually only require a small amount of user interaction, i.e. placing seed points or setting / adjusting parameters. You can switch between the different toolsets by selecting the 2D or 3D tab in the segmentation view. \imageMacro{QmitkSegmentation_ToolOverview.png,"An overview of the existing 2D and 3D tools in MITK.",5.50} \section org_mitk_views_segmentation2dsegmentation 2D segmentation tools With 2D manual contouring you define which voxels are part of the segmentation and which are not. This allows you to create segmentations of any structures of interest in an image. You can also use manual contouring to correct segmentations that result from sub-optimal automatic methods. The drawback of manual contouring is that you might need to define contours on many 2D slices. However, this is mitigated by the interpolation feature, which will make suggestions for a segmentation. To start using one of the editing tools, click its button from the displayed toolbox. The selected editing tool will be active and its corresponding button will stay pressed until you click the button again. Selecting a different tool also deactivates the previous one.\n If you have to delineate a lot of images, shortcuts to switch between tools becomes convenient. For that, just hit the first letter of each tool to activate it (A for Add, S for Subtract, etc.). All of the editing tools work by the same principle: using the mouse (left button) to click anywhere in a 2D window (any of the orientations axial, sagittal, or coronal), moving the mouse while holding the mouse button and releasing the button to finish the editing action. Multi-step undo and redo is fully supported by all editing tools by using the application-wide undo / redo buttons in the toolbar. Remark: Clicking and moving the mouse in any of the 2D render windows will move the crosshair that defines what part of the image is displayed. This behavior is disabled as long as any of the manual segmentation tools are active - otherwise you might have a hard time concentrating on the contour you are drawing. \subsection org_mitk_views_segmentationaddsubtracttools Add and subtract tools \imageMacro{QmitkSegmentation_IMGIconAddSubtract.png,"Add and subtract tools",7.70} Use the left mouse button to draw a closed contour. When releasing the mouse button, the contour will be added (Add tool) to or removed (Subtract tool) from the current segmentation. Adding and subtracting voxels can be iteratively repeated for the same segmentation. Holding CTRL / CMD while drawing will invert the current tool's behavior (i.e. instead of adding voxels, they will be subtracted). \subsection org_mitk_views_segmentationlassotool Lasso tool \imageMacro{QmitkSegmentation_Lasso.png,"Lasso tool",7.70} The tool is a more advanced version of the add/subtract tool. It offers you the following features:
  1. Generating a polygon segmentation (click left mouse button to set ancor point)
  2. Freehand contouring (like the add tool; press left mouse button while moving the mouse)
  3. Move ancor points (select an ancor point, press left mouse button while moving the mouse)
  4. Add new ancor points (press CTRL while click left mouse to add an ancor to the contour)
  5. Delete an ancor point (press Del while ancor point is selected)
  6. Segmentation can be added to the label (Add mode) or subtracted (Subtract mode)
To start a segmentation double left click where the first ancor point should be. To end the segmentation double left click where the last ancor point should be. Please note that:
  • feature 3-6 are only available, if auto confirm is *not* activated
  • feature 3-5 is not available for freehand contour segments
\subsection org_mitk_views_segmentationpaintwipetools Paint and wipe tools \imageMacro{QmitkSegmentation_IMGIconPaintWipe.png,"Paint and wipe tools",7.68} Use the Size slider to change the radius of the round paintbrush tool. Move the mouse in any 2D window and press the left button to draw or erase pixels. Holding CTRL / CMD while drawing will invert the current tool's behavior (i.e. instead of painting voxels, they will be wiped). \subsection org_mitk_views_segmentationregiongrowingtool Region growing tool \imageMacro{QmitkSegmentation_IMGIconRegionGrowing.png,"Region growing tool",3.81} Click at one point in a 2D slice widget to add an image region to the segmentation with the region growing tool. Region Growing selects all pixels around the mouse cursor that have a similar gray value as the pixel below the mouse cursor. This allows to quickly create segmentations of structures that have a good contrast to surrounding tissue. The tool operates based on the current level window, so changing the level window to optimize the contrast for the ROI is encouraged. Moving the mouse up / down is different from left / right: Moving up the cursor while holding the left mouse button widens the range for the included grey values; moving it down narrows it. Moving the mouse left and right will shift the range. The tool will select more or less pixels, corresponding to the changing gray value range. \if THISISNOTIMPLEMENTEDATTHEMOMENT A common issue with region growing is the so called "leakage" which happens when the structure of interest is connected to other pixels, of similar gray values, through a narrow "bridge" at the border of the structure. The Region Growing tool comes with a "leakage detection/removal" feature. If leakage happens, you can left-click into the leakage region and the tool will try to automatically remove this region (see illustration below). \imageMacro{QmitkSegmentation_Leakage.png,"Leakage correction feature of the region growing tool",11.28} \endif \subsection org_mitk_views_segmentationfilltool Fill tool \imageMacro{QmitkSegmentation_IMGIconFill.png,"Fill tool",3.81} Left-click inside a region/segmentation to flood fill all connected pixels that have the same label with the active label. This tool will only work on regions of unlocked labels or on regions that are not labeled at all. \subsection org_mitk_views_segmentationerasetool Erase tool \imageMacro{QmitkSegmentation_IMGIconErase.png,"Erase tool",3.79} This tool removes a connected part of pixels that form a segmentation. You may use it to remove single segmented regions by left-click on specific segmentation region. This tool will only work and regions of unlocked labels or on regions of the active label. \subsection org_mitk_views_segmentationclosetool Close tool \imageMacro{QmitkSegmentation_IMGIconClose.png,"Close tool",3.79} Left-click inside the region/segmentation to fill all "holes" (pixels labelled with another label or no label) inside the region. Therefore this tool behaves like a local closing operation. This tool will not work, when a non-labeled region is selected and holes of locked labels will not be filled. \remark This tool always uses the label of the selected region (even if this label is not the active label). Therefore you can use this tool on regions of the active label and of none locked labels (without the need to change the active label). \subsection org_mitk_views_segmentationlivewiretool Live wire tool \imageMacro{QmitkSegmentation_IMGIconLiveWire.png,"Live wire tool",3.01} The Live Wire Tool acts as a magnetic lasso with a contour snapping to edges of objects. \imageMacro{QmitkSegmentation_IMGLiveWireUsage.PNG,"Steps for using the Live Wire Tool",16.00} The tool handling is the same like the Lasso tool (see for more info), except it generates live wire contours instead of straight lines. \subsection org_mitk_views_segmentationSegmentAnything Segment Anything Tool \imageMacro{QmitkSegmentation_nnUnetTool.png,"Segment Anything tool",10.00} \imageMacro{QmitkSegmentation_SAMTool.png,"Segment Anything tool",10.00} The Segment Anything Tool is a deep learning-based interactive segmentation tool. Originally created by MetaAI, MITK presents this model for medical image segmentation tasks. The tool requires that you have Python 3 installed and available on your machine. Note: On Debian/Ubuntu systems, you need to install git, python3-pip, python3-venv package using the following command: `apt install git python3-pip python3-venv`. For best experience, your machine should be ideally equipped with a CUDA-enabled GPU. For a detailed explanation of what this algorithm is able to, please refer to https://ai.facebook.com/research/publications/segment-anything/
Any adjustments to the \subpage org_mitk_editors_stdmultiwidget_Levelwindow setting impacts the segmentation. However, any applied color maps are ignored. \subsubsection org_mitk_views_segmentationSegmentAnythingWorkflow Workflow: -# Install Segment Anything: Goto Preferences (Ctrl+P) > Segment Anything and click "Install Segment Anything with MedSAM" to install Segment Anything (version: 1.0). The installation process implicitly creates a python virtual environment using the Python located in "System Python" in an MITK mainitained directory. Make sure you have a working internet connection. This might take a while. It is a one time job, though. Once installed, the "Install Segment Anything" button is grayed out. \imageMacro{QmitkSegmentation_SAMTool_Preferences.png,"Segment Anything Preferences",10.00} -# Note: If Python is not listed by MITK in "System Python", click "Select..." in the dropdown to choose an unlisted installation of Python. Note that, while selecting an arbitrary environment folder, only select the base folder, e.g. "/usr/bin/". No need to navigate all the way into "../usr/bin/python3", for example. -# Select a specific model type in the "Model Type" dropdown. The default is "vit_b" for low memory footprint. However, more larger models "vit_l" and "vit_h" are also available for selection. -# Select inference hardware, i.e. any GPU or CPU. This is internally equivalent to setting the CUDA_VISIBLE_DEVICES environment variable. -# Click "OK" to save the preference settings. -# Goto Segmentation View > 2D tools > Segment Anything. -# Click "Initialize Segment Anything" to start the tool backend. This will invoke downloading of the selected model type from the internet. This might take a while. It is a one time job, though. -# Once the tool is initialized, Press SHIFT+Left Click on any of the 3 render windows to start click guided segmentation on that slice. -# Press SHIFT+Right Click for negative clicks to adjust the preview mask on the render window. \subsection org_mitk_views_segmentationMedSAM MedSAM Tool \imageMacro{QmitkSegmentation_nnUnetTool.png,"MedSAM tool",10.00} \imageMacro{QmitkSegmentation_MedSAMTool.png,"MedSAM tool",10.00} The MedSAM (Segment Anything in Medical Images) tool is a specialization of the the Segment Anything (SAM) tool. A new foundation model in the back end is dedicated to universal medical image segmentation. Just like the Segment Anything tool, the MedSAM tool requires that you have Python 3 installed and available on your machine. Note: On Debian/Ubuntu systems, you need to install the git, python3-pip, and python3-venv packages using the following command: `sudo apt install git python3-pip python3-venv`. For best experience, your machine should be ideally equipped with a CUDA-enabled GPU. Any adjustments to the \subpage org_mitk_editors_stdmultiwidget_Levelwindow setting impacts the segmentation. However, any applied color maps are ignored. \subsubsection org_mitk_views_segmentationMedSAMWorkflow Workflow -# Install MedSAM: Goto Preferences (Ctrl+P) > Segment Anything and click "Install Segment Anything with MedSAM" to install Segment Anything (version: 1.0) & MedSAM tool backends together. The installation process implicitly creates a python virtual environment using the Python located in "System Python" in a directory maintained by MITK. Make sure you have a working internet connection, which might take a while. It is a one-time job, though. Once installed, the "Install Segment Anything with MedSAM" button is grayed out. For details, refer to the Segment Anything tool workflow. -# Goto Segmentation View > 2D tools > MedSAM. -# Click "Initialize MedSAM" to start the tool. This will start downloading the model weights from the internet first, if not done before. This might take a while. It is a one-time job, though. -# Once the tool is initialized, press Shift+Left Click on any of the render windows to create a bounding box for that image slice. -# Click on the anchor points of the bounding box to adjust the region of interest. -# Click on Preview to generate segmentation from MedSAM model. Note: For a detailed explanation of what this algorithm is able to, please refer to https://www.nature.com/articles/s41467-024-44824-z \subsection org_mitk_views_segmentationMonaiLabel2D MONAI Label 2D Tool \imageMacro{QmitkSegmentation_nnUnetTool.png,"MONAILabel2D tool",10.00} Read more about the tool here at \subpage org_mitk_views_segmentationMonaiLabel3D \subsection org_mitk_views_segmentationinterpolation 2D and 3D Interpolation Creating segmentations using 2D manual contouring for large image volumes may be very time-consuming, because structures of interest may cover a large range of slices. The segmentation view offers two helpful features to mitigate this drawback:
  • 2D Interpolation
  • 3D Interpolation
The 2D Interpolation creates suggestions for a segmentation whenever you have a slice that
  • has got neighboring slices with segmentations (these do not need to be direct neighbors but could also be a couple of slices away) AND
  • is completely clear of a manual segmentation, i.e. there will be no suggestion if there is even only a single pixel of segmentation in the current slice.
\imageMacro{QmitkSegmentation_2DInterpolation.png,"2D interpolation usage",3.01} Interpolated suggestions are displayed as outlines, until you confirm them as part of the segmentation. To confirm single slices, click the Confirm for single slice button below the toolbox. You may also review the interpolations visually and then accept all of them at once by selecting Confirm for all slices. The 3D interpolation creates suggestions for 3D segmentations. That means if you start contouring, from the second contour onwards, the surface of the segmented area will be interpolated based on the given contour information. The interpolation works with all available manual tools. Please note that this is currently a pure mathematical interpolation, i.e. image intensity information is not taken into account. With each further contour the interpolation result will be improved, but the more contours you provide the longer the recalculation will take. To achieve an optimal interpolation result and in this way a most accurate segmentation you should try to describe the surface with sparse contours by segmenting in arbitrary oriented planes. The 3D interpolation is not meant to be used for parallel slice-wise segmentation, but rather segmentations in i.e. the axial, coronal and sagittal plane. \imageMacro{QmitkSegmentation_3DInterpolationWrongRight.png,"3D interpolation usage",16.00} You can accept the interpolation result by clicking the Confirm-button below the tool buttons. In this case the 3D interpolation will be deactivated automatically so that the result can be post-processed without any interpolation running in the background. Additional to the surface, black contours are shown in the 3D render window, which mark all the drawn contours used for the interpolation. You can navigate between the drawn contours by clicking on the corresponding position nodes in the data manager which are stored as sub-nodes of the selected segmentation. If you do not want to see these nodes just uncheck the Show Position Nodes checkbox and these nodes will be hidden. If you want to delete a drawn contour we recommend to use the Erase-Tool since undo / redo is not yet working for 3D interpolation. The current state of the 3D interpolation can be saved across application restart. For that, just click on save project during the interpolation is active. After restarting the application and load your project you can click on "Reinit Interpolation" within the 3D interpolation GUI area. \section org_mitk_views_segmentation3dsegmentation 3D segmentation tools The 3D tools operate on the whole image and require usually a small amount of interaction like placing seed-points or specifying certain parameters. All 3D tools provide an immediate segmentation feedback, which is displayed as a transparent green overlay. For accepting a preview you have to press the Confirm button of the selected tool. The following 3D tools are available: \subsection org_mitk_views_segmentation3dthresholdtool 3D Threshold tool The thresholding tool simply applies a 3D threshold to the patient image. All pixels with values equal or above the selected threshold are labeled as part of the segmentation. You can change the threshold by either moving the slider of setting a certain value in the spinbox. \imageMacro{QmitkSegmentation_3DThresholdTool.png,"3D Threshold tool",10.00} \subsection org_mitk_views_segmentation3dulthresholdTool 3D upper / lower threshold tool The Upper/Lower Thresholding tool works similar to the simple 3D threshold tool but allows you to define an upper and lower threshold. All pixels with values within this threshold interval will be labeled as part of the segmentation. \imageMacro{QmitkSegmentation_3DULThresholdTool.png,"3D upper / lower threshold tool",10.00} \subsection org_mitk_views_segmentation3dotsutool 3D Otsu tool The 3D Otsu tool provides a more sophisticated thresholding algorithm. It allows you to define a number of regions. Based on the image histogram the pixels will then be divided into different regions. The more regions you define the longer the calculation will take. You can select afterwards which of these regions you want to confirm as segmentation. \imageMacro{QmitkSegmentation_3DOtsuTool.png,"3D Otsu tool",10.00} \subsection org_mitk_views_segmentation3dgrowcuttool 3D GrowCut tool The 3D GrowCut tool uses previously created segmentation labels (e.g. by the "Add"-tool) stored in the segmentation layer 0. The GrowCut tool will use these segmentation labels to create a seedimage that will serve as input to the algorithm. As an advanced setting option, a Distance penalty can be set, which increases the cohesion in the immediate surroundings of the initial labels. Based on the seedimage and the Distance penalty, a growing is started, which includes all areas that are not initially assigned to a specific label. During this process, initially unassigned areas are assigned to the best fitting labels. After the segmentation process, the user can decide which newly generated labels should be confirmed. \imageMacro{QmitkSegmentation_3DGrowCutTool.png,"3D GrowCut tool",16.00} \subsection org_mitk_views_segmentationpickingtool Picking Tool The Picking tool offers two modes that allow you to manipulate "islands" within your segmentation. This is especially useful if e.g. a thresholding provided you with several areas within your image but you are just interested in one special region. - Picking mode: Allows you to select certain "islands". When the pick is confirmed, the complete content of the active label will be removed except the pick. This mode is beneficial if you have a lot segmentation noise and want to pick the relevant parts and dismiss the rest. Hint: You can also pick from other labels, but this will only work if these labels are unlocked. - Relabel mode: Allows you to select certain "islands". When the pick is confirmed, it will be relabeled and added to the active label content. Hint: This mode ignores the locks of other labels, hence you do not need to unlock them explicitly. \imageMacro{QmitkSegmentation_PickingTool.png,"Picking tool",10.00} \subsection org_mitk_views_segmentationnnUNetTool nnU-Net Tool (Ubuntu only) \imageMacro{QmitkSegmentation_nnUnetTool.png,"nnUNet tool",10.00} This tool provides a GUI to the deep learning-based segmentation algorithm called the nnU-Net v1. With this tool, you can get a segmentation mask predicted for the loaded image in MITK. Be ready with the pre-trained weights (a.k.a RESULTS_FOLDER) for your organ or task concerned, before using the tool. For a detailed explanation of the parameters and pre-trained weights folder structure etc., please refer to https://github.com/MIC-DKFZ/nnUNet.
Remark: The tool assumes that you have a Python3 environment with nnU-Net v1 (pip) installed. Your machine should be also equipped with a CUDA enabled GPU. \subsubsection org_mitk_views_segmentationnnUNetToolWorkflow Workflow: -# Select the "Python Path" drop-down to see if MITK has automatically detected other Python environments. Click on a fitting environment for the nnUNet inference or click "Select" in the dropdown to choose an unlisted python environment. Note that, while selecting an arbitrary environment folder, only select the base folder, e.g. "myenv". No need to select all the way until "../myenv/bin/python", for example. -# Click on the "nnUNet Results Folder" directory icon to navigate to the results folder on your hard disk. This is equivalent to setting the RESULTS_FOLDER environment variable. If your results folder is as per the nnUNet required folder structure, the configuration, trainers, tasks and folds are automatically parsed and correspondingly loaded in the drop-down boxes as shown below. Note that MITK automatically checks for the RESULTS_FOLDER environment variable value and, if found, auto parses that directory when the tool is started. \imageMacro{QmitkSegmentation_nnUNet_Settings.png,"nnUNet Segmentation Settings",10} -# Choose your required Task-Configuration-Trainer-Planner-Fold parameters, sequentially. By default, all entries are selected inside the "Fold" dropdown (shown: "All"). Note that, even if you uncheck all entries from the "Fold" dropdown (shown: "None"), then too, all folds would be considered for inferencing. -# For ensemble predictions, you will get the option to select parameters irrespective on postprocessing files available in the ensembles folder of RESULTS_FOLDER. Note that, if a postprocessing json file exists for the selected combination then it will used for ensembling, by default. To choose not to, uncheck the "Use PostProcessing JSON" in the "Advanced" section. \imageMacro{QmitkSegmentation_nnUNet_ensemble.png,"nnUNet Segmentation Settings",10} -# If your task is trained with multi-modal inputs, then "Multi-Modal" checkbox is checked and the no.of modalities are preloaded and shown next to "Required Modalities". Instantly, as much node selectors with corresponding modality names should appear below to select the Data Manager along including a selector with preselected with the reference node. Now, select the image nodes in the node selectors accordingly for accurate inferencing. \imageMacro{QmitkSegmentation_nnUNet_multimodal.png,"nnUNet Multi Modal Settings",10.00} -# Click on "Preview". -# In the "Advanced" section, you can also activate other options like "Mixed Precision" and "Enable Mirroring" (for test time data augmentation) pertaining to nnUNet. \imageMacro{QmitkSegmentation_nnUNet_Advanced.png,"nnUNet Advanced Settings",10.00} -# Use "Advanced" > "GPU Id" combobox to change the preferred GPU for inferencing. This is internally equivalent to setting the CUDA_VISIBLE_DEVICES environment variable. -# Every inferred segmentation is cached to prevent a redundant computation. In case, a user doesn't wish to cache a Preview, uncheck the "Enable Caching" in the "Advanced" section. This will ensure that the current parameters will neither be checked against the existing cache nor a segmentation be loaded from it when Preview is clicked. -# You may always clear all the cached segmentations by clicking "Clear Cache" button. \subsubsection org_mitk_views_segmentationnnUNetToolMisc Miscellaneous: -# In case you want to reload/reparse the folders in the "nnUNet Results Folder", eg. after adding new tasks into it, you may do so without reselecting the folder again by clicking the "Refresh Results Folder" button. -# The "Advanced" > "GPU Id" combobox lists the Nvidia GPUs available by parsing the nvidia-smi utility output. In case your machine has Nvidia CUDA enabled GPUs but the nvidia-smi fails for some reason, the "GPU Id" combobox will show no entries. In such a situation, it's still possible to execute inferencing by manually entering the preferred GPU Id, eg. 0 in the combobox. -# The "Advanced" > "Available Models" lists the available pre-trained tasks for download. Make sure you have internet connection. Then, choose a Task from the dropdown and click the Download button. The pre-trained models for the selected Task will be downloaded and placed to the RESULTS_FOLDER directory automatically. -# In the RESULTS_FOLDER directory, inside the trainer-planner folder of every task, MITK keeps a "mitk_export.json" file for fast loading for multi-modal information. It is recommended not to delete this file(s) for a fast responsive UI. Tip: If multi-modal information shown on MITK is not correct for a given task, you may modify this JSON file and try again. \subsection org_mitk_views_segmentationTotalSegmentator TotalSegmentator Tool \imageMacro{QmitkSegmentation_nnUnetTool.png,"TotalSegmentator tool",10.00} This tool provides a GUI to the deep learning-based segmentation algorithm called the TotalSegmentator (v2). With this tool, you can get a segmentation mask predicted for 117 classes in CT images, loaded in MITK. For a detailed explanation on tasks and supported classes etc., please refer to https://github.com/wasserth/TotalSegmentator
The tool assumes that you have Python >= 3.9 installed and available on your machine. We recommend to install TotalSegmentator via MITK. The "Install TotalSegmentator" action implicitly creates a python virtual environment in an MITK mainitained directory. Note: on Debian/Ubuntu systems, you need to install the python3-venv package using the following command: `apt install python3-venv`. For best results, your machine should be ideally equipped with a CUDA-enabled GPU. \imageMacro{QmitkSegmentation_TotalsegmentatorTool.png, "TotalSegmentator Settings",5} \subsubsection org_mitk_views_segmentationTotalSegmentatorWorkflow Workflow: -# Install TotalSegmentator: Click "Install TotalSegmentator" to install TotalSegmentator (version: 2.0.5) in a virtual environment. Make sure you have a working internet connection. This might take a while. It is a one time job, though. Once installed, the "Install TotalSegmentator" button is grayed out. -# If Python is not found by MITK goto "Install Options" & select the "System Python Path" drop-down to see if MITK has automatically detected other Python environments. Click on a fitting Python installation for TotalSegmentator to use or click "Select" in the dropdown to choose an unlisted installation of Python. Note that, while selecting an arbitrary environment folder, only select the base folder, e.g. "/usr/bin/". No need to navigate all the way into "../usr/bin/python3", for example. -# Select a specific subtask in the "Tasks" drop-downs. The default is "total" for non-specific total segmentation. -# Click on "Run TotalSegmentator" for a preview. -# In the "Advanced" section, you can also activate other options like "Fast" for faster runtime and less memory requirements. Use "Fast" if you only have a CPU for inferencing. -# Use "Advanced" > "GPU Id" combobox to change the preferred GPU for inferencing. This is internally equivalent to setting the CUDA_VISIBLE_DEVICES environment variable. -# In case you want to use your own virtual environment containing TotalSegmentator, goto "Install Options" & check "Use Custom Installation" checkbox. Then, select the environment of your choice by using "Custom Env. Path". \subsection org_mitk_views_segmentationMonaiLabel3D MONAI Label 3D Tool \imageMacro{QmitkSegmentation_nnUnetTool.png,"MONAILabel3D tool",10.00} \imageMacro{QmitkSegmentation_MonaiLabelTool.png,"MONAI Label tool",10.00} MONAI Label is a server-client system that facilitates interactive medical image annotation by using AI. MONAI Label typically hosts different "Apps", e.g. Radiology or Pathology. Each App hosts a set of pretrained models on pertaining datasets. MITK is tested for the Radiology app and only supports auto and click-based "deepgrow" models. For internal reasons, MITK doesn't support "deepedit" and "localization_spine" models specifically. The tool requires that you have a URL to a (self-) hosted MONAI Label server. For a detailed explanation of what MONAI Label is capable of, please refer to https://docs.monai.io/projects/label/en/latest/.
Any adjustments to the \subpage org_mitk_editors_stdmultiwidget_Levelwindow setting impacts the segmentation. However, any applied color maps are ignored. -\subsubsection org_mitk_views_segmentationSegmentAnythingWorkflow Workflow: +\subsubsection org_mitk_views_segmentationMonaiLabelWorkflow Workflow: -# Enter your URL in the "MONAI Server URL" box and press the refresh button next to it. This connects MITK to the MONAI Label server and fetches exposed metadata. -# Select the Radiology app e.g. from the "Available Apps" combobox. This will load all supported model names in the "Models" combo box below it. -# From the "Models" box select the model you want to use. Immediately, all supported classes of the model on which the model was trained will be shown below it. -# If an interactive model is selected e.g. "deepgrow_3d", Press and hold SHIFT and left click on any of the 3 render windows to start click guided segmentation. Press SHIFT+Right click for negative clicks to adjust the preview mask on the render window. -# If an auto-segmentation model is selected e.g. "deepedit_seg", simply click "Preview" for generating masks. \section org_mitk_views_segmentationpostprocessing Additional things you can do with segmentations Segmentations are never an end in themselves. Consequently, the segmentation view adds a couple of "post-processing" actions, accessible through the context-menu of the data manager. \imageMacro{QmitkSegmentation_IMGDataManagerContextMenu.png,"Context menu items for segmentations",10.58}
  • Create polygon %model applies the marching cubes algorithm to the segmentation. This polygon %model can be used for visualization in 3D or other applications such as stereolithography (3D printing).
  • Create smoothed polygon %model uses smoothing in addition to the marching cubes algorithm, which creates models that do not follow the exact outlines of the segmentation, but look smoother.
  • Autocrop can save memory. Manual segmentations have the same extent as the patient image, even if the segmentation comprises only a small sub-volume. This invisible and meaningless margin is removed by autocropping.
\section org_mitk_views_segmentationof3dtimages Segmentation of 3D+t images For segmentation of 3D+t images, some tools give you the option to choose between creating dynamic and static masks.
  • Dynamic masks can be created on each time frame individually.
  • Static masks will be defined on one time frame and will be the same for all other time frames.
In general, segmentation is applied on the time frame that is selected when execution is performed. If you alter the time frame, the segmentation preview is adapted. */