Skip to content

Commit

Permalink
ENH: Make it easier to export multiple series under the same DICOM study
Browse files Browse the repository at this point in the history
When attempting to export a data node that is not in a DICOM hierarchy (patient/study) then Slicer offered to create these items automatically.

Previously, the items were not initialized with any IDs and instead the IDs were generated in the exported files.
However, this made all the exported items have a different patient and study instance.
If the user wanted to add more series to the study then the user either had to manually specify IDs or had to load the data sets from the DICOM database to get the automatically generated IDs.

This commit changes the behavior so that the automatically created patient and study items have valid IDs in them, making it easy to export multiple series under the same DICOM patient/study.
  • Loading branch information
lassoan committed Oct 18, 2022
1 parent 0244f12 commit 899e36a
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 15 deletions.
Expand Up @@ -34,6 +34,7 @@
// Qt includes
#include <QDebug>
#include <QAction>
#include <QRandomGenerator>
#include <QStandardItem>
#include <QMessageBox>

Expand All @@ -48,6 +49,9 @@
// STD includes
#include <algorithm>

// DCMTK includes
#include "dcmtk/dcmdata/dcuid.h"

//-----------------------------------------------------------------------------
/// \ingroup Slicer_QtModules_SubjectHierarchy_Widgets
class qSlicerSubjectHierarchyDICOMPluginPrivate: public QObject
Expand Down Expand Up @@ -479,7 +483,7 @@ void qSlicerSubjectHierarchyDICOMPlugin::openDICOMExportDialog()
}
if (!validSelection)
{
QString message = QString("Data to export need to be under a study item with a parent patient.\n"
QString message = tr("Data to export need to be under a study item with a parent patient.\n"
"Default patient and study will be created and the selected data and its related datasets "
"will be moved in it for export.\n\n"
"If you'd like to create the hierarchy manually, please click Cancel, "
Expand All @@ -491,16 +495,11 @@ void qSlicerSubjectHierarchyDICOMPlugin::openDICOMExportDialog()
QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok);
if (answer == QMessageBox::Ok)
{
// Generate new patient name
std::string patientName = vtkMRMLSubjectHierarchyConstants::GetSubjectHierarchyNewItemNamePrefix()
+ vtkMRMLSubjectHierarchyConstants::GetDICOMLevelPatient();
patientName = shNode->GenerateUniqueItemName(patientName);

if (shNode->IsItemLevel(currentItemID, vtkMRMLSubjectHierarchyConstants::GetDICOMLevelStudy()))
{
// Create parent patient
vtkIdType patientItemID = shNode->CreateSubjectItem(shNode->GetSceneItemID(), patientName);

vtkIdType patientItemID = this->createDefaultPatientItem();
// Move existing study under new patient
shNode->SetItemParent(currentItemID, patientItemID);
}
Expand Down Expand Up @@ -541,21 +540,16 @@ void qSlicerSubjectHierarchyDICOMPlugin::openDICOMExportDialog()
// Create new study if no referenced data item is in a study already
if (!studyItemID)
{
// Generate new study name
std::string studyName = vtkMRMLSubjectHierarchyConstants::GetSubjectHierarchyNewItemNamePrefix()
+ vtkMRMLSubjectHierarchyConstants::GetDICOMLevelStudy();
studyName = shNode->GenerateUniqueItemName(studyName);
// Create new study
studyItemID = shNode->CreateStudyItem(shNode->GetSceneItemID(), studyName);
studyItemID = this->createDefaultStudyItem();

// Create parent patient for new study
vtkIdType patientItemID = shNode->CreateSubjectItem(shNode->GetSceneItemID(), patientName);
vtkIdType patientItemID = this->createDefaultPatientItem();
shNode->SetItemParent(studyItemID, patientItemID);
}
else if (!shNode->GetItemAncestorAtLevel(studyItemID, vtkMRMLSubjectHierarchyConstants::GetDICOMLevelPatient()))
{
// Create parent patient for found study if it had none
vtkIdType patientItemID = shNode->CreateSubjectItem(shNode->GetSceneItemID(), patientName);
vtkIdType patientItemID = this->createDefaultPatientItem();
shNode->SetItemParent(studyItemID, patientItemID);
}

Expand All @@ -581,3 +575,60 @@ void qSlicerSubjectHierarchyDICOMPlugin::openDICOMExportDialog()

delete exportDialog;
}

//---------------------------------------------------------------------------
vtkIdType qSlicerSubjectHierarchyDICOMPlugin::createDefaultPatientItem()
{
vtkMRMLSubjectHierarchyNode* shNode = qSlicerSubjectHierarchyPluginHandler::instance()->subjectHierarchyNode();
if (!shNode)
{
qCritical() << Q_FUNC_INFO << ": Failed to access subject hierarchy node";
return vtkMRMLSubjectHierarchyNode::INVALID_ITEM_ID;
}

// Generate patient name
QString patientName = tr("Anonymous");

// Generate random patient ID
const int patientIdLength = 6;
QString patientID;
for (int i = 0; i < patientIdLength; ++i)
{
patientID += QChar('A' + char(QRandomGenerator::global()->generate() % ('Z' - 'A')));
}

QString patientItemName = QString("%1 (%2)").arg(patientName).arg(patientID);
vtkIdType patientItemID = shNode->CreateSubjectItem(shNode->GetSceneItemID(), patientItemName.toStdString());

shNode->SetItemAttribute(patientItemID, vtkMRMLSubjectHierarchyConstants::GetDICOMPatientNameAttributeName(), patientName.toStdString());
shNode->SetItemAttribute(patientItemID, vtkMRMLSubjectHierarchyConstants::GetDICOMPatientIDAttributeName(), patientID.toStdString());
return patientItemID;
}

//---------------------------------------------------------------------------
vtkIdType qSlicerSubjectHierarchyDICOMPlugin::createDefaultStudyItem()
{
vtkMRMLSubjectHierarchyNode* shNode = qSlicerSubjectHierarchyPluginHandler::instance()->subjectHierarchyNode();
if (!shNode)
{
qCritical() << Q_FUNC_INFO << ": Failed to access subject hierarchy node";
return vtkMRMLSubjectHierarchyNode::INVALID_ITEM_ID;
}

QString studyDescription = tr("No study description");
QDateTime studyDateTime = QDateTime::currentDateTime();
QString studyDateStr = studyDateTime.toString("yyyyMMdd");
QString studyTimeStr = studyDateTime.toString("hhmmss");
char uidBuffer[100]; // must be at least 65 characters long
QString studyInstanceUid = QString::fromLatin1(dcmGenerateUniqueIdentifier(uidBuffer, SITE_STUDY_UID_ROOT));

// Create new study
vtkIdType studyItemID = shNode->CreateStudyItem(shNode->GetSceneItemID(), QString("%1 (%2)").arg(studyDescription).arg(studyDateStr).toStdString());
shNode->SetItemUID(studyItemID, vtkMRMLSubjectHierarchyConstants::GetDICOMUIDName(), studyInstanceUid.toStdString());
shNode->SetItemAttribute(studyItemID, vtkMRMLSubjectHierarchyConstants::GetDICOMStudyInstanceUIDAttributeName(), studyInstanceUid.toStdString());
shNode->SetItemAttribute(studyItemID, vtkMRMLSubjectHierarchyConstants::GetDICOMStudyDescriptionAttributeName(), studyDescription.toStdString());
shNode->SetItemAttribute(studyItemID, vtkMRMLSubjectHierarchyConstants::GetDICOMStudyDateAttributeName(), studyDateStr.toStdString());
shNode->SetItemAttribute(studyItemID, vtkMRMLSubjectHierarchyConstants::GetDICOMStudyTimeAttributeName(), studyTimeStr.toStdString());

return studyItemID;
}
Expand Up @@ -96,6 +96,14 @@ class Q_SLICER_DICOMLIB_SUBJECT_HIERARCHY_PLUGINS_EXPORT qSlicerSubjectHierarchy
/// \param itemID Subject Hierarchy item to show the context menu items for
void showContextMenuActionsForItem(vtkIdType itemID) override;

/// Create a default patient item in the subject hierarchy
/// (with anonymous patient name and random patient ID).
Q_INVOKABLE vtkIdType createDefaultPatientItem();

/// Create a default study item in the subject hierarchy
/// (with no study description and random study instance UID).
Q_INVOKABLE vtkIdType createDefaultStudyItem();

protected slots:
/// Create patient item
void createSubjectItem();
Expand Down

0 comments on commit 899e36a

Please sign in to comment.