Skip to content

Commit

Permalink
ENH: Load DICOM files using DICOM module by default (#7077)
Browse files Browse the repository at this point in the history
Drag-and-dropping a DICOM file made Slicer use ITK DICOM reader, which often did not load the data correctly.

This commit adds a new "DICOM import" reader type, which is selected by default for DICOM files. This reader imports the files into the Slicer DICOM database and switches to the DICOM module.

The workaround of interpreting all files as DICOM by default while in DICOM module has now been removed. "Add data" window is brought up when loading a file, regardless of what is the active module. The workaround surprised users when they could not load non-DICOM data while they were in DICOM module.

If a user wants to read the DICOM file using ITK reader, it is possible by changing "Description" in the "Add data" module to "Volume".

fixes #5726
  • Loading branch information
lassoan committed Jul 10, 2023
1 parent 799ebba commit ff1ba16
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 62 deletions.
2 changes: 1 addition & 1 deletion Base/QTCore/qSlicerScriptedFileReader.cxx
Expand Up @@ -278,7 +278,7 @@ double qSlicerScriptedFileReader::canLoadFileConfidence(const QString& file)cons
return this->Superclass::canLoadFileConfidence(file);
}

if (PyFloat_Check(result))
if (!PyFloat_Check(result))
{
qWarning() << d->PythonSource
<< " - In" << d->PythonClassName << "class, function 'canLoadFileConfidence' "
Expand Down
2 changes: 1 addition & 1 deletion Docs/user_guide/modules/dicom.md
Expand Up @@ -62,7 +62,7 @@ Since DICOM files are often located in several folders, they can cross-reference

:::{note}

When a folder is drag-and-dropped to the Slicer application while not the DICOM module is active, Slicer displays a popup, asking what to do - click OK ("Load directory in DICOM database"). After import is completed, go to DICOM module.
When a folder is drag-and-dropped to the Slicer application, Slicer displays a popup, asking what to do - click OK ("Load directory in DICOM database"). After import is completed, go to DICOM module.

:::

Expand Down
98 changes: 38 additions & 60 deletions Modules/Scripted/DICOM/DICOM.py
Expand Up @@ -531,57 +531,6 @@ def dropEvent(self):
self.directoriesToAdd = []


class DICOMLoadingByDragAndDropEventFilter(qt.QWidget):
"""This event filter is used for overriding drag-and-drop behavior while
the DICOM module is active. To simplify DICOM import, while DICOM module is active
then files or folders that are drag-and-dropped to the application window
are always interpreted as DICOM data.
"""

def eventFilter(self, object, event):
"""
Custom event filter for Slicer Main Window.
Inputs: Object (QObject), Event (QEvent)
"""
if event.type() == qt.QEvent.DragEnter:
self.dragEnterEvent(event)
return True
if event.type() == qt.QEvent.Drop:
self.dropEvent(event)
return True
return False

def dragEnterEvent(self, event):
"""
Actions to do when a drag enter event occurs in the Main Window.
Read up on https://doc.qt.io/qt-5.12/dnd.html#dropping
Input: Event (QEvent)
"""
self.directoriesToAdd, self.filesToAdd = DICOMFileDialog.pathsFromMimeData(event.mimeData())
if self.directoriesToAdd or self.filesToAdd:
event.acceptProposedAction() # allows drop event to proceed
else:
event.ignore()

def dropEvent(self, event):
if not DICOMFileDialog.createDefaultDatabase():
return

if not DICOMFileDialog.validDirectories(self.directoriesToAdd) or not DICOMFileDialog.validDirectories(self.filesToAdd):
confirmMessage = _(
"Import from folders with special (non-ASCII) characters in the name is not supported."
" It is recommended to move files into a different folder and retry."
" Try to import from current location anyway?")
if not slicer.util.confirmYesNoDisplay(confirmMessage):
self.directoriesToAdd = []
return

slicer.modules.DICOMInstance.browserWidget.dicomBrowser.importDirectories(self.directoriesToAdd)
slicer.modules.DICOMInstance.browserWidget.dicomBrowser.importFiles(self.filesToAdd)


#
# DICOM widget
#
Expand All @@ -605,8 +554,6 @@ def setup(self):
if hasattr(self, "reloadCollapsibleButton"):
self.reloadCollapsibleButton.collapsed = True

self.dragAndDropEventFilter = DICOMLoadingByDragAndDropEventFilter()

globals()['d'] = self

self.testingServer = None
Expand Down Expand Up @@ -746,15 +693,8 @@ def enter(self):
self.onOpenBrowserWidget()
self.addListenerObservers()
self.onListenerStateChanged()
# While DICOM module is active, drag-and-drop always performs DICOM import
mw = slicer.util.mainWindow()
if mw:
mw.installEventFilter(self.dragAndDropEventFilter)

def exit(self):
mw = slicer.util.mainWindow()
if mw:
mw.removeEventFilter(self.dragAndDropEventFilter)
self.removeListenerObservers()
self.browserWidget.close()

Expand Down Expand Up @@ -948,3 +888,41 @@ def onClearDatabase(self):
slicer.app.setOverrideCursor(qt.Qt.WaitCursor)
DICOMLib.clearDatabase(slicer.dicomDatabase)
slicer.app.restoreOverrideCursor()


class DICOMFileReader:
"""This reader claims DICOM files with higher-than default confidence
"""

def __init__(self, parent):
self.parent = parent

def description(self):
return 'DICOM import'

def fileType(self):
return 'DICOMFileImport'

def extensions(self):
return ['DICOM (*.dcm)', 'DICOM (*)']

def canLoadFileConfidence(self, filePath):
import pydicom
if pydicom.misc.is_dicom(filePath):
# This is a DICOM file, so we return higher confidence than the default 0.5
# to import DICOM files using DICOM module.
return 0.6
else:
return 0.0

def load(self, properties):

filePath = properties['fileName']
dicomFilesDirectory = os.path.dirname(os.path.abspath(filePath))

# instantiate a new DICOM browser (and create DICOM database if not created yet)
slicer.util.selectModule("DICOM")
dicomBrowser = slicer.modules.DICOMWidget.browserWidget.dicomBrowser
dicomBrowser.importDirectory(dicomFilesDirectory, dicomBrowser.ImportDirectoryMode)

return True

0 comments on commit ff1ba16

Please sign in to comment.