diff --git a/CMakeLists.txt b/CMakeLists.txt index eee7ce1..0a1135d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 ) diff --git a/DicomScanService.cpp b/DicomScanService.cpp new file mode 100644 index 0000000..27aa5c6 --- /dev/null +++ b/DicomScanService.cpp @@ -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" <parse(p_loadDir); + + if(patientInfos->CTfiles .size()!=0){ + cout<< "this->load2VTK(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;iiNumberOfBeams; ii++){ + // cout<< readRT->Beams[ii]->IsocenterPosition[0]<<" "; + // cout<< readRT->Beams[ii]->IsocenterPosition[1]<<" "; + // cout<< readRT->Beams[ii]->IsocenterPosition[2]<<" "<NumberOfBeams <Beams.at(0)->IsocenterPosition[0] <Beams.at(2)->IsocenterPosition[0]<Beams.at(0)->IsocenterPosition[1] <Beams.at(2)->IsocenterPosition[1]<Beams.at(0)->IsocenterPosition[2] <Beams.at(2)->IsocenterPosition[2]<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: "<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" <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 CTfilenames){ + + myImage = myImageType::New(); + rDICOM = itk::ImageSeriesReader::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::New(); + + in = itk::VTKImageExport ::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 ::New(); + //vol3D->DeepCopy(out->GetOutput()); + //vol3D->Update(); + vol3D = out->GetOutput(); + cout<GetSpacing()[0] <<" "<GetSpacing()[1] <<" "<GetSpacing()[2] <clearInfo() " <clearInfo(); + + std::cout<<"parsing dir: "<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() "<filenames.size() <filenames.size()-1);i>-1;i--) + { + //cout << gCheckDICOMModalityToInt(patientInfos->filenames.at(i).c_str()) <SetFileName (patientInfos->filenames.at(i).c_str()); + + if( !fileReader->Read() ) { + cout<< "Error reading file: "<< patientInfos->filenames.at(i).data() <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 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" <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" <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" < +#include +#include +#include "gPatientRTGeneralInfos.h" +//#include "wrkDirParser.h" +#include +#include +#include +#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 myImageType; + + +class DicomScanService { +public: + DicomScanService(); + ~DicomScanService(); + + struct Callbacks { + std::function folderIsEmpty; + std::function statusInfo; + std::function parse_result; + std::function loadFail; + std::function loadEnd; + std::function loadedRTIso; + std::function loadedVolBBox; + std::function referenceChange; + std::function 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 CTfilenames); + void connectToVTK(); + void actualizeOut(); + void parse(QString p_loadDir); + t_patientOrientation m_patientOrientation; + vtkSmartPointer vol3D; + + myImageType::Pointer myImage; + itk::ImageSeriesReader::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 out; + itk::VTKImageExport ::Pointer in; + + RTPlan * readRT; + double* m_rtIsocenter; + bool virtualIso; + t_reference m_reference; + + +private: + Callbacks callbacks; +}; + +#endif diff --git a/DicomScanWorker.cpp b/DicomScanWorker.cpp new file mode 100644 index 0000000..9ce4469 --- /dev/null +++ b/DicomScanWorker.cpp @@ -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); +} diff --git a/DicomScanWorker.h b/DicomScanWorker.h new file mode 100644 index 0000000..715ad2e --- /dev/null +++ b/DicomScanWorker.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +#include + +#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; +}; diff --git a/SkullRemovalService.cpp b/SkullRemovalService.cpp new file mode 100644 index 0000000..9ea172e --- /dev/null +++ b/SkullRemovalService.cpp @@ -0,0 +1,138 @@ +#include "SkullRemovalService.h" + +#include "connectITKVTK.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace +{ +using InternalPixelType = float; +constexpr unsigned int Dimension = 3; + +using InternalImageType = itk::Image; +using MaskPixelType = unsigned char; +using MaskImageType = itk::Image; + +using ImportFilterType = itk::VTKImageImport; +using CastingINVFilterType = itk::CastImageFilter; +using CurvatureFlowImageFilterType = itk::CurvatureFlowImageFilter; +using ConnectedThresholdImageFilterType = itk::ConnectedThresholdImageFilter; +using CastingFilterType = itk::CastImageFilter; + +using StructuringElementType = itk::BinaryBallStructuringElement; +using DilateFilterType = itk::BinaryDilateImageFilter; +using ErodeFilterType = itk::BinaryErodeImageFilter; +using MaskNegatedFilterType = itk::MaskNegatedImageFilter; +} // namespace + +vtkImageData* SkullRemovalService::run(vtkImageData* inputVolume, + double thr_low, + double thr_up, + std::atomic_bool* abortFlag, + std::function 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 vtkExporter = vtkSmartPointer::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(thr_low)); + connectedThreshold->SetUpper(static_cast(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(size[0] / 2); + seed[1] = start[1] + static_cast(size[1] / 2); + seed[2] = start[2] + static_cast(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 vtkImporter = vtkSmartPointer::New(); + auto itkExporter = itk::VTKImageExport::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; +} diff --git a/SkullRemovalService.h b/SkullRemovalService.h new file mode 100644 index 0000000..c75ffc6 --- /dev/null +++ b/SkullRemovalService.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include + +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 progressCb = {}); +}; diff --git a/SkullRemovalWorker.cpp b/SkullRemovalWorker.cpp new file mode 100644 index 0000000..7bff4a3 --- /dev/null +++ b/SkullRemovalWorker.cpp @@ -0,0 +1,44 @@ +#include "SkullRemovalWorker.h" +#include "SkullRemovalService.h" + +#include + +#include + +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); +} diff --git a/SkullRemovalWorker.h b/SkullRemovalWorker.h new file mode 100644 index 0000000..c0d89c6 --- /dev/null +++ b/SkullRemovalWorker.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +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}; +}; diff --git a/VolumePrepService.cpp b/VolumePrepService.cpp new file mode 100644 index 0000000..06541cf --- /dev/null +++ b/VolumePrepService.cpp @@ -0,0 +1,12 @@ +#include "VolumePrepService.h" + +#include + +vtkImageData* VolumePrepService::prepare(vtkImageData* input) +{ + if (!input) return nullptr; + vtkImageData* out = vtkImageData::New(); + out->DeepCopy(input); + out->Modified(); + return out; +} diff --git a/VolumePrepService.h b/VolumePrepService.h new file mode 100644 index 0000000..4413e39 --- /dev/null +++ b/VolumePrepService.h @@ -0,0 +1,10 @@ +#pragma once + +class vtkImageData; + +// Simple volume preparation (deep copy / detaching from loader thread). +class VolumePrepService +{ +public: + vtkImageData* prepare(vtkImageData* input); +}; diff --git a/VolumePrepWorker.cpp b/VolumePrepWorker.cpp new file mode 100644 index 0000000..24da7d0 --- /dev/null +++ b/VolumePrepWorker.cpp @@ -0,0 +1,31 @@ +#include "VolumePrepWorker.h" +#include "VolumePrepService.h" + +#include + +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); +} diff --git a/VolumePrepWorker.h b/VolumePrepWorker.h new file mode 100644 index 0000000..9286c76 --- /dev/null +++ b/VolumePrepWorker.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +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}; +}; diff --git a/mainw.cpp b/mainw.cpp index efd6675..c41d7dc 100644 --- a/mainw.cpp +++ b/mainw.cpp @@ -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::vector"); qRegisterMetaType< std::string >("std::string"); @@ -94,7 +100,8 @@ MainWindow::MainWindow(QString p_loadPath){ qRegisterMetaType("MarkerList"); qRegisterMetaType("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])); diff --git a/mainw.h b/mainw.h index c34e4af..e73cb11 100644 --- a/mainw.h +++ b/mainw.h @@ -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;