adding service worker structure for dicomscan skullremoval and volumepreparation

This commit is contained in:
Giovanni Fattori
2026-01-07 00:33:36 +01:00
parent 7e785c9a09
commit 61ba9d2e93
15 changed files with 1014 additions and 13 deletions
+12 -5
View File
@@ -62,32 +62,39 @@ find_package(GDCM REQUIRED)
# --- Sources ---
set(SRCS
DicomScanService.cpp
DicomScanWorker.cpp
SkullRemovalService.cpp
SkullRemovalWorker.cpp
VolumePrepService.cpp
VolumePrepWorker.cpp
main.cpp
mainw.cpp
ui_mainw.cpp
dicomUtils.cpp
gRen.cpp
gLoadPatient.cpp
LocalizationService.cpp
LocalizationWorker.cpp
gSkullRemoval.cpp
)
set(HDR
DicomScanService.h
DicomScanWorker.h
SkullRemovalService.h
SkullRemovalWorker.h
VolumePrepService.h
VolumePrepWorker.h
mainw.h
ui_mainw.h
connectITKVTK.h
dicomUtils.h
gPatientRTGeneralInfos.h
gRen.h
gLoadPatient.h
LocalizationService.h
LocalizationWorker.h
types.h
types_qt.h
gSkullRemoval.h
itkQtAdaptor.h
)
+450
View File
@@ -0,0 +1,450 @@
#include "DicomScanService.h"
/*_________________________________________________________-
Patient loader for gLocalize.
no wrkDir behaviour. just load data
____________________________________________________________*/
DicomScanService::gLoadPatient(){
patientInfos = new gPatientRTGeneralInfos;
readRT=0;
m_rtIsocenter=0;
initialized=false;
virtualIso=false;
//cout<< "DicomScanService::gLoadPatient" <<endl;
}
void DicomScanService::load(QString p_loadDir){
this->parse(p_loadDir);
if(patientInfos->CTfiles .size()!=0){
cout<< "this->load2VTK(patientInfos)" <<endl;
if(readRT!=0){
delete readRT;
readRT = 0;
}
if(m_rtIsocenter!=0){
delete m_rtIsocenter;
m_rtIsocenter = 0;
}
if(!patientInfos->rtIonPlanPath.isEmpty()){
readRT = new RTPlan;
readRT->fillRTPlan(patientInfos->rtIonPlanPath.toLatin1().data() );
m_rtIsocenter = new double[3];
if (readRT->NumberOfBeams > 0){
{
const auto& iso = readRT->Beams.at(0)->IsocenterPosition;
m_rtIsocenter[0] = iso[0];
m_rtIsocenter[1] = iso[1];
m_rtIsocenter[2] = iso[2];
}
if (readRT->PatientOrientation.rfind("HFS", 0) == 0)
m_patientOrientation = SUPINE;
else if (readRT->PatientOrientation.rfind("HFP", 0) == 0)
m_patientOrientation = PRONE;
else m_patientOrientation = NOTDEFINED;
if (callbacks.loadedRTIso) callbacks.loadedRTIso(m_rtIsocenter);
//for (int ii=0;ii<readRT->NumberOfBeams; ii++){
// cout<< readRT->Beams[ii]->IsocenterPosition[0]<<" ";
// cout<< readRT->Beams[ii]->IsocenterPosition[1]<<" ";
// cout<< readRT->Beams[ii]->IsocenterPosition[2]<<" "<<endl;
//}
//cout<< "nbeams: "<<readRT->NumberOfBeams <<endl;
//cout<< "iso0: "<<readRT->Beams.at(0)->IsocenterPosition[0] <<endl;
//cout<< "iso2: "<<readRT->Beams.at(2)->IsocenterPosition[0]<<endl;
//cout<< "iso0: "<<readRT->Beams.at(0)->IsocenterPosition[1] <<endl;
//cout<< "iso2: "<<readRT->Beams.at(2)->IsocenterPosition[1]<<endl;
//cout<< "iso0: "<<readRT->Beams.at(0)->IsocenterPosition[2] <<endl;
//cout<< "iso2: "<<readRT->Beams.at(2)->IsocenterPosition[2]<<endl;
/*check for virtual iso, adapted from GB, gOTS v04Build34*/
if(readRT->NumberOfBeams > 2 &&
(readRT->Beams.at(0)->IsocenterPosition[0] != readRT->Beams.at(2)->IsocenterPosition[0] ||
readRT->Beams.at(0)->IsocenterPosition[1] != readRT->Beams.at(2)->IsocenterPosition[1] ||
readRT->Beams.at(0)->IsocenterPosition[2] != readRT->Beams.at(2)->IsocenterPosition[2])
)
virtualIso=true;
else
virtualIso=false;
//cout << "virtual Iso: "<<virtualIso << endl;
if (callbacks.virtualIsoTested) callbacks.virtualIsoTested(virtualIso);
} else {
cout<<"Number of Beams = 0 in RTIonPlan, FAIL" <<endl;
delete m_rtIsocenter;
delete readRT;
readRT = 0;
m_rtIsocenter = 0;
}
}
cout<< "DicomScanService::load2VTK _ 2 " <<endl;
this->loadDICOM(patientInfos->CTfiles);
this->connectToVTK();
// this->changeRef(GOTSREF);
/* use DCM as default */
this->changeRef(DCMREF);
//trueOffset_rot_prev=trueOffset_rot;
//this->load2VTK(patientInfos);
} else {
//DO NOTHING. THE folderisempty signal was already emitted.
}
cout<< " DicomScanService::load _ END" <<endl;
}
void DicomScanService::changeRef(int selectedRef){
switch (selectedRef){
case DCMREF:
trueOffset=origin;
break;
case RTREF:
trueOffset=origin;
if(m_patientOrientation == SUPINE) {
cout<< " ***** Patient is SUPINE" <<endl;
trueOffset[0]-= m_rtIsocenter[0];
trueOffset[1]-= m_rtIsocenter[1];
trueOffset[2]-= m_rtIsocenter[2];
} else if (m_patientOrientation == PRONE) {
cout<< " ***** Patient is PRONE" <<endl;
/* adapted from CThandler project*/
trueOffset[0] = 0.0;
trueOffset[1] = 0.0;
trueOffset[2] = 0.0;
cout<<" ***** Reset trueOffset: "<<trueOffset[0]<<" "<<trueOffset[1]<<" "<<trueOffset[2]<<endl;
trueOffset[0] += -origin[0] ;
trueOffset[1] += -origin[1];
trueOffset[2] += origin[2] ;
cout<<" ***** Correct for CT isocenter trueOffset: "<<trueOffset[0]<<" "<<trueOffset[1]<<" "<<trueOffset[2]<<endl;
trueOffset[0] += +m_rtIsocenter[0];
trueOffset[1] += +m_rtIsocenter[1];
trueOffset[2] +=- m_rtIsocenter[2];
cout<<" ***** Correct for rtIso and volume bounds trueOffset: "<<trueOffset[0]<<" "<<trueOffset[1]<<" "<<trueOffset[2]<<endl;
} else if (m_patientOrientation == NOTDEFINED) {
cout<< "!!! Patient is NOT PRONE OR SUPINE! VERY VERY CRITICAL SITUATION !!!" <<endl;
}
break;
case GOTSREF:
trueOffset[0] = 0.0;
trueOffset[1] = 0.0;
trueOffset[2] = 0.0;
break;
}
directionToWCS.SetIdentity( );
directionToWCS[0][0]=1;
directionToWCS[1][0]=0;
directionToWCS[2][0]=0;
directionToWCS[0][1]=0;
directionToWCS[1][1]=1;
directionToWCS[2][1]=0;
directionToWCS[0][2]=0;
directionToWCS[1][2]=0;
directionToWCS[2][2]=1;
imageDir *= directionToWCS;
myImageType::PointType trueOffset_rot=trueOffset;
trueOffset_rot[0]= directionToWCS[0][0]* trueOffset[0]+directionToWCS[0][1]* trueOffset[1] + directionToWCS[0][2]* trueOffset[2];
trueOffset_rot[1]= directionToWCS[1][0]* trueOffset[0]+directionToWCS[1][1]* trueOffset[1] + directionToWCS[1][2]* trueOffset[2];
trueOffset_rot[2]= directionToWCS[2][0]* trueOffset[0]+directionToWCS[2][1]* trueOffset[1] + directionToWCS[2][2]* trueOffset[2];
/*vedi se è giusto ma prob lo è */
myImage->SetDirection( /*imageDir*/directionToWCS );
myImage->SetOrigin( trueOffset_rot );
myImage->Update();
in->SetInput(myImage);
this->connectToVTK();
this->actualizeOut();
/*calculate deltas for marker representation update*/
if(initialized){
if (callbacks.referenceChange) callbacks.referenceChange(
trueOffset_rot_prev[0]-trueOffset_rot[0],
trueOffset_rot_prev[1]-trueOffset_rot[1],
trueOffset_rot_prev[2]-trueOffset_rot[2]);
trueOffset_rot_prev=trueOffset_rot;
} else {
trueOffset_rot_prev=trueOffset_rot;
initialized=true;
}
}
void DicomScanService::loadDICOM(std::vector<std::string> CTfilenames){
myImage = myImageType::New();
rDICOM = itk::ImageSeriesReader<myImageType>::New();
iGDCMimage = itk::GDCMImageIO::New();
myDICOMseries = itk::GDCMSeriesFileNames::New();
rDICOM->SetImageIO(iGDCMimage);
rDICOM->SetFileNames(CTfilenames);
// rDICOM->SetReverseOrder(true);
try{
rDICOM->Update();
}
catch (itk::ExceptionObject &ex){
std::cout << ex << std::endl;
return; // error!
}
myImage=rDICOM->GetOutput();
imageDir = myImage->GetDirection( );
origin = myImage->GetOrigin( );
sizeOfImage = myImage->GetLargestPossibleRegion().GetSize();
}
void DicomScanService::connectToVTK(){
out = vtkSmartPointer<vtkImageImport>::New();
in = itk::VTKImageExport <myImageType>::New();
in->SetInput(myImage);
out->SetUpdateInformationCallback(in->GetUpdateInformationCallback());
out->SetPipelineModifiedCallback(in->GetPipelineModifiedCallback());
out->SetWholeExtentCallback(in->GetWholeExtentCallback());
out->SetSpacingCallback(in->GetSpacingCallback());
out->SetOriginCallback(in->GetOriginCallback());
out->SetScalarTypeCallback(in->GetScalarTypeCallback());
out->SetNumberOfComponentsCallback(in->GetNumberOfComponentsCallback());
out->SetPropagateUpdateExtentCallback(in->GetPropagateUpdateExtentCallback());
out->SetUpdateDataCallback(in->GetUpdateDataCallback());
out->SetDataExtentCallback(in->GetDataExtentCallback());
out->SetBufferPointerCallback(in->GetBufferPointerCallback());
out->SetCallbackUserData(in->GetCallbackUserData());
}
void DicomScanService::actualizeOut(){
//in->Update();
out->Update();
//vol3D = vtkSmartPointer <vtkImageData>::New();
//vol3D->DeepCopy(out->GetOutput());
//vol3D->Update();
vol3D = out->GetOutput();
cout<<vol3D->GetSpacing()[0] <<" "<<vol3D->GetSpacing()[1] <<" "<<vol3D->GetSpacing()[2] <<endl;
cout<<"fatto update" <<endl;
if (callbacks.loadEnd) callbacks.loadEnd(vol3D);
}
void DicomScanService::parse(QString p_loadDir){
loadDir=p_loadDir;
std::cout<< " ______________________________ " <<endl;
std::cout<< " __ DicomScanService::load __ " <<endl;
int result= NOERRORS;
gdcm::Reader* fileReader;
gdcm::MediaStorage msStructRTStruct;
gdcm::MediaStorage msStructRTIonPlan;
gdcm::Directory d;
gdcm::IPPSorter s;
std::cout<< " __ wrkDirParser::exec_parse __ patientInfos->clearInfo() " <<endl;
patientInfos->clearInfo();
std::cout<<"parsing dir: "<<p_loadDir.toUtf8().constData()<<std::endl;
if( gdcm::System::FileIsDirectory( p_loadDir.toUtf8().constData() ) )
{
d.Load(p_loadDir.toUtf8().constData(), false);
gdcm::Directory::FilenamesType const &files = d.GetFilenames();
for( gdcm::Directory::FilenamesType::const_iterator it = files.begin(); it != files.end(); ++it )
{
if(QFileInfo (it->c_str()).fileName().split(".").last() != QString("raw") )
patientInfos->filenames.push_back( it->c_str() );
}
patientInfos->filenames.size() == 0 ? result = PARSER_FOLDER_EMPTY : result = NOERRORS;
} else {
result=PARSER_FOLDER_NOTEXISTING;
};
cout << " patientInfos->filenames.size() "<<endl;
cout<< patientInfos->filenames.size() <<endl;
if(result == NOERRORS)
for(int i=(patientInfos->filenames.size()-1);i>-1;i--)
{
//cout << gCheckDICOMModalityToInt(patientInfos->filenames.at(i).c_str()) <<endl;
fileReader = new gdcm::Reader;
fileReader->SetFileName (patientInfos->filenames.at(i).c_str());
if( !fileReader->Read() ) {
cout<< "Error reading file: "<< patientInfos->filenames.at(i).data() <<endl;
delete fileReader;
patientInfos->filenames.erase(patientInfos->filenames.begin()+i);
continue;
}
//msStructRTIonPlan.SetFromFile( fileReader->GetFile() );
//if( msStructRTIonPlan == gdcm::MediaStorage::RTIonPlanStorage )
if( gCheckDICOMModalityToInt(patientInfos->filenames.at(i).c_str()) == RTPLAN )
{
if(!patientInfos->rtIonPlanPath.isEmpty()) {
// result=PARSER_RTPLAN_NOTUNIQUE;
continue;
} else {
const gdcm::File &file = fileReader->GetFile();
const gdcm::DataSet &ds = file.GetDataSet();
//PATIENT NAME
strcpy( patientInfos->PatientName,gGetStringValueFromTag( gdcm::Tag(0x0010,0x0010), ds));
// // // PATIENT ID For ex: DICOM (0010,0020) = 1933197
strcpy( patientInfos->PatientID , gGetStringValueFromTag( gdcm::Tag(0x0010,0x0020), ds));
// // // PATIENT AGE For ex: DICOM (0010,1010) = 031Y
strcpy( patientInfos->PatientAge , gGetStringValueFromTag( gdcm::Tag(0x0010,0x1010), ds)) ;
// // // PATIENT SEX For ex: DICOM (0010,0040) = M
strcpy( patientInfos->PatientSex, gGetStringValueFromTag( gdcm::Tag(0x0010,0x0040), ds) );
// // // PATIENT BIRTHDATE For ex: DICOM (0010,0030) = 19680427
strcpy( patientInfos->PatientBirthDate, gGetStringValueFromTag( gdcm::Tag(0x0010,0x0030), ds) );
// // // SERIES NUMBER For ex: DICOM (0020,0011) = 902
strcpy( patientInfos->SeriesNumber, gGetStringValueFromTag( gdcm::Tag(0x0020,0x0011), ds) );
// // // SERIES DESCRIPTION For ex: DICOM (0008,103e) = SCOUT
strcpy( patientInfos->SeriesDescription , gGetStringValueFromTag( gdcm::Tag(0x0008,0x103e), ds) );
// // // STUDY ID For ex: DICOM (0020,0010) = 37481
strcpy( patientInfos->StudyID, gGetStringValueFromTag( gdcm::Tag(0x0020,0x0010), ds) );
// // // STUDY DESCRIPTION For ex: DICOM (0008,1030) = BRAIN/C-SP/FACIAL
strcpy( patientInfos->StudyDescription, gGetStringValueFromTag( gdcm::Tag(0x0008,0x1030), ds) );
const gdcm::DataElement &gSetupSequence=ds.GetDataElement(gdcm::Tag(0x300a,0x0180));
//LOOK FOR PATIENT ORIENTATION
gdcm::SmartPointer<gdcm::SequenceOfItems> sqiPS = gSetupSequence.GetValueAsSQ();
const gdcm::DataSet& gPatSetupNest = sqiPS->GetItem(1).GetNestedDataSet();
//SET PATIENT ORIENTATION STRING
strcpy(patientInfos->PatientOrientation, gGetStringValueFromTag(gdcm::Tag(0x0018, 0x5100),gPatSetupNest));
patientInfos->rtIonPlanPath=QString(patientInfos->filenames.at(i).c_str());
cout<< "RTPlan import success" <<endl;
result=NOERRORS;
patientInfos->filenames.erase(patientInfos->filenames.begin()+i);
delete fileReader;
continue;
}
}
// msStructRTStruct.SetFromFile( fileReader->GetFile() );
// if( msStructRTStruct == gdcm::MediaStorage::RTStructureSetStorage ) {
if( gCheckDICOMModalityToInt(patientInfos->filenames.at(i).c_str()) == RTSTRUCT ){
if(!patientInfos->rtStructurePath.isEmpty()) {
patientInfos->rtStructurePath.clear();
// result=PARSER_RTSTRUCT_NOTUNIQUE;
continue;
} else {
patientInfos->rtStructurePath=QString(patientInfos->filenames.at(i).c_str());
cout<< "RTStruct import success" <<endl;
result=NOERRORS;
patientInfos->filenames.erase(patientInfos->filenames.begin()+i);
delete fileReader;
continue;
}
}
delete fileReader;
}
//OK
//if(result == NOERRORS && patientInfos->rtIonPlanPath.isEmpty() )
// result= PARSER_RTPLAN_MISSING;
//if(result == NOERRORS && patientInfos->rtStructurePath.isEmpty())
// result=PARSER_RTSTRUCT_MISSING;
s.SetComputeZSpacing( true );
s.SetZSpacingTolerance( 1e-2 );
if(result==NOERRORS)
if(!s.Sort( patientInfos->filenames ) ) {
result=PARSER_CT_FAILED_SORTING;
} else {
s.GetFilenames().size() == 0 ? result = PARSER_CT_FILES_MISSING : result=NOERRORS;
}
if(result == NOERRORS){
patientInfos->CTfiles=s.GetFilenames ();
//cout<< "CT files sorting succeeded" <<endl;
if (callbacks.statusInfo) callbacks.statusInfo("CT files sorting succeeded");
} else {
switch(result){
case PARSER_FOLDER_EMPTY:
cout<< "Empty folder. Parse failed" <<endl;
if (callbacks.folderIsEmpty) callbacks.folderIsEmpty();
break;
case PARSER_FOLDER_NOTEXISTING:
if (callbacks.statusInfo) callbacks.statusInfo("NonExisting folder. Parse failed");
break;
case PARSER_RTPLAN_NOTUNIQUE:
if (callbacks.statusInfo) callbacks.statusInfo("RtIonPlan not unique. Parse failed");
break;
case PARSER_RTSTRUCT_NOTUNIQUE:
if (callbacks.statusInfo) callbacks.statusInfo("RtStructure not unique. Parse failed");
break;
case PARSER_CT_FAILED_SORTING:
if (callbacks.statusInfo) callbacks.statusInfo("Failed to sort CT files");
if (callbacks.folderIsEmpty) callbacks.folderIsEmpty();
break;
case PARSER_CT_FILES_MISSING:
if (callbacks.statusInfo) callbacks.statusInfo("CT file Missing");
if (callbacks.folderIsEmpty) callbacks.folderIsEmpty();
break;
case PARSER_RTPLAN_MISSING:
if (callbacks.statusInfo) callbacks.statusInfo("RtIonPlan missing");
break;
case PARSER_RTSTRUCT_MISSING:
if (callbacks.statusInfo) callbacks.statusInfo("rtStructure missing");
break;
}
}
if (callbacks.parse_result) callbacks.parse_result(result,patientInfos);
}
DicomScanService::~DicomScanService(){
delete patientInfos;
patientInfos=nullptr;
if (m_rtIsocenter) { delete [] m_rtIsocenter; m_rtIsocenter=nullptr; }
delete readRT;
readRT=nullptr;
}
+148
View File
@@ -0,0 +1,148 @@
#ifndef _DICOMSCANSERVICE_H_
#define _DICOMSCANSERVICE_H_
#include <qstring.h>
#include <functional>
#include <utility>
#include "gPatientRTGeneralInfos.h"
//#include "wrkDirParser.h"
#include <qdir.h>
#include <vtkSmartPointer.h>
#include <vtkImageData.h>
#include "dicomUtils.h"
/*_________________________________________________________-
Patient loader for gLocalize.
no wrkDir behaviour. just load data
____________________________________________________________*/
#include "connectITKVTK.h"
//#include "itkAnalyzeImageIO.h"
#include "itkChangeInformationImageFilter.h"
#include "itkImage.h"
#include "itkImage.h"
#include "itkImageFileWriter.h"
#include "itkOrientImageFilter.h"
#include "itkPermuteAxesImageFilter.h"
#include "itkRescaleIntensityImageFilter.h"
#include "itkImageSeriesReader.h"
#include "itkGDCMImageIO.h"
#include "itkGDCMSeriesFileNames.h"
#include "gdcmIPPSorter.h"
#ifndef TPATIENTORIENTATION
#define TPATIENTORIENTATION
typedef enum {
NOTDEFINED=0,
SUPINE = 1,
PRONE =2
} t_patientOrientation;
#endif
#ifndef PARSERVARIABLES
#define PARSERVARIABLES
enum patientErrors {
NOERRORS,
PARSER_FOLDER_NOTEXISTING,
PARSER_FOLDER_EMPTY,
PARSER_RTPLAN_NOTUNIQUE,
PARSER_RTPLAN_MISSING,
PARSER_RTSTRUCT_NOTUNIQUE,
PARSER_RTSTRUCT_MISSING,
PARSER_CT_FILES_MISSING,
PARSER_CT_FAILED_SORTING,
POPULATE_INCONSISTENT_DATASET,
POPULATE_NOTEXISTING,
CLEAR_FOLDER_NOTEXISTING
};
enum patientParseOption{
NOCLEAN,
CLEAN
};
enum patientCleanMode{
JUSTCLEAR,
ISLOADINGCHECK
};
#endif
#ifndef _T_REFERENCE_
#define _T_REFERENCE_
typedef enum{
DCMREF=0,
RTREF=1,
GOTSREF=2
}t_reference;
#endif
typedef itk::Image<short,3> myImageType;
class DicomScanService {
public:
DicomScanService();
~DicomScanService();
struct Callbacks {
std::function<void()> folderIsEmpty;
std::function<void(QString)> statusInfo;
std::function<void(int, gPatientRTGeneralInfos*)> parse_result;
std::function<void(QString)> loadFail;
std::function<void(vtkImageData*)> loadEnd;
std::function<void(double*)> loadedRTIso;
std::function<void(double*, double*, int*)> loadedVolBBox;
std::function<void(double, double, double)> referenceChange;
std::function<void(bool)> virtualIsoTested;
};
void setCallbacks(Callbacks cb) { callbacks = std::move(cb); }
public:
void load(QString p_loadDir);
void changeRef(int selectedRef);
private:
QString loadDir;
gPatientRTGeneralInfos* patientInfos;
void loadDICOM(std::vector<std::string> CTfilenames);
void connectToVTK();
void actualizeOut();
void parse(QString p_loadDir);
t_patientOrientation m_patientOrientation;
vtkSmartPointer <vtkImageData> vol3D;
myImageType::Pointer myImage;
itk::ImageSeriesReader<myImageType>::Pointer rDICOM;
itk::GDCMImageIO::Pointer iGDCMimage ;
itk::GDCMSeriesFileNames::Pointer myDICOMseries;
myImageType::DirectionType imageDir;
myImageType::PointType origin;
myImageType::SizeType sizeOfImage;
myImageType::PointType trueOffset;
myImageType::PointType trueOffset_rot;
myImageType::PointType trueOffset_rot_prev;
myImageType::DirectionType directionToWCS;
bool initialized;
vtkSmartPointer<vtkImageImport> out;
itk::VTKImageExport <myImageType>::Pointer in;
RTPlan * readRT;
double* m_rtIsocenter;
bool virtualIso;
t_reference m_reference;
private:
Callbacks callbacks;
};
#endif
+35
View File
@@ -0,0 +1,35 @@
#include "DicomScanWorker.h"
DicomScanWorker::DicomScanWorker(QObject* parent)
: QObject(parent)
{
DicomScanService::Callbacks cb;
cb.folderIsEmpty = [&]() { emit folderIsEmpty(); };
cb.statusInfo = [&](QString msg) { emit statusInfo(msg); };
cb.parse_result = [&](int r, gPatientRTGeneralInfos* infos) { emit parse_result(r, infos); };
cb.loadFail = [&](QString msg) { emit loadFail(msg); };
cb.loadEnd = [&](vtkImageData* vol) { emit loadEnd(vol); };
cb.loadedRTIso = [&](double* iso) { emit loadedRTIso(iso); };
cb.loadedVolBBox = [&](double* bbox, double* spacing, int* dim) { emit loadedVolBBox(bbox, spacing, dim); };
cb.referenceChange = [&](double dx, double dy, double dz) { emit referenceChange(dx, dy, dz); };
cb.virtualIsoTested = [&](bool b) { emit virtualIsoTested(b); };
svc.setCallbacks(std::move(cb));
}
void DicomScanWorker::load(QString p_loadDir)
{
m_abort.store(false);
if (m_abort.load()) { emit aborted(); return; }
svc.load(p_loadDir);
if (m_abort.load()) emit aborted();
}
void DicomScanWorker::changeRef(int selectedRef)
{
svc.changeRef(selectedRef);
}
void DicomScanWorker::abort()
{
m_abort.store(true);
}
+40
View File
@@ -0,0 +1,40 @@
#pragma once
#include <QObject>
#include <atomic>
#include <QString>
#include "DicomScanService.h"
class vtkImageData;
class gPatientRTGeneralInfos;
// Qt wrapper around DicomScanService (runs in a worker thread).
class DicomScanWorker : public QObject
{
Q_OBJECT
public:
explicit DicomScanWorker(QObject* parent = nullptr);
public slots:
void load(QString p_loadDir);
void changeRef(int selectedRef);
void abort();
signals:
void folderIsEmpty();
void statusInfo(QString msg);
void parse_result(int result, gPatientRTGeneralInfos* patientInfos);
void loadFail(QString msg);
void loadEnd(vtkImageData* vol);
void loadedRTIso(double* iso);
void loadedVolBBox(double* bbox, double* spacing, int* dimension);
void referenceChange(double dx, double dy, double dz);
void virtualIsoTested(bool virtualIso);
void aborted();
private:
std::atomic_bool m_abort{false};
DicomScanService svc;
};
+138
View File
@@ -0,0 +1,138 @@
#include "SkullRemovalService.h"
#include "connectITKVTK.h"
#include <vtkImageData.h>
#include <vtkImageExport.h>
#include <vtkImageImport.h>
#include <vtkSmartPointer.h>
#include <itkBinaryDilateImageFilter.h>
#include <itkBinaryErodeImageFilter.h>
#include <itkBinaryThresholdImageFilter.h>
#include <itkCastImageFilter.h>
#include <itkConnectedThresholdImageFilter.h>
#include <itkCurvatureFlowImageFilter.h>
#include <itkImage.h>
#include <itkMaskNegatedImageFilter.h>
#include <itkVTKImageImport.h>
#include <itkExceptionObject.h>
#include <stdexcept>
namespace
{
using InternalPixelType = float;
constexpr unsigned int Dimension = 3;
using InternalImageType = itk::Image<InternalPixelType, Dimension>;
using MaskPixelType = unsigned char;
using MaskImageType = itk::Image<MaskPixelType, Dimension>;
using ImportFilterType = itk::VTKImageImport<InternalImageType>;
using CastingINVFilterType = itk::CastImageFilter<InternalImageType, InternalImageType>;
using CurvatureFlowImageFilterType = itk::CurvatureFlowImageFilter<InternalImageType, InternalImageType>;
using ConnectedThresholdImageFilterType = itk::ConnectedThresholdImageFilter<InternalImageType, MaskImageType>;
using CastingFilterType = itk::CastImageFilter<MaskImageType, MaskImageType>;
using StructuringElementType = itk::BinaryBallStructuringElement<MaskPixelType, Dimension>;
using DilateFilterType = itk::BinaryDilateImageFilter<MaskImageType, MaskImageType, StructuringElementType>;
using ErodeFilterType = itk::BinaryErodeImageFilter<MaskImageType, MaskImageType, StructuringElementType>;
using MaskNegatedFilterType = itk::MaskNegatedImageFilter<InternalImageType, MaskImageType, InternalImageType>;
} // namespace
vtkImageData* SkullRemovalService::run(vtkImageData* inputVolume,
double thr_low,
double thr_up,
std::atomic_bool* abortFlag,
std::function<void(const std::string&, double)> progressCb)
{
if (!inputVolume)
throw std::runtime_error("SkullRemovalService::run: inputVolume is null");
auto isAborted = [&]() -> bool { return abortFlag && abortFlag->load(); };
if (progressCb) progressCb("Skull masking: preparing pipeline", 0.0);
if (isAborted()) return nullptr;
// VTK -> ITK
vtkSmartPointer<vtkImageExport> vtkExporter = vtkSmartPointer<vtkImageExport>::New();
vtkExporter->SetInputData(inputVolume);
auto itkImporter = ImportFilterType::New();
ConnectPipelines(vtkExporter.GetPointer(), itkImporter);
// Pipeline (mirrors old gSkullRemoval::runFilter in a pure form)
auto smoothing = CurvatureFlowImageFilterType::New();
auto connectedThreshold = ConnectedThresholdImageFilterType::New();
auto caster_mask = CastingFilterType::New();
auto binaryDilateFilter = DilateFilterType::New();
auto binaryErodeFilter = ErodeFilterType::New();
auto maskNegatedFilter = MaskNegatedFilterType::New();
smoothing->SetNumberOfIterations(10);
smoothing->SetTimeStep(0.125);
smoothing->SetInput(itkImporter->GetOutput());
connectedThreshold->SetInput(smoothing->GetOutput());
connectedThreshold->SetLower(static_cast<InternalPixelType>(thr_low));
connectedThreshold->SetUpper(static_cast<InternalPixelType>(thr_up));
connectedThreshold->SetReplaceValue(255);
// Seed at volume center
connectedThreshold->UpdateOutputInformation();
InternalImageType::Pointer in = itkImporter->GetOutput();
const auto region = in->GetBufferedRegion();
const auto size = region.GetSize();
const auto start = region.GetIndex();
InternalImageType::IndexType seed;
seed[0] = start[0] + static_cast<long>(size[0] / 2);
seed[1] = start[1] + static_cast<long>(size[1] / 2);
seed[2] = start[2] + static_cast<long>(size[2] / 2);
connectedThreshold->SetSeed(seed);
caster_mask->SetInput(connectedThreshold->GetOutput());
StructuringElementType kernel;
kernel.SetRadius(10);
kernel.CreateStructuringElement();
binaryDilateFilter->SetKernel(kernel);
binaryErodeFilter->SetKernel(kernel);
binaryDilateFilter->SetDilateValue(255);
binaryErodeFilter->SetErodeValue(255);
binaryDilateFilter->SetInput(caster_mask->GetOutput());
binaryErodeFilter->SetInput(binaryDilateFilter->GetOutput());
maskNegatedFilter->SetInput1(itkImporter->GetOutput());
maskNegatedFilter->SetInput2(binaryErodeFilter->GetOutput());
if (progressCb) progressCb("Skull masking: running", 0.5);
if (isAborted()) return nullptr;
try {
maskNegatedFilter->Update();
} catch (const itk::ExceptionObject& ex) {
if (isAborted()) return nullptr;
throw std::runtime_error(std::string("SkullRemovalService: ITK exception: ") + ex.GetDescription());
}
if (isAborted()) return nullptr;
// ITK -> VTK
vtkSmartPointer<vtkImageImport> vtkImporter = vtkSmartPointer<vtkImageImport>::New();
auto itkExporter = itk::VTKImageExport<InternalImageType>::New();
itkExporter->SetInput(maskNegatedFilter->GetOutput());
ConnectPipelines(itkExporter, vtkImporter);
vtkImporter->Update();
vtkImageData* out = vtkImageData::New();
out->DeepCopy(vtkImporter->GetOutput());
if (progressCb) progressCb("Skull masking: done", 1.0);
return out;
}
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include <atomic>
#include <functional>
#include <string>
class vtkImageData;
// Pure skull removal / masking pipeline (no Qt).
class SkullRemovalService
{
public:
SkullRemovalService() = default;
// Returns a NEW vtkImageData* (caller owns; use vtkSmartPointer if you prefer).
// Throws std::runtime_error on failure (unless aborted).
vtkImageData* run(vtkImageData* inputVolume,
double thr_low,
double thr_up,
std::atomic_bool* abortFlag = nullptr,
std::function<void(const std::string&, double)> progressCb = {});
};
+44
View File
@@ -0,0 +1,44 @@
#include "SkullRemovalWorker.h"
#include "SkullRemovalService.h"
#include <vtkImageData.h>
#include <QString>
SkullRemovalWorker::SkullRemovalWorker(QObject* parent)
: QObject(parent)
{
}
void SkullRemovalWorker::run(vtkImageData* inputVolume, double thr_low, double thr_up)
{
m_abort.store(false);
emit skull_mask_upd("Skull masking...", 0.0);
SkullRemovalService svc;
try {
vtkImageData* out = svc.run(
inputVolume, thr_low, thr_up, &m_abort,
[&](const std::string& msg, double p) { emit skull_mask_upd(QString::fromStdString(msg), p * 100.0); });
if (m_abort.load()) {
if (out) out->Delete();
emit aborted();
return;
}
emit skull_mask_upd("Skull masking done", 100.0);
emit skull_mask_end(out); // receiver should Delete() if needed (or deep copy)
} catch (const std::exception& e) {
if (m_abort.load()) {
emit aborted();
return;
}
emit errMsg(QString(e.what()));
}
}
void SkullRemovalWorker::abort()
{
m_abort.store(true);
}
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#include <QObject>
#include <atomic>
class vtkImageData;
class SkullRemovalWorker : public QObject
{
Q_OBJECT
public:
explicit SkullRemovalWorker(QObject* parent = nullptr);
public slots:
void run(vtkImageData* inputVolume, double thr_low, double thr_up);
void abort();
signals:
void skull_mask_end(vtkImageData* maskedVolume);
void errMsg(QString msg);
void skull_mask_upd(QString msg, double val);
void aborted();
private:
std::atomic_bool m_abort{false};
};
+12
View File
@@ -0,0 +1,12 @@
#include "VolumePrepService.h"
#include <vtkImageData.h>
vtkImageData* VolumePrepService::prepare(vtkImageData* input)
{
if (!input) return nullptr;
vtkImageData* out = vtkImageData::New();
out->DeepCopy(input);
out->Modified();
return out;
}
+10
View File
@@ -0,0 +1,10 @@
#pragma once
class vtkImageData;
// Simple volume preparation (deep copy / detaching from loader thread).
class VolumePrepService
{
public:
vtkImageData* prepare(vtkImageData* input);
};
+31
View File
@@ -0,0 +1,31 @@
#include "VolumePrepWorker.h"
#include "VolumePrepService.h"
#include <vtkImageData.h>
VolumePrepWorker::VolumePrepWorker(QObject* parent) : QObject(parent) {}
void VolumePrepWorker::run(vtkImageData* input)
{
m_abort.store(false);
emit progress(0.0);
if (m_abort.load()) { emit aborted(); return; }
VolumePrepService svc;
vtkImageData* out = svc.prepare(input);
if (m_abort.load()) {
if (out) out->Delete();
emit aborted();
return;
}
emit progress(100.0);
emit finished(out);
}
void VolumePrepWorker::abort()
{
m_abort.store(true);
}
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#include <QObject>
#include <atomic>
class vtkImageData;
class VolumePrepWorker : public QObject
{
Q_OBJECT
public:
explicit VolumePrepWorker(QObject* parent = nullptr);
public slots:
void run(vtkImageData* input);
void abort();
signals:
void finished(vtkImageData* prepared);
void error(QString msg);
void progress(double percent);
void aborted();
private:
std::atomic_bool m_abort{false};
};
+9 -2
View File
@@ -84,6 +84,12 @@ MainWindow::MainWindow(QString p_loadPath){
loadPatient->moveToThread(loadPatient_thread);
loadPatient_thread->start();
// Volume preparation worker (detaches volumes from the loader thread)
volumePrep = new VolumePrepWorker;
volumePrep_thread = new QThread;
volumePrep->moveToThread(volumePrep_thread);
volumePrep_thread->start();
qRegisterMetaType< gPatientRTGeneralInfos* >("gPatientRTGeneralInfos *");
qRegisterMetaType< std::vector<std::string> >("std::vector<std::string>");
qRegisterMetaType< std::string >("std::string");
@@ -94,7 +100,8 @@ MainWindow::MainWindow(QString p_loadPath){
qRegisterMetaType<MarkerList>("MarkerList");
qRegisterMetaType<LocalizationParams>("LocalizationParams");
connect(loadPatient, SIGNAL(loadEnd(vtkImageData*)), Visualizer,SLOT(loadVolume (vtkImageData* )));
connect(loadPatient, SIGNAL(loadEnd(vtkImageData*)), volumePrep, SLOT(run(vtkImageData* )));
connect(volumePrep, SIGNAL(finished(vtkImageData*)), Visualizer, SLOT(loadVolume (vtkImageData* )));
connect(loadPatient, SIGNAL(folderIsEmpty()),this,SLOT(onParsedEmptyFolder()));
connect(loadPatient, SIGNAL(parse_result(int , gPatientRTGeneralInfos* )),this,SLOT(onParsedOK(int , gPatientRTGeneralInfos* )));
connect(loadPatient, SIGNAL(loadedRTIso(double*)),this,SLOT(onRTIsoAvailable(double*)));
@@ -562,7 +569,7 @@ void MainWindow::call_skullMask(){
Q_ARG(QString, "Skull masking..." ),
Q_ARG(int, 3000));
QMetaObject::invokeMethod(skullRemoval, "runFilter", Qt::QueuedConnection,
QMetaObject::invokeMethod(skullRemoval, "run", Qt::QueuedConnection,
Q_ARG(vtkImageData*, Visualizer->getVolume()),
Q_ARG(double, regGrownParameters[1]),
Q_ARG(double,regGrownParameters[0]));
+11 -6
View File
@@ -9,9 +9,10 @@
class Ui_MainWindow; // forward declaration (defined in ui_mainw.h)
class gRen;
class gLoadPatient;
class DicomScanWorker;
class LocalizationWorker;
class gSkullRemoval;
class SkullRemovalWorker;
class VolumePrepWorker;
class gLoadPatDialog;
//#include "wrkDirParser.h"
@@ -19,14 +20,15 @@ class gLoadPatDialog;
#include "gPatientRTGeneralInfos.h"
#include "dicomUtils.h"
#include "connectITKVTK.h"
#include "gLoadPatient.h"
#include "LocalizationWorker.h"
#include "DicomScanWorker.h"
#include "SkullRemovalWorker.h"
#include "VolumePrepWorker.h"
#include "types_qt.h"
#include "gSkullRemoval.h"
class gLoadPatDialog: public QDialog{
@@ -65,12 +67,15 @@ private:
void connectUi(Ui_MainWindow* Ui);
gRen * Visualizer;
gLoadPatient* loadPatient;
DicomScanWorker* loadPatient;
QThread* loadPatient_thread;
VolumePrepWorker* volumePrep{nullptr};
QThread* volumePrep_thread{nullptr};
LocalizationWorker* localize_marker{nullptr};
QThread* localize_thread;
gSkullRemoval* skullRemoval;
SkullRemovalWorker* skullRemoval;
QThread* skull_thread;
QStandardItemModel* treeModel;