From f18e57ff0e37c84853c3c17fe10c8dfd3cdb27ad Mon Sep 17 00:00:00 2001 From: Giovanni Fattori Date: Mon, 13 Oct 2014 15:41:02 +0200 Subject: [PATCH] Working version of gLocalize software installed at the CNAO. This extends the embedded function in gOTS code with: - manual volume masking - visualization tools - no memory issues. --- CMakeLists.txt | 154 ++++++ connectITKVTK.h | 51 ++ dicomUtils.cpp | 485 ++++++++++++++++++ dicomUtils.h | 95 ++++ gLoadPatient.cpp | 434 ++++++++++++++++ gLoadPatient.h | 142 ++++++ gLocalize.cpp | 380 ++++++++++++++ gLocalize.h | 215 ++++++++ gPatientRTGeneralInfos.h | 78 +++ gRen.cpp | 434 ++++++++++++++++ gRen.h | 298 +++++++++++ gSkullRemoval.cpp | 264 ++++++++++ gSkullRemoval.h | 172 +++++++ icons/Thumbs.db | Bin 0 -> 18432 bytes icons/ortoIco.png | Bin 0 -> 19065 bytes icons/pb_load.png | Bin 0 -> 8430 bytes icons/pb_quit.png | Bin 0 -> 16139 bytes icons/volrenIco.png | Bin 0 -> 83220 bytes images.qrc | 8 + itkQtAdaptor.h | 162 ++++++ main.cpp | 83 +++ mainw.cpp | 1043 ++++++++++++++++++++++++++++++++++++++ mainw.h | 237 +++++++++ 23 files changed, 4735 insertions(+) create mode 100755 CMakeLists.txt create mode 100755 connectITKVTK.h create mode 100755 dicomUtils.cpp create mode 100755 dicomUtils.h create mode 100755 gLoadPatient.cpp create mode 100755 gLoadPatient.h create mode 100755 gLocalize.cpp create mode 100755 gLocalize.h create mode 100755 gPatientRTGeneralInfos.h create mode 100755 gRen.cpp create mode 100755 gRen.h create mode 100755 gSkullRemoval.cpp create mode 100755 gSkullRemoval.h create mode 100755 icons/Thumbs.db create mode 100755 icons/ortoIco.png create mode 100755 icons/pb_load.png create mode 100755 icons/pb_quit.png create mode 100755 icons/volrenIco.png create mode 100755 images.qrc create mode 100755 itkQtAdaptor.h create mode 100755 main.cpp create mode 100755 mainw.cpp create mode 100755 mainw.h diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100755 index 0000000..4b3477a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,154 @@ +cmake_minimum_required (VERSION 2.8) + +IF (NOT CMAKE_BUILD_TYPE) + SET (CMAKE_BUILD_TYPE "Release" CACHE STRING + "Choose the type of build, options are: Debug Release + RelWithDebInfo MinSizeRel." FORCE) +ENDIF () + +project(gLocalize) +set(TRG gLocalize) + + +include_directories (BEFORE ${CMAKE_CURRENT_SOURCE_DIR}) + + +find_package(VTK REQUIRED) +include(${VTK_USE_FILE}) +ADD_DEFINITIONS(${VTK_DEFINITIONS}) + +find_package(Qt4 REQUIRED) +include(${QT_USE_FILE}) +ADD_DEFINITIONS(${QT_DEFINITIONS}) + + +FIND_PACKAGE(GDCM REQUIRED) +IF(NOT GDCM_DIR) + MESSAGE(FATAL_ERROR "Please set GDCM_DIR.") +ELSE(NOT GDCM_DIR) + include(${GDCM_USE_FILE}) + INCLUDE_DIRECTORIES(${GDCM_INCLUDE_DIRS}) +ENDIF(NOT GDCM_DIR) + + +SET(ITK_MIN_VERSION "3.20") + + +FIND_PACKAGE(ITK REQUIRED) +IF(NOT ITK_FOUND) + MESSAGE(SEND_ERROR "ITK ${ITK_MIN_VERSION} not found. Please set the ITK_DIR variable.") +ELSE(NOT ITK_FOUND) + # The version check fails with cmake + # 2.8.4+linux, manually perform check +IF("${ITK_VERSION_MAJOR}.${ITK_VERSION_MINOR}" + VERSION_LESS + ${ITK_MIN_VERSION}) + MESSAGE(SEND_ERROR "ITK ${ITK_MIN_VERSION} not found (version ${ITK_VERSION_MAJOR}.${ITK_VERSION_MINOR} provided). Please set the ITK_DIR variable.") +ELSE("${ITK_VERSION_MAJOR}.${ITK_VERSION_MINOR}" VERSION_LESS ${ITK_MIN_VERSION}) + INCLUDE(${ITK_USE_FILE}) +ENDIF("${ITK_VERSION_MAJOR}.${ITK_VERSION_MINOR}" VERSION_LESS ${ITK_MIN_VERSION}) + +ENDIF(NOT ITK_FOUND) + +# set(QT_ROOT "C:/Qt/4.8.2") +# SET(ZLIB_INCLUDE_DIRS "${QT_ROOT}/src/3rdparty/zlib") +# message("QT_ROOT variable: ${QT_ROOT}") +# message("${ZLIB_INCLUDE_DIRS}") +# include_directories(${ZLIB_INCLUDE_DIRS}) + +IF(UNIX) + find_package(ZLIB REQUIRED) +ELSE(UNIX) + # SET(ZLIB_INCLUDE_DIRS + # "${QT_ROOT}/src/3rdparty/zlib" + # CACHE + # STRING + # "Path + # to + # ZLIB + # headers + # of + # Qt") + SET(ZLIB_LIBRARIES "") + IF(NOT EXISTS "${ZLIB_INCLUDE_DIRS}/zlib.h") + MESSAGE("Please specify a valid zlib include dir") + ENDIF(NOT EXISTS "${ZLIB_INCLUDE_DIRS}/zlib.h") +ENDIF(UNIX) + +include_directories(${PROJECT_BINARY_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${PROJECT_SOURCE_DIR}/src) + +set(SRCS + ${PROJECT_SOURCE_DIR}/main.cpp + ${PROJECT_SOURCE_DIR}/mainw.cpp + ${PROJECT_SOURCE_DIR}/mainw.h + ${PROJECT_SOURCE_DIR}/connectITKVTK.h + ${PROJECT_SOURCE_DIR}/dicomUtils.cpp + ${PROJECT_SOURCE_DIR}/dicomUtils.h + ${PROJECT_SOURCE_DIR}/gPatientRTGeneralInfos.h + ${PROJECT_SOURCE_DIR}/gRen.h + ${PROJECT_SOURCE_DIR}/gRen.cpp + #${PROJECT_SOURCE_DIR}/loadDCM2VTK.h + #${PROJECT_SOURCE_DIR}/loadDCM2VTK.cpp + #${PROJECT_SOURCE_DIR}/wrkDirParser.cpp + #${PROJECT_SOURCE_DIR}/wrkDirParser.h + ${PROJECT_SOURCE_DIR}/gLoadPatient.h + ${PROJECT_SOURCE_DIR}/gLoadPatient.cpp + ${PROJECT_SOURCE_DIR}/gLocalize.h + ${PROJECT_SOURCE_DIR}/gLocalize.cpp + ${PROJECT_SOURCE_DIR}/gSkullRemoval.h + ${PROJECT_SOURCE_DIR}/gSkullRemoval.cpp + ${PROJECT_SOURCE_DIR}/itkQtAdaptor.h + ) + +set(HDR + ${PROJECT_SOURCE_DIR}/mainw.h + ${PROJECT_SOURCE_DIR}/connectITKVTK.h + ${PROJECT_SOURCE_DIR}/dicomUtils.h + ${PROJECT_SOURCE_DIR}/gPatientRTGeneralInfos.h + ${PROJECT_SOURCE_DIR}/gRen.h + #${PROJECT_SOURCE_DIR}/loadDCM2VTK.h + #${PROJECT_SOURCE_DIR}/wrkDirParser.h + ${PROJECT_SOURCE_DIR}/gLoadPatient.h + ${PROJECT_SOURCE_DIR}/gLocalize.h + ${PROJECT_SOURCE_DIR}/gSkullRemoval.h + ${PROJECT_SOURCE_DIR}/itkQtAdaptor.h + + ) +QT4_WRAP_CPP(HDR_MOC ${HDR}) + + + # SET(RES ${PROJECT_SOURCE_DIR}/resources/images.qrc) + # QT4_ADD_RESOURCES(RES_RCC ${RES}) + + +SET(RES ${PROJECT_SOURCE_DIR}/images.qrc) +QT4_ADD_RESOURCES(RES_RCC ${RES}) + + + + ADD_DEFINITIONS(-Wall -O3 -DQT_DLL -DQT_THREAD_SUPPORT) + + add_executable(${TRG} ${SRCS} ${HDR_MOC} ${RES_RCC} ) + #${RES_RCC} ) + + target_link_libraries(${TRG} + ${QT_LIBRARIES} + ${QT_QTMAIN_LIBRARY} + QVTK + ${VTK_LIBRARIES} + ${QT_QTCORE_LIBRARY} + ${QT_QTGUI_LIBRARY} + gdcmCommon + gdcmDICT + ITKIO + ITKCommon + ITKBasicFilters + ITKAlgorithms + ITKNumerics + ITKStatistics + ITKSpatialObject + ) + + diff --git a/connectITKVTK.h b/connectITKVTK.h new file mode 100755 index 0000000..27b625f --- /dev/null +++ b/connectITKVTK.h @@ -0,0 +1,51 @@ +#ifndef __ConnectVTKITK_h +#define __ConnectVTKITK_h + + +#include "itkVTKImageImport.h" +#include "itkVTKImageExport.h" +#include "itkVTKImageExportBase.h" +#include +#include + +//template +//void ConnectITKToVTK(ITK_Exporter in, VTK_Importer* out) + +/*template +void ConnectITKToVTK(itk::VTKImageExport* in, vtkImageImport* out) */ +template +void ConnectITKToVTK(ITK_Exporter in, VTK_Importer* out) +{ + 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()); + }; + + //template + //void ConnectVTKToITK(vtkImageExport* in, itk::VTKImageImport* out) +template +void ConnectVTKToITK(VTK_Exporter *in, ITK_Importer out) + { + 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()); + }; + #endif \ No newline at end of file diff --git a/dicomUtils.cpp b/dicomUtils.cpp new file mode 100755 index 0000000..c089dd2 --- /dev/null +++ b/dicomUtils.cpp @@ -0,0 +1,485 @@ +#include "dicomUtils.h" + + +//FUNCTION DECLARATION NECESSARY FOR COPY/PASTE FROM vtkGDCMImageReader +// const char *gGetStringValueFromTag(const gdcm::Tag& t, const gdcm::DataSet& ds); + + const char *gGetStringValueFromTag(const gdcm::Tag& t, const gdcm::DataSet& ds) +{ + static std::string buffer; + buffer = ""; // cleanup previous call + if( ds.FindDataElement( t ) ) + { + const gdcm::DataElement& de = ds.GetDataElement( t ); + const gdcm::ByteValue *bv = de.GetByteValue(); + if( bv ) // Can be Type 2 + { + buffer = std::string( bv->GetPointer(), bv->GetLength() ); + // Will be padded with at least one \0 + } + } + // Since return is a const char* the very first \0 will be considered + return buffer.c_str(); +}; + + //GENERAL INDEPENDENT FUNCTION TO CHECK FOR FILE DICOM MODALITY (PLAN, STRUCT, IMAGE) REQUIRES CORRECT PATH +const char * gCheckDICOMModality(const char *filename) +{ + gdcm::Reader gR; + gR.SetFileName(filename);//str.toAscii().data()); + if(!gR.Read()) + return("NODCMFILE"); + + const gdcm::File &file = gR.GetFile(); + const gdcm::DataSet &ds = file.GetDataSet(); + return(gGetStringValueFromTag( gdcm::Tag(0x0008,0x0060), ds) ); //RETURN MODALITY +}; + +int gCheckDICOMModalityToInt(const char *filename){ + + const char * tmp = gCheckDICOMModality(filename); + //cout<< tmp< sqiPS = gSetupSequence.GetValueAsSQ(); + const gdcm::DataSet& gPatSetupNest = sqiPS->GetItem(1).GetNestedDataSet(); + //SET PATIENT ORIENTATION STRING + strcpy(PatientOrientation, gGetStringValueFromTag(gdcm::Tag(0x0018, 0x5100),gPatSetupNest)); + + const gdcm::DataElement &gBeamSequence=ds.GetDataElement(gdcm::Tag(0x300a,(IsCarbon==false ? 0x03a2 : 0x00b0))); +// (THESE ARE THE BEAMS) + gdcm::SmartPointer sqi = gBeamSequence.GetValueAsSQ(); + if(!sqi || !sqi->GetNumberOfItems()) + { + cout<<"usteria" <GetNumberOfItems(); + //ALLOCATE BEAM STRUCTURES + Beams=new IonBeamProperties* [NumberOfBeams]; + memset(Beams,NULL,NumberOfBeams*sizeof(IonBeamProperties *)); + + + //ITERATES ON BEAM NUMBER + for(int i=0;iGetItem(i+1); //ITEM START FROM 1 + const gdcm::DataSet& gBeamNestedds = item.GetNestedDataSet(); //THIS IS BEAM DATASET + + //ALLOCATE BEAM CLASS + Beams[i]=new IonBeamProperties; + //READ BEAM DATA + Beams[i]->BeamNumber = (atoi(gGetStringValueFromTag(gdcm::Tag(0x300a, 0x00c0),gBeamNestedds))); //BEAM NUMBER + strcpy (Beams[i]->BeamName,(gGetStringValueFromTag(gdcm::Tag(0x300a, 0x00c2),gBeamNestedds))); //BEAM NAME + + if(IsCarbon==false) + strcpy (Beams[i]->SupportType,gGetStringValueFromTag(gdcm::Tag(0x300a, 0x0350),gBeamNestedds));//PATIENT SUPPORT TYPE IMPLEMENT TRICK FOR THE CHAIR + else strcpy(Beams[i]->SupportType,"TABLE"); + + strcpy(Beams[i]->SupportId,gGetStringValueFromTag(gdcm::Tag(0x300a, 0x0352),gBeamNestedds));//PATIENT SUPPORT TYPE IMPLEMENT TRICK FOR THE CHAIR + +//gBeams[i]->gSetSupportType("CHAIR"); + + //READ GEOMETRY + //READ SEQUENCE OF CONTROL POINTS + //v04Build27 DO DIFFERENTLY IF PROTONS OR CARBONS + const gdcm::DataElement &gCPSequence=gBeamNestedds.GetDataElement(gdcm::Tag(0x300a,IsCarbon==false ? 0x03a8 : 0x0111)); + //FIND ITEM SEQUENCE (THESE ARE THE CONTROL POINTS) + gdcm::SmartPointer sqicp = gCPSequence.GetValueAsSQ(); + + //READ DATA FROM ITEM 1 + const gdcm::DataSet& gCPNestedds = sqicp->GetItem(1).GetNestedDataSet(); + + //GANTRY ANGLE (NEEDED FOR CHAIR ?) + Beams[i]->GantryAngle=atoi(gGetStringValueFromTag(gdcm::Tag(0x300a, 0x011e),gCPNestedds)); + + //ISOCENTER POSITION (THIS IS BEAM POSITION NOT PATIENT ALIGNMENT (VOLUME CENTER) POSITION) + double iso[3]; + char str[MAXSTRINGLENGHT]; + strcpy(str,gGetStringValueFromTag(gdcm::Tag(0x300a, 0x012c),gCPNestedds)); + + Beams[i]->IsocenterPosition[0]=atof(strtok(str,"\\")); + Beams[i]->IsocenterPosition[1]=atof(strtok(NULL,"\\")); + Beams[i]->IsocenterPosition[2]=atof(strtok(NULL,"\\")); +// Beams[i]->IsocenterPosition[](iso[0],iso[1],iso[2]); + + //SUPPORT ANGLE (YAW ANGLE OF SUPPORT WITH RESPECT TO FIXED REFERENCE SYSTEM) AROUND Z AXIS (VERTICAL) + //POSITIVE COUNTER-CLOCKWISE WHEN VIEWED FROM TOP OF Z-Axis + Beams[i]->TableYawAngle=atof(gGetStringValueFromTag(gdcm::Tag(0x300a, 0x0122),gCPNestedds)); +/*if(i==1) + gBeams[i]->gSetTableYawAngle(0); +*/ + //TABLE TOP PITCH (ROTATION AROUND X AXIS, i.e. LATERAL AXIS OF TABLE AND BEAM DIRECTION IN FIXED REF SYS) + ////v04Build27 IF PROTONS READ IT FROM PLAN; IF CARBONS SET DEFAULT VALUE (0.0) AND FORGET CHAIR + if(IsCarbon==false) + { gdcm::Attribute <0x300a, 0x0140> pa; + pa.SetFromDataElement(gCPNestedds.GetDataElement( pa.GetTag())); + Beams[i]->TablePitchAngle =pa.GetValue(); + } + else + (Beams[i]->TablePitchAngle)=(0.0); + +//v04 Build23 SET SUPPORT TYPE CORRECTLY IF PITCH IS 90 (IT IS CHAIR); DO IT FOR ALL BEAMS +// if(i==0) +// { if(Beams[i]->TablePitchAngle()>45) +// strcpy (Beams[i]->SupportType,("CHAIR")); +// } +// else if (i>0) +// { if(Beams[0]->SupportChair()==true) +// Beams[i]->gSetSupportType("CHAIR"); +// } + + //TABLE TOP ROLL (ROTATION AROUND Y AXIS, i.e. LONGITUDINAL AXIS OF TABLE) + //v04Build27 IF PROTONS READ IT FROM PLAN; IF CARBONS SET DEFAULT VALUE (0.0) + if(IsCarbon==false) + { gdcm::Attribute <0x300a, 0x0144> ra; + ra.SetFromDataElement(gCPNestedds.GetDataElement(ra.GetTag())); + Beams[i]->TableRollAngle =(ra.GetValue()); + } + else + (Beams[i]->TableRollAngle)=(0.0); + + + + + + //WHAT ABOUT TABLE TOP VERTICAL POSITION, LOGITUDINAL AND LATERAL POSITION ? USEFUL SOMEHOW ? + // THEY ARE READ AND USED TO REALIZE VOLUME CENTER ALIGNMENT IN ROOM ISOCENTER + //FIND PATIENT SETUP SEQUENCE (IT IS UNIQUE FOR ALL BEAMS BUT I DO IT BEAM BY BEAM) + //v04Build27 DO NOT LOOK FOR PATIENT SETUPSEQUENCE + /* if(!ds.FindDataElement(gdcm::Tag(0x300a,0x0180))) + throw(gException("CRITICAL: PATIENT SETUP SEQUENCE NOT FOUND IN RT ION PLAN")); + const gdcm::DataElement &gPatientSetupSequence=ds.GetDataElement(gdcm::Tag(0x300a,0x0180)); + gdcm::SmartPointer sqpt = gPatientSetupSequence.GetValueAsSQ(); + int psuseq=sqpt->GetNumberOfItems(); //CHECK NUMBER OF SETUPSEQUENCE VS BEAM NUMBER + + //READ DATA FROM ITEM 1 OR BEAM IS MORE PATIENT SETUP SEQUENCE ARE FOUND (ATTENTION !!!!) + //v03 Build30: READ TABLE POSITIONS FROM SETUPSEQUENCE + const gdcm::DataSet& gPSNestedds = sqpt->GetItem((i+1)<=psuseq ? (i+1) : 1).GetNestedDataSet(); //IF PSTIENT SETUP SEQUENCE < THAN BEAMS USE FIRST +*/ + //v04Build27 READ BEAMS ABSOLUTE GEOMETRY NOT FROM SETUP SEQUENCE BUT FROM CONTROL POINT SEQUENCE (NEW VERSION OIS FOR CARBONS) + + /*float tablelat=atof(gGetStringValueFromTag(gdcm::Tag(0x300a, 0x01d6),gPSNestedds)); + float tablelong=atof(gGetStringValueFromTag(gdcm::Tag(0x300a, 0x01d4),gPSNestedds)); + float tablevert=atof(gGetStringValueFromTag(gdcm::Tag(0x300a, 0x01d2),gPSNestedds));*/ + + float deltatablelat=atof(gGetStringValueFromTag(gdcm::Tag(0x300a, 0x012a),gCPNestedds)); + float deltatablelong=atof(gGetStringValueFromTag(gdcm::Tag(0x300a, 0x0129),gCPNestedds)); + float deltatablevert=atof(gGetStringValueFromTag(gdcm::Tag(0x300a, 0x0128),gCPNestedds)); + + //v03 (Build 30) NOW ADD TO TREATMENT BEMA EVENTUAL RELATIVE SHIFTS BETWEEN SETUP SEQUENCE AND STORE IN BEAM STRUCTURE ONLY TO TREATMETN BEAMS + // if(i==0) //THIS IS SETUP BEAM DO NOTHING +//v04Build26 TREAT ALL BEAMS THE SAME WAY (SET-UP+BEAMS)(NEW VERSION OF OIS) +//v04Build27 USE PPS GEOMETRY READ FROM POINT SEQUENCE + { Beams[i]->TableLatDispl=(deltatablelat); + Beams[i]->TableLongDispl=(deltatablelong); + Beams[i]->TableVertDispl=(deltatablevert); + } + /* else + { gBeams[i]->gSetTableLatDispl(tablelat); + gBeams[i]->gSetTableLongDispl(tablelong); + gBeams[i]->gSetTableVertDispl(tablevert); + }*/ +//v04Build26 DO NOT ADD DELTASHIFTS (NEW VERSION OF OIS) + //v04Build1: CHECK FOR VIRTUAL ISO + + + //ADD DELTASHIFTS TO TABLE DISPLACEMENTS + //v04Build26 DO NOT ADD DELTASHIFTS (NEW VERSION OF OIS) + /*gBeams[i]->gSetTableLatDispl(tablelat+atof(gGetStringValueFromTag(gdcm::Tag(0x300a, 0x012a),gCPNestedds))); + gBeams[i]->gSetTableLongDispl(tablelong+atof(gGetStringValueFromTag(gdcm::Tag(0x300a, 0x0129),gCPNestedds))); + gBeams[i]->gSetTableVertDispl(tablevert+atof(gGetStringValueFromTag(gdcm::Tag(0x300a, 0x0128),gCPNestedds))); + + //UPDATE ISOCENTER POSITION ON TREATMENT BEAM USING CT REFERENCE (LONG IS Z COORDINATE; Y IS VERTICAL; Z I) + //v04Build26 DO NOT ADD DELTASHIFTS (NEW VERSION OF OIS) + + gBeams[i]->gSetIsocenterPosition(iso[0]-deltatablelat,iso[1]-deltatablevert,iso[2]-deltatablelong); //SIGNS AND COORDINATE TO BE CHECKED (OK for LONG) + + } +*/ + + //v04Build28 CORRECT ISO POSITION IN CASE OF VIRTUAL ISO + /* if(i<0) //DO THIS ONLY FOR TREATMENT BEAMS + gBeams[i]->gSetIsocenterPosition(iso[0]-(gBeams[i]->gGetTableLatDispl()-gBeams[0]->gGetTableLatDispl()),iso[1]-(gBeams[i]->gGetTableVertDispl()-gBeams[0]->gGetTableVertDispl()),iso[2]-(gBeams[i]->gGetTableLongDispl()-gBeams[0]->gGetTableLongDispl())); //SIGNS AND COORDINATE TO BE CHECKED (OK for LONG) +*/ + //UPDATE AT FIRST CNAO PATIENT SET TABLE POSITION FROM PLAN AT THE ISOCENTER POSITION FOR EVERY BEAM + /* gBeams[i]->gSetTableLatDispl(gBeams[i]->gGetIsocenterPosition()[0]); + gBeams[i]->gSetTableLongDispl(gBeams[i]->gGetIsocenterPosition()[1]);gui + gBeams[i]->gSetTableVertDispl(gBeams[i]->gGetIsocenterPosition()[2]); +*/ + // FILL gPPS CLASS WITH CURRENT PPS CONFIGURATION UPDATING CONVENTIONS FROM PLAN TO PPS (-90 FOR CONVENTION CT-PPS) +// Beams[i]->gGetCurrentPPS()->gSetPPSXPlan(gBeams[i]->gGetTableLatDispl()); +// Beams[i]->gGetCurrentPPS()->gSetPPSYPlan(gBeams[i]->gGetTableLongDispl()); +// Beams[i]->gGetCurrentPPS()->gSetPPSYOrig(gBeams[i]->gGetTableLongDispl()); //SAVE ALSO AN ORIGINAL COPY FROM PLAN OF LONGITUFINAL POSITION (FOR INDEXING DIALOG) +// Beams[i]->gGetCurrentPPS()->gSetPPSZPlan(gBeams[i]->gGetTableVertDispl()); +// + + + //v03 Build35: ADD TO TREATMENT BEAMS YAW THE YAW VALUE OF SETUP BEAM TO RECOVER ABSOLUTE VALUES FROM RELATIVE ONES AS SENT BY OIS + if(i>0) // THIS IS TREATMENT BEAM + { //v04Build03 SET PROPER DELTA ANGLES FOR BEAM ROTATION (IF DELTA IS NEGATIVE SET IT NEGATIVE) + double yaw=Beams[i]->TableYawAngle; + if(yaw>180) + yaw=yaw-360; + //V04Build26 NEW VERSION OF MOSAIQ OIS DO NOT USE RELATIVE ANGLES + //yaw=yaw+gBeams[0]->gGetTableYawAngle(); + Beams[i]->TableYawAngle=(yaw); + + double pitch=Beams[i]->TablePitchAngle; + if(pitch>180) + pitch=pitch-360; + //V04Build26 NEW VERSION OF MOSAIQ OIS DO NOT USE RELATIVE ANGLES + //pitch=pitch+gBeams[0]->gGetTablePitchAngle(); + //v04Build23 SET ALL PITCH AT 90 (NOT STABLE) IT IS FOR THE CHAIR + if(pitch>150) pitch=90; + Beams[i]->TablePitchAngle=(pitch); + + + double roll=Beams[i]->TableRollAngle; + if(roll>180) + roll=roll-360; + //V04Build26 NEW VERSION OF MOSAIQ OIS DO NOT USE RELATIVE ANGLES + //roll=roll+gBeams[0]->gGetTableRollAngle(); + Beams[i]->TableRollAngle=(roll); + } + + //HERE 90 DEGREES IS SUMMED IN ORDER TO SOLVE TEH AMBIGUITY BETWEEN TPS REFERENCE AND ROOM/ODEVIS REFERENCE + //0 DEGREE YAW IN TPS BECOMES +90 YAW IN ROOM/PPS MEANING BEAM IS FROM LATERAL + //THIS WE DO IT JUST FOR THE USER INTERFACE DISPLAYING DEGREES OF YAW ROTATION EXPECTED OR TO BE INSERTED IN THE PPS + //v04Build24 ADJUST PPS YAW ANGLE FOR PPS AND CHAIR +// double yaw=gBeams[0]->gIsSupportChair()==false ? gBeams[i]->gGetTableYawAngle()+90.0 : gBeams[i]->gGetTableYawAngle()-90.0; +// //NOW SET +- 180 YAW ROTATION CONVENTION +// if(yaw>180) +// yaw=yaw-360; +// gBeams[i]->gGetCurrentPPS()->gSetPPSRotateDegreePlan(yaw); //CONVERSION FROM CT BASE PLAN TO PPS (ANGLE RANGES -180 +180) +// +// //v04Build23 ATTENZIONE ! +// if(gBeams[0]->gIsSupportChair()==false) gBeams[i]->gGetCurrentPPS()->gSetPPSPitchDegreePlan(gBeams[i]->gGetTablePitchAngle()); +// else gBeams[i]->gGetCurrentPPS()->gSetPPSPitchDegreePlan(gBeams[i]->gGetTablePitchAngle()-90); //NEED TO SUBTRACT 90 DEGRESS FOR PPS CONFIGURATION : THERE IS THE CHAIR +// +// gBeams[i]->gGetCurrentPPS()->gSetPPSRollDegreePlan(gBeams[i]->gGetTableRollAngle()); +// +// +// +// //SET CURRENT PPS CONFIGURATION WITH PLAN CONFIGURATION (THERE I TAKE CARE OF THE CHAIR) +// gBeams[i]->gGetCurrentPPS()->gSetPlanConfig(); + + /* //UPDATE TABLE DISPLACEMENT ACCORDING TO SIEMENS TPS USING THE ISOCENTER CO-ORDINATESIF DEFINE W.R.T. INDEXING POINT + //MIGHT NEED TO SUBTRACT COORDINATES OF STRUCTURE VOLUMECENTER IF RELATIVE REFERENCE SYSTEM IS NOT DEFINED IN TPS + gBeams[i]->gSetTableLatDispl(gBeams[i]->gGetIsocenterPosition()[0]); + gBeams[i]->gSetTableLongDispl(gBeams[i]->gGetIsocenterPosition()[1]); + gBeams[i]->gSetTableVertDispl(gBeams[i]->gGetIsocenterPosition()[2]);*/ +// gBeams[i]->gSetIsocenterPosition(iso[0]- gBeams[i]->gGetCurrentPPS()->getPPSXPlan(),iso[1]- gBeams[i]->gGetCurrentPPS()->getPPSYPlan(),iso[2]- gBeams[i]->gGetCurrentPPS()->getPPSZPlan()); + + + } + return true; + }; diff --git a/dicomUtils.h b/dicomUtils.h new file mode 100755 index 0000000..cbfdd3c --- /dev/null +++ b/dicomUtils.h @@ -0,0 +1,95 @@ + +//FUNCTION DECLARATION NECESSARY FOR COPY/PASTE FROM vtkGDCMImageReader +// const char *gGetStringValueFromTag(const gdcm::Tag& t, const gdcm::DataSet& ds); + +#ifndef DICOMUTILITIES +#define DICOMUTILITIES +//gdcm +#include +#include +#include +#include +#include + +using namespace std; + +#define MAXSTRINGLENGHT 255 +#define BADVALUE -9999 + +enum{ + RTPLAN, + RTSTRUCT, + IMAGE, + NODCMFILE +}; + +const char *gGetStringValueFromTag(const gdcm::Tag& t, const gdcm::DataSet& ds); +const char * gCheckDICOMModality(const char *filename); +int gCheckDICOMModalityToInt(const char *filename); + + +class IonBeamProperties{ +public: +IonBeamProperties(); + +~IonBeamProperties(); + +int BeamNumber; +char* BeamName; +char* SupportType; +char* SupportId; +int GantryAngle; +double* IsocenterPosition; +float TableYawAngle; +float TablePitchAngle; +float TableRollAngle; +float TableLatDispl; +float TableLongDispl; +float TableVertDispl; + +void PrintSelf(); +}; + +/*-----*/ +//CLASS gIonBEAMProperties +class RTPlan +{ + public: + char* PatientName; + char* PatientID; + char* PatientSex; + char* PatientAge; + char* PatientBirthDate; + char* SeriesNumber; + char* SeriesDescription; + char* StudyID; + char* StudyDescription; + char* Modality; + + double* IsocenterPosition; + double* VolumeCenter; + int BeamNumber; + char* BeamName; + double TableRollAngle; + double TableYawAngle; + double TablePitchAngle; + double TableLatDispl; + double TableVertDispl; + double TableLongDispl; + char* SupportType; + char* SupportId; + char* PatientOrientation; + int NumberOfBeams; + + IonBeamProperties** Beams; + + + ~RTPlan(); + + RTPlan(); + void PrintSelf(); + + bool fillRTPlan( char *filertionplan); + }; + +#endif \ No newline at end of file diff --git a/gLoadPatient.cpp b/gLoadPatient.cpp new file mode 100755 index 0000000..dccab8b --- /dev/null +++ b/gLoadPatient.cpp @@ -0,0 +1,434 @@ +#include "gLoadPatient.h" + + +/*_________________________________________________________- + +Patient loader for gLocalize. +no wrkDir behaviour. just load data +____________________________________________________________*/ + + +gLoadPatient::gLoadPatient(){ + patientInfos = new gPatientRTGeneralInfos; + readRT=0; + m_rtIsocenter=0; + initialized=false; + virtualIso=false; + //cout<< "gLoadPatient::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){ + memcpy(m_rtIsocenter, readRT->Beams[0]->IsocenterPosition, 3* sizeof(double) ); + if (strncmp (readRT->PatientOrientation,"HFS",3) == 0 ) + m_patientOrientation = SUPINE; + else if (strncmp (readRT->PatientOrientation,"HFP",3) == 0 ) + m_patientOrientation = PRONE; + else m_patientOrientation = NOTDEFINED; + + emit 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[0]->IsocenterPosition[0] <Beams[2]->IsocenterPosition[0]<Beams[0]->IsocenterPosition[1] <Beams[2]->IsocenterPosition[1]<Beams[0]->IsocenterPosition[2] <Beams[2]->IsocenterPosition[2]<NumberOfBeams > 2 && + (readRT->Beams[0]->IsocenterPosition[0] != readRT->Beams[2]->IsocenterPosition[0] || + readRT->Beams[0]->IsocenterPosition[1] != readRT->Beams[2]->IsocenterPosition[1] || + readRT->Beams[0]->IsocenterPosition[2] != readRT->Beams[2]->IsocenterPosition[2]) + ) + virtualIso=true; + else + virtualIso=false; + //cout << "virtual Iso: "<loadDICOM(patientInfos->CTfiles); + this->connectToVTK(); + this->changeRef(GOTSREF); + //trueOffset_rot_prev=trueOffset_rot; + + + //this->load2VTK(patientInfos); + } else { + //DO NOTHING. THE folderisempty signal was already emitted. + } + cout<< " gLoadPatient::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){ + emit 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 gLoadPatient::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 gLoadPatient::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 gLoadPatient::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 "gPatientRTGeneralInfos.h" +//#include "wrkDirParser.h" +#include +#include +#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 gLoadPatient : public QObject{ + Q_OBJECT +public: + gLoadPatient(); + +public slots: + 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; + +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); + +}; + +#endif \ No newline at end of file diff --git a/gLocalize.cpp b/gLocalize.cpp new file mode 100755 index 0000000..ba52403 --- /dev/null +++ b/gLocalize.cpp @@ -0,0 +1,380 @@ +#include "gLocalize.h" + +#include +#include + +gLocalize::gLocalize(){ + + abortSignal=false; +} + + +void gLocalize::localize(vtkImageData* vol, + int march_tresh, + double thrDown_D, + double thrUp_D, + double thr_HAUSD, + double thr_S, + QList selectedFilters){ + + abortSignal=false; + int i=0; + int progress=0; + //variabili + vtkSmartPointer fixOutput = vtkSmartPointer::New(); + + vtkSmartPointer filter = vtkSmartPointer::New(); + vtkSmartPointer marchingcubes = vtkSmartPointer::New() ; + vtkSmartPointer smoother = vtkSmartPointer::New(); + vtkSmartPointer fixed = vtkSmartPointer :: New(); + + + + + + marchingcubes->SetInput(vol); + + + //The algorithm starts with a marching cube at a given intensity level threshold + marchingcubes->SetValue(0,march_tresh); // Surface #0, iso-value=1 + marchingcubes->SetComputeNormals(0); + if (marchingcubes.GetPointer()->GetNumberOfInputConnections(0)==0) + {return; + //EMIT FAIL QUALCOSA + //return -1; + } + marchingcubes->Update(); + std::cout<< "inizio run" <GetNumberOfContours()); + if(item.toInt()<=0){ + throw("CRITICAL: No surfaces found with given treshold"); + QList dummylist; + // emit sendrenderMarkerList (dummylist); + } + +// Smoothing filter and normals calculation on marching cubes output data + smoother->SetInput(marchingcubes->GetOutput()); + smoother->SetNumberOfIterations(20); + smoother->NormalizeCoordinatesOn(); + smoother->SetPassBand(0.01); + smoother->BoundarySmoothingOn(); + smoother->Update(); + + + vtkSmartPointer normals=vtkSmartPointer::New(); + normals->SetInput(smoother->GetOutput()); + vtkSmartPointer normalact=vtkSmartPointer::New(); + vtkSmartPointer normalmap=vtkSmartPointer::New(); + normalmap->SetInput( normals->GetOutput()); + normalact->SetMapper(normalmap); + +// Start of marker recognitioning procedure + + filter->AddInput(normals->GetOutput()); + filter->SetExtractionModeToAllRegions(); + filter->SetColorRegions(0); + filter->Update(); + QString regionnumber = regionnumber.number(filter->GetNumberOfExtractedRegions()); + filter->SetExtractionModeToSpecifiedRegions(); + filter->Update(); + + //Some data allocation: + //surflist: list of surfaces where the recognized marker are added + //tempsurf: one surface used as temporary variable during geometrical filtering + QList surflist; + surflist.clear(); + surf tempsurf; + + + //Double check on number of surface in ConnectivityFilter output + if(regionnumber.toInt()==0){ + std::cout<<"ok"< dummylist; + + // emit sendrenderMarkerList (dummylist); + }; + if(regionnumber.toInt()>3000){ + cout<<"ho trovato troppe superfici: "<AddSpecifiedRegion(idouble); + filter->Update(); + + // Filter on bounding box diagonal lenght + if(selectedFilters.at(0)) + if(ok==1) { + tempsurf.lenght=filter->GetOutput()->GetLength(); + if(tempsurf.lenghtthrUp_D ) + ok=0; + } + + //Hausdorf reads from tempsurf the bounding box center and use it as reference center + if(selectedFilters.at(1)) + if(ok==1) { + OKd++; + filter->GetOutput()->GetCenter(tempsurf.center); + tempsurf.hausdorff=hausdorff(filter,tempsurf); + if(tempsurf.hausdorff> (thr_HAUSD) ) + ok=0; + + } + +// Filter based on sides difference + if(selectedFilters.at(2)) + if(ok==1) { + OKh++; + filter->GetOutput()->GetBounds(tempsurf.bounds); + tempsurf.confronto_lati=lati(tempsurf.bounds); + if(tempsurf.confronto_lati>thr_S) + ok=0; + } + + //if all filters are verified, add a surface index and append it to the surflist + if(ok==1) { + OKs++; + tempsurf.sorting=idouble; //indexing + surflist.append(tempsurf); + } + + // MANDATORY filter cleaning + filter->DeleteSpecifiedRegion(idouble); + +} + +cout<<"Nsurfaces"< final_list; + surf tmp_p; + + if(surflist.count()!=0){ + for(i=0;iAddSpecifiedRegion(surflist.at(i).sorting); + filter->Update(); + + //Centroid calculation needs mesh vertex + vtkSmartPointer edge_extractor =vtkSmartPointer::New(); + edge_extractor->RemoveAllInputs(); + edge_extractor->SetInput(filter->GetOutput()); + edge_extractor->Update(); + + vtkSmartPointer tmp = vtkSmartPointer::New(); + tmp=edge_extractor->GetOutput(); + tmp->Update(); + tmp->BuildCells(); + tmp->BuildLinks(); + + QList punti; + point ptemp; + + for (int id=0;id< (int) tmp->GetNumberOfPoints();id++){ + ptemp.x=tmp->GetPoint(id)[0]; + ptemp.y=tmp->GetPoint(id)[1]; + ptemp.z=tmp->GetPoint(id)[2]; + /*cout<GetOutput()->GetPoint(id)[0]<<" "<GetOutput()->GetPoint(id)[1]<<" "<GetOutput()->GetPoint(id)[2]<DeleteSpecifiedRegion(surflist.at(i).sorting); + + final_list.append(tmp_p); + }; + + }; + + + for(int ii=0;ii punti) +{ + int i; + point centroide; + centroide.x=0,centroide.y=0,centroide.z=0; + for ( i=0;id2 && d1>d3) return d1; + if(d2>d1 && d2>d3) return d2; + if(d3>d2 && d3>d1) return d3; + return 9999; +} + + +int gLocalize::get_byteorder() +{ + union { + long l; + char c[4]; + } test; + test.l = 1; + if( test.c[3] && !test.c[2] && !test.c[1] && !test.c[0] ) + return ORDER_BIGENDIAN; + + if( !test.c[3] && !test.c[2] && !test.c[1] && test.c[0] ) + return ORDER_LITTLEENDIAN; + + return ORDER_UNKNOWN; +} + +/** + * Implementation of the Hausdorff distance from a reference 1 cm diameter sphere + */ +double gLocalize::hausdorff(vtkPolyDataConnectivityFilter* filter, surf tempsurf) +{ + vtkSmartPointer source_sphere = vtkSmartPointer::New(); + source_sphere->SetCenter(tempsurf.center[0],tempsurf.center[1],tempsurf.center[2]); + source_sphere->SetRadius(5.0); + source_sphere->Update(); + + vtkSmartPointer sphere = source_sphere->GetOutput(); + sphere->Update(); + sphere->BuildCells(); + + vtkSmartPointer edge_extractor =vtkSmartPointer::New(); + edge_extractor->RemoveAllInputs(); + edge_extractor->SetInput(filter->GetOutput()); + edge_extractor->Update(); + + vtkSmartPointer tmp = vtkSmartPointer::New(); + tmp=edge_extractor->GetOutput(); + tmp->Update(); + tmp->BuildCells(); + tmp->BuildLinks(); + + QList punti_sup; + point ptemp; + + for (int id=0;id< (int) tmp->GetNumberOfPoints();id++){ + ptemp.x=tmp->GetPoint(id)[0]; + ptemp.y=tmp->GetPoint(id)[1]; + ptemp.z=tmp->GetPoint(id)[2]; + /*cout<GetOutput()->GetPoint(id)[0]<<" "<GetOutput()->GetPoint(id)[1]<<" "<GetOutput()->GetPoint(id)[2]< punti_sphere; + for (int id=0;id< (int) sphere->GetNumberOfPoints();id++){ + ptemp.x=sphere->GetPoint(id)[0]; + ptemp.y=sphere->GetPoint(id)[1]; + ptemp.z=sphere->GetPoint(id)[2]; + /*cout<GetOutput()->GetPoint(id)[0]<<" "<GetOutput()->GetPoint(id)[1]<<" "<GetOutput()->GetPoint(id)[2]< largest) largest = dist; + } + + summin += smallest; + summax += largest; + + if (smallest > maxmin) maxmin = smallest; // so this is max min + if (largest > maxmax) maxmax = largest; // and this is max max + } + +punti_sphere.clear(); +punti_sup.clear(); +return maxmin; // Hausdorff distance +} \ No newline at end of file diff --git a/gLocalize.h b/gLocalize.h new file mode 100755 index 0000000..a590f53 --- /dev/null +++ b/gLocalize.h @@ -0,0 +1,215 @@ +#ifndef _GLOCALIZE_H_ +#define _GLOCALIZE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//#include "gSkullRemoval.h" + +#include +#include +#include +#include +#include +#include +//#include "gTRiPtools_b.h" +#include +//#include "resampler.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// #include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + + +#define ORDER_BIGENDIAN 0 +#define ORDER_LITTLEENDIAN 1 +#define ORDER_UNKNOWN 2 + +// Some Macros to make the code more readable +// + +//#ifndef SWAP_TMP_INT +// #define SWAP_TMP_INT +// #define EVAL_INDEX(X,Y,L) (reference[ (Y)*(L) +(X)]) +// int swap_tmp_int; +// #define INT_SWAP(X,Y) swap_tmp_int=X; X=Y; Y=swap_tmp_int; +//#endif +// +// + +#define N_ITERAZ_ICP 100 + + +typedef struct { + +public: +double x; +double y; +double z; +}point; + + +typedef struct { + +public: +int sorting; +int surfn; +double lenght; +int ntriangle; +int npoint; +double bounds[6]; +double center[3]; +double volume; +int ismarker; +point centroid; +double centroid_median [3]; +point fitting ; +double confronto_lati; +double hausdorff; +}surf; + + + + + +class gLocalize : public QObject{ + +Q_OBJECT + +public: + gLocalize(); + +public slots: + void localize(vtkImageData* vol, + int march_tresh, + double thrDown_D, + double thrUp_D, + double thr_HAUSD, + double thr_S, + QList selectedFilters); + void callAbort(){ + cout<< "Aborted" <); + void localizationProgress(int); + void localizationAborted(); + +private: + double hausdorff(vtkPolyDataConnectivityFilter* filter, surf tempsurf); + int get_byteorder(); + double lati(double p[6]); + point centroid(QList< point > punti); + volatile bool abortSignal; + + + /** + * Functions for Qlist sorting + */ + bool lenghtLessThan(const surf &d1, const surf &d2){ return d1.lenght > d2.lenght; }; + bool tringleLessThan(const surf &d1, const surf &d2){ return d1.ntriangle > d2.ntriangle; }; + bool zLessThan(const surf &d1, const surf &d2){ return d1.center[2] < d2.center[2]; }; + bool confronto_latiLessThan(const surf &d1, const surf &d2){return d1.confronto_lati +#include +#define MAXSTRINGLENGHT 256 + + +class gPatientRTGeneralInfos { + +public: + gPatientRTGeneralInfos(){ + PatientName = new char [MAXSTRINGLENGHT]; + PatientID = new char [MAXSTRINGLENGHT]; + PatientSex = new char [MAXSTRINGLENGHT]; + PatientAge = new char [MAXSTRINGLENGHT]; + PatientBirthDate = new char [MAXSTRINGLENGHT]; + + SeriesNumber = new char [MAXSTRINGLENGHT]; + SeriesDescription = new char [MAXSTRINGLENGHT]; + StudyID = new char [MAXSTRINGLENGHT]; + StudyDescription = new char [MAXSTRINGLENGHT]; + + PatientOrientation= new char [MAXSTRINGLENGHT]; + + clearInfo(); + }; + + void clearInfo(){ + filenames.clear(); + CTfiles.clear(); + rtIonPlanPath.clear(); + rtStructurePath.clear(); + PatientName[0]=0; + PatientID[0]=0; + PatientSex[0]=0; + PatientBirthDate[0]=0; + SeriesNumber[0]=0; + SeriesDescription[0]=0; + StudyID[0]=0; + StudyDescription[0]=0; + PatientOrientation[0]=0; + }; + + ~gPatientRTGeneralInfos(){ + delete [] PatientName; + delete [] PatientID; + delete [] PatientSex; + delete [] PatientAge; + delete [] PatientBirthDate; + delete [] SeriesNumber; + delete [] SeriesDescription; + delete [] StudyID; + delete [] StudyDescription; + delete [] PatientOrientation; + }; + +public: + char* PatientName; + char* PatientID; + char* PatientSex; + char* PatientAge; + char* PatientBirthDate; + char* SeriesNumber; + char* SeriesDescription; + char* StudyID; + char* StudyDescription; + char* PatientOrientation; + + std::vector filenames; + QString rtIonPlanPath; + QString rtStructurePath; + std::vector CTfiles; + +}; + + +#endif \ No newline at end of file diff --git a/gRen.cpp b/gRen.cpp new file mode 100755 index 0000000..e130afa --- /dev/null +++ b/gRen.cpp @@ -0,0 +1,434 @@ + +#include "gRen.h" + +gRen::gRen( QVTKWidget* m_qvtk){ + CTinfos.clear(); + qvtk= m_qvtk; + nmarker=0; + + markerSources = 0; + markerMappers = 0; + markerActors = 0; + + this-> buildRenderPipe(); + this-> createRenderers(); + +} + + +void gRen::onAutoSkullMaskEnd(vtkImageData* fixedVol){ + cout << "Auto masking complete"<::New(); + //CTvolume->Initialize(); + CTvolume->DeepCopy(fixedVol); + CTvolume->Update(); + + extractVOI->Update(); + ren1->Render(); + qvtk->GetRenderWindow()->Render(); +} + + +void gRen::loadVolume(vtkImageData* volCT){ + + CTvolume=vtkSmartPointer::New(); + CTvolume->Initialize(); + CTvolume->DeepCopy(volCT); + CTvolume->Update(); + CTvolume->GetExtent(CTinfos.extent); + CTvolume->GetSpacing(CTinfos.spacing); + CTvolume->GetOrigin(CTinfos.origin); + cout<< "ORIGIN REN: "<SetInput(CTvolume/*extractVOI->GetOutput()*/); +#else + volumeMapper->SetInputData(CTvolume); +#endif + ren1->AddViewProp(volume); + + if(volume->GetVisibility() == 0) + volume->SetVisibility(false); + else + volume->SetVisibility(true); + + extractVOI->SetInput(CTvolume); + extractVOI->SetVOI(CTvolume->GetExtent()); + extractVOI->Update(); + + boxCallback_NEW->setNominalGeometry(volCT->GetExtent(),volCT->GetBounds(),volCT->GetSpacing()); + boxWidget_NEW->SetRotationEnabled(false); + boxWidget_NEW->SetScalingEnabled(true); + boxWidget_NEW->SetTranslationEnabled(true); + boxWidget_NEW->SetPriority(1); + boxWidget_NEW->SetInput((vtkDataSet *) CTvolume); + boxWidget_NEW->PlaceWidget(); + boxWidget_NEW->InsideOutOn(); + boxWidget_NEW->GetOutlineProperty()->SetRepresentationToWireframe(); + boxWidget_NEW->GetOutlineProperty()->SetAmbient(1.0); + boxWidget_NEW->GetOutlineProperty()->SetAmbientColor(1,1,1); + boxWidget_NEW->GetOutlineProperty()->SetLineWidth(3); + boxWidget_NEW->GetSelectedOutlineProperty()->SetRepresentationToWireframe(); + boxWidget_NEW->GetSelectedOutlineProperty()->SetAmbient(1.0); + boxWidget_NEW->GetSelectedOutlineProperty()->SetAmbientColor(1,0,0); + boxWidget_NEW->GetSelectedOutlineProperty()->SetLineWidth(3); + + outline-> SetInputConnection(extractVOI->GetOutputPort()); + + planeWidgetX->SetInput(extractVOI->GetOutput()); + planeWidgetY->SetInput(extractVOI->GetOutput()); + planeWidgetZ->SetInput(extractVOI->GetOutput()); + + if(outlineActor->GetVisibility() == 0){ + } + else{ + planeWidgetX->On(); + planeWidgetY->On(); + planeWidgetZ->On(); + } + + orthoPlanes->SetPlane(0, planeWidgetX); + orthoPlanes->SetPlane(1, planeWidgetY); + orthoPlanes->SetPlane(2, planeWidgetZ); + orthoPlanes->ResetPlanes(); + + vtkCamera * cam = ren1->GetActiveCamera(); + cam->SetPosition(0, 0, -1); + cam->SetFocalPoint(0, 0, 0); + cam->SetViewUp(0, -1, 0); + + ren1->ResetCamera(); + ren1->Render(); + qvtk->GetRenderWindow()->Render(); + + emit PatientLoaded(); +} + +void gRen::createRenderers(){ + ren1 = vtkRenderer::New(); + ren1->SetBackground(0.13, 0.25, 0.32); + qvtk->GetRenderWindow()->AddRenderer(ren1); + ren1->Render(); + + ren1->AddActor(assi); + ren1->AddActor( outlineActor); + + + boxWidget_NEW = vtkBoxWidget::New(); + boxWidget_NEW->SetInteractor(qvtk->GetInteractor()); + boxWidget_NEW->SetPlaceFactor(1.0); + + // - - - - - - - - - - - - - - - - - - - - - + + boxCallback_NEW->rcm=volumeMapper; + + boxWidget_NEW->AddObserver(vtkCommand::InteractionEvent, boxCallback_NEW); +} + +void gRen::buildRenderPipe(){ + + outline = vtkSmartPointer::New(); + + vtkPolyDataMapper* outlineMapper = vtkPolyDataMapper::New(); + outlineMapper->SetInputConnection(outline->GetOutputPort()); + + outlineActor = vtkSmartPointer ::New(); + outlineActor->SetMapper( outlineMapper); + + vtkCellPicker* picker = vtkCellPicker::New(); + picker->SetTolerance(0.005); + + vtkProperty* ipwProp = vtkProperty::New(); + //// //assign default props to the ipw's texture plane actor + + planeWidgetX = vtkImagePlaneWidget::New(); + planeWidgetX->SetInteractor( qvtk->GetRenderWindow()->GetInteractor()); + planeWidgetX->SetKeyPressActivationValue('x'); + planeWidgetX->SetPicker(picker); + planeWidgetX->RestrictPlaneToVolumeOn(); + planeWidgetX->GetPlaneProperty()->SetColor(1,0,0); + planeWidgetX->SetTexturePlaneProperty(ipwProp); + planeWidgetX->TextureInterpolateOff(); + planeWidgetX->SetResliceInterpolateToNearestNeighbour(); + planeWidgetX->SetPlaneOrientationToXAxes(); + planeWidgetX->DisplayTextOn(); + planeWidgetX->InteractionOff(); + planeWidgetX->InteractionOn(); + + planeWidgetY = vtkImagePlaneWidget::New(); + planeWidgetY->SetInteractor( qvtk->GetRenderWindow()->GetInteractor()); + planeWidgetY->SetKeyPressActivationValue('y'); + planeWidgetY->SetPicker(picker); + planeWidgetY->GetPlaneProperty()->SetColor(1,1,0); + planeWidgetY->SetTexturePlaneProperty(ipwProp); + planeWidgetY->TextureInterpolateOn(); + planeWidgetY->SetResliceInterpolateToLinear(); + planeWidgetY->SetPlaneOrientationToYAxes(); + planeWidgetY->SetLookupTable( planeWidgetX->GetLookupTable()); + planeWidgetY->DisplayTextOn(); + planeWidgetY->UpdatePlacement(); + + planeWidgetZ = vtkImagePlaneWidget::New(); + planeWidgetZ->SetInteractor( qvtk->GetRenderWindow()->GetInteractor()); + planeWidgetZ->SetKeyPressActivationValue('z'); + planeWidgetZ->SetPicker(picker); + planeWidgetZ->GetPlaneProperty()->SetColor(0,0,1); + planeWidgetZ->SetTexturePlaneProperty(ipwProp); + planeWidgetZ->TextureInterpolateOn(); + planeWidgetZ->SetResliceInterpolateToCubic(); + planeWidgetZ->SetPlaneOrientationToZAxes(); + planeWidgetZ->SetLookupTable( planeWidgetX->GetLookupTable()); + planeWidgetZ->DisplayTextOn(); + + + assi= vtkAxesActor::New(); + assi->SetOrigin(0,0,0); + assi->AxisLabelsOff(); + assi->SetTotalLength(15,15,15); + + volumeMapper = vtkSmartPointer::New(); + volumeMapper->SetBlendModeToComposite(); // composite first + + volumeProperty = vtkSmartPointer::New(); + volumeProperty->ShadeOff(); + volumeProperty->SetInterpolationType(VTK_LINEAR_INTERPOLATION); + + compositeOpacity = vtkSmartPointer::New(); + compositeOpacity->AddPoint(-3024, 0, 0.5, 0.0 ); + compositeOpacity->AddPoint(-16, 0, .49, .61 ); + compositeOpacity->AddPoint(-10, .1, .49, .4 ); + compositeOpacity->AddPoint(500, .4, .49, .4 ); + compositeOpacity->AddPoint(641, .72, .5, 0.0 ); + compositeOpacity->AddPoint(1700, .8, .5, 0.0 ); + compositeOpacity->AddPoint(2500, .8, .5, 0.0 ); + compositeOpacity->AddPoint(3071, .71, 0.5, 0.0); + volumeProperty->SetScalarOpacity(compositeOpacity); // composite first. + + + color = vtkSmartPointer::New(); + color->AddRGBPoint( -3024, 0, 0, 0, 0.5, 0.0 ); + color->AddRGBPoint( -16, 0.73, 0.25, 0.30, 0.49, .61 ); + color->AddRGBPoint( -10, .3, .3, .3, .5, 0.0 ); + color->AddRGBPoint( 500, .7, .7, .7, .5, 0.0 ); + color->AddRGBPoint( 641, .90, .82, .56, .5, 0.0 ); + color->AddRGBPoint( 1700, 1, 0,0, .5, 1 ); + color->AddRGBPoint( 2500, 1, 0,0, .5,.1 ); + color->AddRGBPoint( 3071, 1, 1, 1, .5, 0.0 ); + volumeProperty->SetColor(color); + + volumeProperty->ShadeOn(); + volumeProperty->SetAmbient(0.1); + volumeProperty->SetDiffuse(0.9); + volumeProperty->SetSpecular(0.2); + volumeProperty->SetSpecularPower(10.0); + volumeProperty->SetScalarOpacityUnitDistance(0.8919); + + //compositeOpacity = vtkSmartPointer::New(); + //compositeOpacity->AddPoint(0.0,0.0); + //compositeOpacity->AddPoint(80.0,1.0); + //compositeOpacity->AddPoint(80.1,0.0); + //compositeOpacity->AddPoint(255.0,0.0); + //volumeProperty->SetScalarOpacity(compositeOpacity); // composite first. + // + //color = vtkSmartPointer::New(); + //color->AddRGBPoint(0.0 ,0.0,0.0,1.0); + //color->AddRGBPoint(40.0 ,1.0,0.0,0.0); + //color->AddRGBPoint(255.0,1.0,1.0,1.0); + //volumeProperty->SetColor(color); + + volume = vtkSmartPointer::New(); + volume->SetMapper(volumeMapper); + volume->SetProperty(volumeProperty); + volume->SetVisibility(false); + + outlineActor->SetVisibility(true); + planeWidgetX->EnabledOn(); + planeWidgetY->EnabledOn(); + planeWidgetZ->EnabledOn(); + planeWidgetX->On(); + planeWidgetY->On(); + planeWidgetZ->On(); + + + extractVOI = vtkSmartPointer::New(); + boxCallback_NEW = vtkSmartPointer::New(); + boxCallback_NEW->setExtractVoiFilter(extractVOI); + + orthoPlanes = vtkImageOrthoPlanes::New(); + + +} + +void gRen::onSingleMarkerChange(int idx, bool state){ + + cout<< "onSingleMarkerChange : "<GetProperty()->SetColor(0,1,0); + } else { + markerActors[idx]->GetProperty()->SetColor(1,1,1); + } + qvtk->GetRenderWindow()->Render(); +} + + +void gRen::renderMarkers(QList marker_list){ + + cout << "renderMarkers" << endl; + + if(markerSources != 0) { + for(int ii=0;iiRemoveActor(markerActors[ii]); + markerActors[ii]->RemoveAllObservers(); + markerMappers[ii]->RemoveAllObservers(); + markerSources[ii]->RemoveAllObservers(); + markerSources[ii]->Delete(); + markerActors[ii]->Delete(); + markerMappers[ii]->Delete(); + } + delete [] markerActors; + delete [] markerMappers; + delete [] markerSources; + ren1->RemoveActor(pointLabels); + //modelPoints->Delete(); + //stringData->Delete(); + //pointLabels->Delete(); + //cells->Delete(); + //polyData->Delete(); + //ids->Delete(); + //ldm->Delete(); + } + + nmarker = marker_list.size(); + markerSources = new vtkSphereSource* [nmarker]; + markerMappers = new vtkPolyDataMapper* [nmarker]; + markerActors = new vtkActor* [nmarker]; + + modelPoints = vtkSmartPointer::New(); + modelPoints->Reset(); + modelPoints->SetNumberOfPoints(nmarker); + + + stringData = vtkSmartPointer::New(); + stringData->Reset(); + stringData->SetName("Labels"); + + for(int i=0;iInsertNextValue(QString::number(i).toLatin1().constData()); + modelPoints->InsertPoint(i,marker_list.at(i).centroid.x*1000,marker_list.at(i).centroid.y*1000,marker_list.at(i).centroid.z*1000); + + markerSources[i] = vtkSphereSource::New(); + markerSources[i]->SetCenter(marker_list.at(i).centroid.x*1000,marker_list.at(i).centroid.y*1000,marker_list.at(i).centroid.z*1000); + markerSources[i]->SetRadius(10); + + markerMappers[i]= vtkPolyDataMapper::New(); + markerMappers[i]->SetInputConnection (markerSources[i]->GetOutputPort()); + + markerActors[i]= vtkActor::New(); + markerActors[i]->SetMapper(markerMappers[i]); + markerActors[i]->GetProperty()->SetRepresentationToWireframe(); + markerActors[i]->GetProperty()->SetColor(0,1,0); + ren1->AddActor(markerActors[i]); + } + + cout << "modelPoints" << endl; + modelPoints->Modified(); + + cells = vtkSmartPointer::New(); + cells->Reset(); + cells->InsertNextCell(nmarker); + for(int i = 0; i < nmarker; i++) + cells->InsertCellPoint(i); + + polyData = vtkSmartPointer::New(); + polyData->Reset(); + polyData->SetPoints(modelPoints); + polyData->SetVerts(cells); + polyData->GetPointData()->AddArray(stringData); + + ids = vtkSmartPointer::New(); + ids->SetInput( polyData ); + ids->PointIdsOn(); + ids->CellIdsOn(); + ids->FieldDataOn(); + cout << "ids" << endl; + ldm = vtkSmartPointer::New(); + ldm->SetInputConnection( ids->GetOutputPort() ); + ldm->SetLabelModeToLabelFieldData(); + ldm->GetLabelTextProperty()->SetShadow(false); + ldm->GetLabelTextProperty()->SetFontSize(22); + cout << "plab" << endl; + pointLabels = vtkSmartPointer::New(); + pointLabels->SetMapper( ldm ); + ren1->AddActor(pointLabels); + qvtk->GetRenderWindow()->Render(); +} + +void gRen::onReferenceChange(double dx, double dy, double dz){ + + if(markerSources == 0) + return; + + for(int i=0;iSetCenter( + markerSources[i]->GetCenter()[0]-dx, + markerSources[i]->GetCenter()[1]-dy, + markerSources[i]->GetCenter()[2]-dz); + markerSources[i]->Update(); + + modelPoints->SetPoint(i, + modelPoints->GetPoint(i)[0]-dx, + modelPoints->GetPoint(i)[1]-dy, + modelPoints->GetPoint(i)[2]-dz); + } + + modelPoints->Modified(); + qvtk->GetRenderWindow()->Render(); +} + +void gRen::onRequestRenderChange(int idx){ + switch (idx){ + case 0: + volume->SetVisibility(true); + outlineActor->SetVisibility(false); + planeWidgetX->EnabledOff(); + planeWidgetY->EnabledOff(); + planeWidgetZ->EnabledOff(); + ren1->Render(); + break; + + case 1: + volume->SetVisibility(false); + outlineActor->SetVisibility(true); + planeWidgetX->EnabledOn(); + planeWidgetY->EnabledOn(); + planeWidgetZ->EnabledOn(); + ren1->Render(); + break; + + default: + break; + } +} + + +void gRen::onRequestManualMask(int idx){ + + switch (idx){ + case 0: + boxWidget_NEW->On(); + qvtk->GetRenderWindow()->Render(); + break; + + case 1: + boxWidget_NEW->Off(); + qvtk->GetRenderWindow()->Render(); + break; + + default: + break; + } + +} + + diff --git a/gRen.h b/gRen.h new file mode 100755 index 0000000..ed42f26 --- /dev/null +++ b/gRen.h @@ -0,0 +1,298 @@ +#ifndef _GREN_H_ +#define _GREN_H_ + + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "gLocalize.h" + + +#include + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + + +#include +#include +#include + + + + +class vtkMyCallback : public vtkCommand +{ +public: + vtkSmartVolumeMapper *rcm; + static vtkMyCallback *New() { return new vtkMyCallback; } + + virtual void Execute(vtkObject *caller, unsigned long, void*) + { + vtkBoxWidget *boxWidget = reinterpret_cast(caller); + + // Get the actual box coordinates/planes + vtkSmartPointer polydata = vtkSmartPointer::New(); + //static_cast(boxWidget->GetRepresentation())->GetPolyData (polydata); + + boxWidget->GetPolyData(polydata); + double *ac_bounds=polydata->GetBounds(); + /*check if box is larger than volume size*/ + // double *ac_bounds= boxWidget->GetRepresentation()->GetBounds(); + + for(int ii =0 ;ii<6 ;ii+=2 ){ + if(ac_bounds[ii]nominalBounds[ii]) + ac_bounds[ii]=nominalBounds[ii]; + } + + boxWidget->PlaceWidget(ac_bounds); +// boxWidget->Render(); + + /*clip the orthoplanes*/ + extractVOI->SetVOI( nominalExtent[0] + (int) (abs(ac_bounds[0]-nominalBounds[0])/VolSpacing[0]), + nominalExtent[1] - (int) (abs(ac_bounds[1]-nominalBounds[1])/VolSpacing[0]), + nominalExtent[2] +(int) (abs(ac_bounds[2]-nominalBounds[2])/VolSpacing[1]), + nominalExtent[3] -(int) (abs(ac_bounds[3]-nominalBounds[3])/VolSpacing[1]), + nominalExtent[4] + (int) (abs(ac_bounds[4]-nominalBounds[4])/VolSpacing[2]), + nominalExtent[5] -(int) (abs(ac_bounds[5]-nominalBounds[5])/VolSpacing[2]) + ); + + + /*clip the vol ren*/ + vtkPlanes *planes = vtkPlanes::New(); + // The implicit function vtkPlanes is used in conjunction with the + // volume ray cast mapper to limit which portion of the volume is + // volume rendered. + boxWidget->GetPlanes(planes); + this->rcm->SetClippingPlanes(planes); + } + + void setNominalGeometry(int* extents, double* bounds, double* spacing){ + memcpy(&nominalBounds,bounds,6*sizeof(double)); + memcpy(&nominalExtent,extents,6*sizeof(int)); + memcpy(&VolSpacing,spacing,3*sizeof(double)); + } + + void setExtractVoiFilter(vtkExtractVOI* p_extractVOI){ + extractVOI=p_extractVOI; + }; + +private: + vtkExtractVOI* extractVOI; + double nominalBounds[6]; + int nominalExtent[6]; + double VolSpacing[3]; +}; + + +// This does the actual work. +// Callback for the interaction +class vtkBoxCallback : public vtkCommand +{ + public: + static vtkBoxCallback *New() + { + return new vtkBoxCallback; + } + + void setExtractVoiFilter(vtkExtractVOI* p_extractVOI){ + extractVOI=p_extractVOI; + }; + + void setVolumeMapper(vtkSmartVolumeMapper* p_mapper){ + volMapper=p_mapper; + } + + void setRenderer(vtkRenderer* p_ren){ + ren=p_ren; + } + + void setNominalGeometry(int* extents, double* bounds, double* spacing){ + memcpy(&nominalBounds,bounds,6*sizeof(double)); + memcpy(&nominalExtent,extents,6*sizeof(int)); + memcpy(&VolSpacing,spacing,3*sizeof(double)); + } + + virtual void Execute(vtkObject *caller, unsigned long, void*) + { + + vtkBoxWidget2 *boxWidget = reinterpret_cast(caller); + + // Get the actual box coordinates/planes + vtkSmartPointer polydata = vtkSmartPointer::New(); + static_cast(boxWidget->GetRepresentation())->GetPolyData (polydata); + + /*check if box is larger than volume size*/ + double *ac_bounds= boxWidget->GetRepresentation()->GetBounds(); + + for(int ii =0 ;ii<6 ;ii+=2 ){ + if(ac_bounds[ii]nominalBounds[ii]) + ac_bounds[ii]=nominalBounds[ii]; + } + + boxWidget->GetRepresentation()->PlaceWidget(ac_bounds); + boxWidget->Render(); + + // Display the center of the box + // double p[3]; + // polydata->GetPoint(14,p); // As per the vtkBoxRepresentation documentation, the 15th point (index 14) is the center of the box + //std::cout << "Box center: " << p[0] << " " << p[1] << " " << p[2] << std::endl; + + //cout<<"nominal VOI"<GetVOI()[0]<<" "<GetVOI()[1]<<" "<GetVOI()[2]<<" "<< + // extractVOI->GetVOI()[3]<<" "<GetVOI()[4]<<" "<GetVOI()[5]<SetVOI( nominalExtent[0] + (int) (abs(ac_bounds[0]-nominalBounds[0])/VolSpacing[0]), + nominalExtent[1] - (int) (abs(ac_bounds[1]-nominalBounds[1])/VolSpacing[0]), + nominalExtent[2] +(int) (abs(ac_bounds[2]-nominalBounds[2])/VolSpacing[1]), + nominalExtent[3] -(int) (abs(ac_bounds[3]-nominalBounds[3])/VolSpacing[1]), + nominalExtent[4] + (int) (abs(ac_bounds[4]-nominalBounds[4])/VolSpacing[2]), + nominalExtent[5] -(int) (abs(ac_bounds[5]-nominalBounds[5])/VolSpacing[2]) + ); + + //extractVOI->Update(); + //volMapper->Update(); + ren->Render(); + + } + vtkBoxCallback(){} + +private: + vtkExtractVOI* extractVOI; + double nominalBounds[6]; + int nominalExtent[6]; + double VolSpacing[3]; + vtkRenderer* ren; + vtkSmartVolumeMapper* volMapper; + +}; + +class gRen : public QObject{ + Q_OBJECT + +public: + gRen( QVTKWidget* m_qvtk); + vtkImageData* getVolume(){return extractVOI->GetOutput();} + double* getSpacing(){return CTvolume->GetSpacing();}; + +private: + vtkSmartPointer CTvolume; + vtkSmartPointer outline; + vtkSmartPointer outlineActor; + vtkImageMapToColors* colorMap_X; + vtkImagePlaneWidget* planeWidgetX; + vtkImagePlaneWidget* planeWidgetY; + vtkImagePlaneWidget* planeWidgetZ; + QVTKWidget* qvtk; + vtkImageOrthoPlanes* orthoPlanes; + vtkAxesActor * assi; + vtkRenderer* ren1; + + vtkSmartPointer volumeMapper; + vtkSmartPointer color; + vtkSmartPointer compositeOpacity; + vtkSmartPointer volume; + vtkSmartPointer volumeProperty; + vtkSmartPointer boxWidget; + vtkSmartPointer boxRepresentation; + vtkSmartPointer boxCallback ; + vtkSmartPointer boxCallback_NEW; + vtkSmartPointer extractVOI; + vtkBoxWidget *boxWidget_NEW; + + vtkSphereSource** markerSources; + vtkPolyDataMapper** markerMappers; + vtkActor** markerActors; + vtkSmartPointer modelPoints; + vtkSmartPointer pointLabels; + int nmarker; + vtkSmartPointer stringData; + vtkSmartPointer cells ; + vtkSmartPointer polyData; + vtkSmartPointer ids; + vtkSmartPointer ldm; + + struct{ + int extent [6]; + double center [3]; + double origin [3]; + double spacing [3]; + QString fName; + void clear(){ + std::fill(extent,extent+6,9999); + std::fill(center,center+3,9999); + std::fill(origin,origin+3,9999); + std::fill(spacing,spacing+3,9999); + fName.clear(); + }; + }CTinfos; + + void buildRenderPipe(); + void createRenderers(); + + +public slots: + void loadVolume (vtkImageData* volCT); + void renderMarkers(QList marker_list); + void onRequestRenderChange(int idx); + void onRequestManualMask(int idx); + void onAutoSkullMaskEnd(vtkImageData* fixedVol); + void onSingleMarkerChange(int idx,bool state); + void onReferenceChange(double dx, double dy, double dz); + + + +signals: + void PatientLoaded(); + +}; + +#endif \ No newline at end of file diff --git a/gSkullRemoval.cpp b/gSkullRemoval.cpp new file mode 100755 index 0000000..80991ae --- /dev/null +++ b/gSkullRemoval.cpp @@ -0,0 +1,264 @@ +#include "gSkullRemoval.h" +// +///** +// * This will be setup as a callback for a progress event on an ITK +// * filter. +// */ +//struct ProgressDisplay +//{ +// +//public: +// QString observedProcess; +// +// ProgressDisplay(itk::ProcessObject* process,QString observed): m_Process(process) { +// observedProcess=observed; +// } +// +// void Display() +// { +// float progress = m_Process->GetProgress()*100.0; +// cout <::New(); + vtkExporter = vtkSmartPointer::New(); + smoothing = CurvatureFlowImageFilterType::New(); + connectedThreshold = ConnectedFilterType::New(); + binaryErodeFilter = ErodeFilterType::New(); + binaryDilateFilter = DilateFilterType::New(); + itkExporter1 = ExportFilter1Type::New(); + itkImporter = ImportFilterType::New(); + caster_writer_inv =CastingwriterINVFilterType::New(); + maskNegatedFilter = MaskNegatedFilterType::New(); + caster_writer = CastingwriterFilterType::New(); + caster = CastingFilterType::New(); + + /*TEST*/ + m_RedrawCommand = RedrawCommandType::New(); + m_RedrawCommand->SetCallbackFunction( this, &gSkullRemoval::ProcessEvent ); + m_RedrawCommand->SetCallbackFunction( this, &gSkullRemoval::ConstProcessEvent ); + + this->Observe(smoothing.GetPointer()); + this->Observe(connectedThreshold.GetPointer()); + this->Observe(binaryErodeFilter.GetPointer()); + this->Observe(binaryDilateFilter.GetPointer()); + this->Observe(maskNegatedFilter.GetPointer()); +} +gSkullRemoval::~gSkullRemoval() +{ + + +} + + + +void gSkullRemoval::runFilter(vtkImageData* inputVolume,double thr_low, double thr_up) +{ +// imageProcessing(); + // ProgressDisplay progDisplaySmooth (smoothing,"Smoothing"); + //itk::SimpleMemberCommand::Pointer progEventSmooth = itk::SimpleMemberCommand::New(); + //progEventSmooth->SetCallbackFunction(&progDisplaySmooth,&ProgressDisplay::Display); + //smoothing->AddObserver(itk::ProgressEvent(), progEventSmooth); + // ProgressDisplay progDisplayRegion (connectedThreshold,"Region growing"); + //itk::SimpleMemberCommand::Pointer progEventRegion = itk::SimpleMemberCommand::New(); + //progEventRegion->SetCallbackFunction(&progDisplayRegion,&ProgressDisplay::Display); + //connectedThreshold->AddObserver(itk::ProgressEvent(), progEventRegion); + + // ProgressDisplay progDisplayErode (binaryErodeFilter,"Morphological erode"); + // itk::SimpleMemberCommand::Pointer progEventErode = itk::SimpleMemberCommand::New(); + // progEventErode->SetCallbackFunction(&progDisplayErode,&ProgressDisplay::Display); + // binaryErodeFilter->AddObserver(itk::ProgressEvent(), progEventErode); + + // ProgressDisplay progDisplayDilate(binaryDilateFilter,"Morphological dilate"); + // itk::SimpleMemberCommand::Pointer progEventDilate = itk::SimpleMemberCommand::New(); + // progEventDilate->SetCallbackFunction(&progDisplayDilate,&ProgressDisplay::Display); + // binaryDilateFilter->AddObserver(itk::ProgressEvent(), progEventDilate); + + + vtkExporter->SetInput(inputVolume); + ConnectPipelines(vtkExporter.GetPointer(), itkImporter); + caster_writer_inv->SetInput(itkImporter->GetOutput()); + smoothing->SetInput( caster_writer_inv->GetOutput() ); + connectedThreshold->SetInput( smoothing->GetOutput() ); + connectedThreshold->Update(); + caster->SetInput( connectedThreshold->GetOutput() ); + smoothing->SetNumberOfIterations( 10 ); + smoothing->SetTimeStep( 0.125 ); + const InternalPixelType lowerThreshold = thr_low ; + const InternalPixelType upperThreshold = thr_up; + connectedThreshold->SetLower( lowerThreshold ); + connectedThreshold->SetUpper( upperThreshold ); + connectedThreshold->SetReplaceValue( 255 ); + InternalImageType::Pointer inputImage = caster_writer_inv->GetOutput(); + InternalImageType::SizeType size = inputImage->GetBufferedRegion().GetSize(); + InternalImageType::IndexType start = inputImage->GetBufferedRegion().GetIndex(); + + // set a seed by default in the center of the image. + InternalImageType::IndexType seed; + seed[0] = start[0] + size[0] / 2; + seed[1] = start[1] + size[1] / 2; + seed[2] = start[2] + size[2] / 2; + connectedThreshold->SetSeed( seed ); + + StructuringElementType structuringElement; + structuringElement.SetRadius( 10 ); + structuringElement.CreateStructuringElement(); + binaryDilateFilter->SetKernel( structuringElement ); + binaryErodeFilter->SetKernel( structuringElement ); + binaryDilateFilter->SetInput( caster->GetOutput() ); + binaryErodeFilter->SetInput( binaryDilateFilter->GetOutput() ); + binaryDilateFilter->SetDilateValue( 255 ); + binaryErodeFilter->SetErodeValue( 255 ); + +// if (dcmoutput == 1) writer->SetInput(binaryErodeFilter->GetOutput()); + maskNegatedFilter->SetInput1(caster_writer_inv->GetOutput()); + maskNegatedFilter->SetInput2(binaryErodeFilter->GetOutput()); + +// maskNegatedFilter->Update(); + + //caster_writer->SetInput(maskNegatedFilter->GetOutput()); + // if (dcmoutput == 1){ writer_internal->SetFileName( "/home/gio/dati/output/out_mask.dcm" ); + // writer_internal->SetInput(caster_writer->GetOutput());}; + +bool execeptCaught = false; + + itkImporter->ReleaseDataFlagOn(); + //caster_writer_inv->ReleaseDataFlagOn(); + connectedThreshold->ReleaseDataFlagOn(); + binaryDilateFilter->ReleaseDataFlagOn(); + binaryErodeFilter->ReleaseDataFlagOn(); + + //maskNegatedFilter->ReleaseDataFlagOn(); + + try { + maskNegatedFilter.GetPointer()->Update(); + } + catch( itk::ExceptionObject & excep ) + { + if (aborted) std::cerr << "Abort caught!" << std::endl; + else { + execeptCaught=true; + emit errMsg(QString(excep.GetDescription())); + std::cerr << "Exception caught !" << std::endl; + std::cerr << excep << std::endl; + } + }; + + if (!aborted && execeptCaught==false){ + + +// fixedVolume->DeepCopy(inputVolume); + +// try +// { +// // if (dcmoutput == 1) {writer->Update(); +// // writer_internal->Update();}; +// fixedVolume->DeepCopy(vtkImporter1->GetOutput()); +// cout<<"finito itk"< WriterType; + //WriterType::Pointer writer = WriterType::New(); + //writer->SetFileName("out.mha"); + //writer->SetInput( maskNegatedFilter->GetOutput() ); + //writer->Update(); + // + + + itkExporter1->SetInput( maskNegatedFilter->GetOutput() ); + ConnectPipelines(itkExporter1.GetPointer(), vtkImporter1); + vtkImporter1->Update(); + + emit skull_mask_end(vtkImporter1->GetOutput()); + //fixedVolume->DeepCopy(); + } +} + + +void gSkullRemoval::abortSignal() +{ + aborted=true; +// cout<<"arrivato segnale di abort in itk: setto gli abort generate data"<AbortExecuteOn(); + maskNegatedFilter.GetPointer()->AbortGenerateDataOn(); + binaryErodeFilter.GetPointer()->AbortGenerateDataOn(); + binaryDilateFilter.GetPointer()->AbortGenerateDataOn(); + connectedThreshold.GetPointer()->AbortGenerateDataOn(); + smoothing.GetPointer()->AbortGenerateDataOn(); + + // cout<<"fatto"<( caller ); + if(caller == smoothing) + emit skull_mask_upd("2/5 : Surface smoothing...", process->GetProgress()); + + if(caller == connectedThreshold) + emit skull_mask_upd("1/5 : Region growing...", process->GetProgress()); + + if(caller == binaryErodeFilter) + emit skull_mask_upd("4/5 : Morphological erode...", process->GetProgress()); + + if(caller ==binaryDilateFilter) + emit skull_mask_upd("3/5 : Morphological dilate...", process->GetProgress()); + + if(caller == maskNegatedFilter) + emit skull_mask_upd("5/5 : Volume masking...", process->GetProgress()); + + + + } +} + + + + +void +gSkullRemoval::ConstProcessEvent( const itk::Object * caller, + const itk::EventObject & event ) +{ + if( typeid( itk::ProgressEvent ) == typeid( event ) ) + { + itk::ProcessObject::ConstPointer process = + dynamic_cast< const itk::ProcessObject *>( caller ); + cout<< "B"<< process->GetProgress() <AddObserver( itk::ProgressEvent(), m_RedrawCommand.GetPointer() ); +} diff --git a/gSkullRemoval.h b/gSkullRemoval.h new file mode 100755 index 0000000..a098bcd --- /dev/null +++ b/gSkullRemoval.h @@ -0,0 +1,172 @@ +#ifndef GSKULLREMOVAL_H +#define GSKULLREMOVAL_H + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "itkQtAdaptor.h" + + + + const unsigned int Dimension = 3; + //typedef float InternalPixelType; + typedef signed short InternalPixelType; + typedef itk::Image< InternalPixelType, Dimension > InternalImageType; + typedef unsigned char OutputPixelType; + typedef itk::Image< OutputPixelType, Dimension > OutputImageType; + typedef short int writerPixelType; + typedef itk::Image< writerPixelType, Dimension > writerImageType; + typedef itk::CastImageFilter< InternalImageType, writerImageType > CastingwriterFilterType; + typedef itk::CastImageFilter< writerImageType, InternalImageType > CastingwriterINVFilterType; + typedef itk::ConnectedThresholdImageFilter< InternalImageType, InternalImageType > ConnectedFilterType; + typedef itk::CastImageFilter< InternalImageType, OutputImageType > CastingFilterType; + typedef itk::ImageFileReader< InternalImageType > ReaderType; + typedef itk::ImageFileWriter< OutputImageType > WriterType; + typedef itk::CurvatureFlowImageFilter< InternalImageType, InternalImageType > CurvatureFlowImageFilterType; + typedef itk::VTKImageImport ImportFilterType; + typedef itk::BinaryBallStructuringElement < unsigned char, 3 > StructuringElementType; + typedef itk::BinaryErodeImageFilter < OutputImageType, OutputImageType, StructuringElementType> ErodeFilterType; + typedef itk::BinaryDilateImageFilter < OutputImageType, OutputImageType, StructuringElementType> DilateFilterType; + typedef itk::MaskNegatedImageFilter MaskNegatedFilterType; +// typedef itk::ImageFileWriter< writerImageType > WriterTypeInternal; + typedef unsigned char MaskPixelType; + typedef itk::Image< MaskPixelType, Dimension > MaskImageType; + typedef itk::VTKImageExport< InternalImageType > ExportFilter1Type; + typedef itk::VTKImageExport< MaskImageType > ExportFilter2Type; + + template +void ConnectPipelines(ITK_Exporter exporter, VTK_Importer* importer) +{ + importer->SetUpdateInformationCallback(exporter->GetUpdateInformationCallback()); + importer->SetPipelineModifiedCallback(exporter->GetPipelineModifiedCallback()); + importer->SetWholeExtentCallback(exporter->GetWholeExtentCallback()); + importer->SetSpacingCallback(exporter->GetSpacingCallback()); + importer->SetOriginCallback(exporter->GetOriginCallback()); + importer->SetScalarTypeCallback(exporter->GetScalarTypeCallback()); + importer->SetNumberOfComponentsCallback(exporter->GetNumberOfComponentsCallback()); + importer->SetPropagateUpdateExtentCallback(exporter->GetPropagateUpdateExtentCallback()); + importer->SetUpdateDataCallback(exporter->GetUpdateDataCallback()); + importer->SetDataExtentCallback(exporter->GetDataExtentCallback()); + importer->SetBufferPointerCallback(exporter->GetBufferPointerCallback()); + importer->SetCallbackUserData(exporter->GetCallbackUserData()); +} + +/** + * This function will connect the given vtkImageExport filter to + * the given itk::VTKImageImport filter. + */ +template +void ConnectPipelines(VTK_Exporter* exporter, ITK_Importer importer) +{ + importer->SetUpdateInformationCallback(exporter->GetUpdateInformationCallback()); + importer->SetPipelineModifiedCallback(exporter->GetPipelineModifiedCallback()); + importer->SetWholeExtentCallback(exporter->GetWholeExtentCallback()); + importer->SetSpacingCallback(exporter->GetSpacingCallback()); + importer->SetOriginCallback(exporter->GetOriginCallback()); + importer->SetScalarTypeCallback(exporter->GetScalarTypeCallback()); + importer->SetNumberOfComponentsCallback(exporter->GetNumberOfComponentsCallback()); + importer->SetPropagateUpdateExtentCallback(exporter->GetPropagateUpdateExtentCallback()); + importer->SetUpdateDataCallback(exporter->GetUpdateDataCallback()); + importer->SetDataExtentCallback(exporter->GetDataExtentCallback()); + importer->SetBufferPointerCallback(exporter->GetBufferPointerCallback()); + importer->SetCallbackUserData(exporter->GetCallbackUserData()); +} + + + +class gSkullRemoval : public QObject +{ + Q_OBJECT + +public: + gSkullRemoval(); + ~gSkullRemoval(); + + + /*test*/ + + /** Command Class invoked for button redraw */ + typedef itk::MemberCommand< gSkullRemoval > RedrawCommandType; + /** Get Command */ + RedrawCommandType * GetRedrawCommand( void ) const; + + + /** Manage a Progress event */ + void ProcessEvent(itk::Object * caller, const itk::EventObject & event ); + void ConstProcessEvent(const itk::Object * caller, const itk::EventObject & event ); + + + /** Manage a Progress event */ + void Observe( itk::Object *caller ); + private: + + RedrawCommandType::Pointer m_RedrawCommand; + + /*ENDTEST*/ + public slots: + void runFilter(vtkImageData* inputVolume,double thr_low, double thr_up); + void abortSignal(); + +private: + vtkImageData *fixedVolume; + int dcmoutput; + bool aborted; + + +private: + vtkSmartPointer vtkImporter1; + vtkSmartPointer vtkExporter; + CurvatureFlowImageFilterType::Pointer smoothing; + ConnectedFilterType::Pointer connectedThreshold; + ErodeFilterType::Pointer binaryErodeFilter; + DilateFilterType::Pointer binaryDilateFilter; + ExportFilter1Type::Pointer itkExporter1; + CastingwriterINVFilterType::Pointer caster_writer_inv; + MaskNegatedFilterType::Pointer maskNegatedFilter; + ImportFilterType :: Pointer itkImporter; + writerImageType *importedITK; + InternalImageType *fixedITK; + CastingwriterFilterType::Pointer caster_writer ; + CastingFilterType::Pointer caster; + +signals: + void skull_mask_end(vtkImageData * fixedVolume); + void errMsg(QString msg); + void skull_mask_upd(QString msg, double val); + }; + + +#endif \ No newline at end of file diff --git a/icons/Thumbs.db b/icons/Thumbs.db new file mode 100755 index 0000000000000000000000000000000000000000..e5d174deb07e437c3b67ede78dce28e924ba834a GIT binary patch literal 18432 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 J{eN@>{s$DYW<>x1 literal 0 HcmV?d00001 diff --git a/icons/ortoIco.png b/icons/ortoIco.png new file mode 100755 index 0000000000000000000000000000000000000000..4e88815aa97ba76f3f9412b888f41230d9e9d9a1 GIT binary patch literal 19065 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 literal 0 HcmV?d00001 diff --git a/icons/pb_quit.png b/icons/pb_quit.png new file mode 100755 index 0000000000000000000000000000000000000000..9b4db0ce7ca17646e007a67e9cad943495f88e3d GIT binary patch literal 16139 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 Fe*iR_9VY+) literal 0 HcmV?d00001 diff --git a/icons/volrenIco.png b/icons/volrenIco.png new file mode 100755 index 0000000000000000000000000000000000000000..9aa19c629d24bb82687725f10e8fd88ef33c5522 GIT binary patch literal 83220 zcmeFY(|2Xf7d9HZ!%ot%ZKq>)Y$rQ*I<{@6W7{3uwy|T|Hox@y`_9ez1J2dCSYwZx zqt;$EYfU^gpSi;1WyQY1VZ(udfP9k>7f}QO0qyv@N@2jhBuVf-SzkY(4vJzzAeEE2 z$6o^oQ$ZO)5RjS}_&0sXuQ9BxxP}7=2x8yA3v|e)#0UiBvrs}rP}x=IOdHbm`{4q) zKSh7MQ?lLa@=T&@?XuCXKTvCBqqVJ#8@Q_Fa#DCP-%&^>A1(~xa{ieu;Z7Aatz`xV zdf@i34Lfo%ewKRaaX9R;!7{9|l+)!1e}pkjm!q|Hsiil)=~Mn8yAIY0`A-T0f7D#s zw)(%p65H3P<@duZLF4}f({@TjGXI-;mImp#w#MdhET{Q@?`$sz+_@BUWZQf3)$@kx&%>)#W!(~F}C0_M^0xs*5=29(zuYsR-oo{q{n+e0aMEQS$ zubED3^Iz)}XzH%i5Ez=!u1o$X0RAWVn)!9-e_H4*(&p?QR4z;T%>GXxFeLMJ=YKj@ z|HhcMb67kp7kV&2Au8=xX(y(WN}#5juVt+2>I@7j*7x zC6ask^v1YDxE~a2k?fqp`I%QXxgWD%MyTiUZ$UYgVP*j|^O~YSFf*>aC>^)6Y(mtJ zvgjE*B_GQpj3eNeFW{FnmvaC4XJluUUv4vRSBFF^F!n7=9{#C#$neCiU?*(hQ;xqd zJR(yF<5salY6|M?;^IB~*TUws+}GaJA<5@wU Vkl-50(kltTYKreTs>tiOH8S^ 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

2V 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 aFMr>CEt< + + icons/pb_load.png + icons/pb_quit.png + icons/ortoIco.png + icons/volrenIco.png + + \ No newline at end of file diff --git a/itkQtAdaptor.h b/itkQtAdaptor.h new file mode 100755 index 0000000..551ae0e --- /dev/null +++ b/itkQtAdaptor.h @@ -0,0 +1,162 @@ +/*========================================================================= + + Program: Insight Segmentation & Registration Toolkit + Module: itkQtAdaptor.h + Language: C++ + Date: $Date$ + Version: $Revision$ + + Copyright (c) 2002 Insight Consortium. All rights reserved. + See ITKCopyright.txt or http://www.itk.org/HTML/Copyright.htm for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notices for more information. + +=========================================================================*/ + + + +#include +#include "itkObject.h" +#include "itkObjectFactory.h" +#include "itkCommand.h" + + +namespace itk { + +/** Helper class that interface with Qt Signals and Slots */ +class QtTranslator : public QObject +{ + + Q_OBJECT + +public: + QtTranslator() {} + virtual ~QtTranslator() {} + +signals: + void Signal(); + +public slots: + virtual void Slot() {}; + virtual void Slot(int) {}; + virtual void Slot(double) {}; + +}; + + + +/** Helper class that interface Methods with Qt Slots */ +template +class QtSlotAdaptor : public QtTranslator +{ + typedef void (T::*TMemberFunctionVoidPointer)(); + typedef void (T::*TMemberFunctionIntPointer)(int); + typedef void (T::*TMemberFunctionDoublePointer)(double); + +public: + QtSlotAdaptor():m_MemberFunctionVoid(0), + m_MemberFunctionInt(0), + m_MemberFunctionDouble(0) {} + + virtual ~QtSlotAdaptor() {} + + /** Specify the callback function. */ + void SetCallbackFunction(T* object, + TMemberFunctionVoidPointer memberFunction) + { + m_This = object; + m_MemberFunctionVoid = memberFunction; + } + + /** Specify the callback function. */ + void SetCallbackFunction(T* object, + TMemberFunctionIntPointer memberFunction) + { + m_This = object; + m_MemberFunctionInt = memberFunction; + } + + /** Specify the callback function. */ + void SetCallbackFunction(T* object, + TMemberFunctionDoublePointer memberFunction) + { + m_This = object; + m_MemberFunctionDouble = memberFunction; + } + + /** Slot to be connected to Qt Signals. */ + void Slot() + { + if( m_MemberFunctionVoid ) + { + ((*m_This).*(m_MemberFunctionVoid))(); + } + } + + /** Slot to be connected to Qt Signals. */ + void Slot(int value) + { + if( m_MemberFunctionInt ) + { + ((*m_This).*(m_MemberFunctionInt))(value); + } + } + + /** Slot to be connected to Qt Signals. */ + void Slot(double value) + { + if( m_MemberFunctionDouble ) + { + ((*m_This).*(m_MemberFunctionDouble))(value); + } + } + + + +protected: + T* m_This; + TMemberFunctionVoidPointer m_MemberFunctionVoid; + TMemberFunctionIntPointer m_MemberFunctionInt; + TMemberFunctionDoublePointer m_MemberFunctionDouble; + + +}; + + + + + +/** Helper class that interface Observers with Qt Signals */ +class QtSignalAdaptor : public QtTranslator +{ + typedef SimpleMemberCommand CommandType; + +public: + QtSignalAdaptor() + { + m_Command = CommandType::New(); + m_Command->SetCallbackFunction( this, & QtSignalAdaptor::EmitSignal ); + } + + virtual ~QtSignalAdaptor() {} + + CommandType * GetCommand() + { + return m_Command; + } + + void EmitSignal() + { + emit Signal(); + } + +private: + CommandType::Pointer m_Command; +}; + + + + +} // end namespace diff --git a/main.cpp b/main.cpp new file mode 100755 index 0000000..dd4fcfb --- /dev/null +++ b/main.cpp @@ -0,0 +1,83 @@ + + +#include "mainw.h" +#include + #include "itkTextOutput.h" + +#include + + + +int main(int argc, char **argv) { + + std::cout << "Hello, world!" << std::endl; + + vtkObject::SetGlobalWarningDisplay(false); + itk::OutputWindow::SetInstance(itk::TextOutput::New()); + + QApplication app(argc,argv); + + if(!QFile("config.ini").exists()){ + QMessageBox msgBox; + msgBox.setIcon(QMessageBox::Icon::Critical); + msgBox.setText("gLocalize config file missing... Exiting."); + msgBox.setInformativeText("Please add config.ini file in the gLocalize path."); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setDefaultButton(QMessageBox::Ok); + int ret = msgBox.exec(); + cout<<"prequit" <value("Path/inDir").toString()).exists() || !QDir(config->value("Path/outDir").toString()).exists()){ + QString userMsg; + userMsg.clear(); + userMsg.append("Please check config.ini file parameters:\n"); + ( QDir(config->value("Path/inDir").toString()).exists() == true? "" : userMsg.append("- Patient directory : [Path]/inDir\n") ); + ( QDir(config->value("Path/outDir").toString()).exists() == true? "" : userMsg.append("- Output directory : [Path]/outDir\n") ); + + QMessageBox msgBox; + msgBox.setIcon(QMessageBox::Icon::Critical); + msgBox.setText("gLocalize configuration error... Exiting."); + msgBox.setInformativeText(userMsg); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setDefaultButton(QMessageBox::Ok); + + QSpacerItem* horizonatlSpacer= new QSpacerItem(300,0,QSizePolicy::Minimum,QSizePolicy::Expanding); + QGridLayout* layout = (QGridLayout*) msgBox.layout(); + layout->addItem(horizonatlSpacer, layout->rowCount(), 0 ,1 ,layout->columnCount()); + + int ret = msgBox.exec(); + cout<<"prequit" <show(); + int res= app.exec(); + + + + + + return 0; +} diff --git a/mainw.cpp b/mainw.cpp new file mode 100755 index 0000000..6dbff79 --- /dev/null +++ b/mainw.cpp @@ -0,0 +1,1043 @@ +#include "mainw.h" +#include + +#include + +#include + + void Ui_MainWindow::setupUi(QMainWindow *MainWindow) + { + if (MainWindow->objectName().isEmpty()) + MainWindow->setObjectName(QString::fromUtf8("MainWindow")); + MainWindow->resize(872, 737); + actionLoad = new QAction(MainWindow); + actionLoad->setObjectName(QString::fromUtf8("actionLoad")); + actionQuit = new QAction(MainWindow); + actionQuit->setObjectName(QString::fromUtf8("actionQuit")); + actionVolRen = new QAction(MainWindow); + actionVolRen->setObjectName(QString::fromUtf8("actionVolRen")); + actionVolRen->setCheckable(true); + actionOrthoslice = new QAction(MainWindow); + actionOrthoslice->setObjectName(QString::fromUtf8("actionOrthoslice")); + actionOrthoslice->setCheckable(true); + viewGroup = new QActionGroup(MainWindow); + viewGroup->addAction(actionVolRen); + viewGroup->addAction(actionOrthoslice); + actionOrthoslice->setChecked(true); + centralwidget = new QWidget(MainWindow); + centralwidget->setObjectName(QString::fromUtf8("centralwidget")); + horizontalLayout_8 = new QHBoxLayout(centralwidget); + horizontalLayout_8->setObjectName(QString::fromUtf8("horizontalLayout_8")); + widget = new QWidget(centralwidget); + widget->setObjectName(QString::fromUtf8("widget")); + widget->setMaximumSize(QSize(300, 16777215)); + widget->setMinimumSize(QSize(300, 0)); + verticalLayout_5 = new QVBoxLayout(widget); + verticalLayout_5->setObjectName(QString::fromUtf8("verticalLayout_5")); + frame_2 = new QFrame(widget); + frame_2->setObjectName(QString::fromUtf8("frame_2")); + frame_2->setFrameShape(QFrame::StyledPanel); + frame_2->setFrameShadow(QFrame::Raised); + verticalLayout_2 = new QVBoxLayout(frame_2); + verticalLayout_2->setObjectName(QString::fromUtf8("verticalLayout_2")); + formLayout_2 = new QFormLayout(); + formLayout_2->setObjectName(QString::fromUtf8("formLayout_2")); + label_6 = new QLabel(frame_2); + label_6->setObjectName(QString::fromUtf8("label_6")); + + formLayout_2->setWidget(0, QFormLayout::LabelRole, label_6); + + l_patientID = new QLabel(frame_2); + l_patientID->setObjectName(QString::fromUtf8("l_patientID")); + l_patientID->setMinimumSize(QSize(91, 0)); + + formLayout_2->setWidget(0, QFormLayout::FieldRole, l_patientID); + + label_8 = new QLabel(frame_2); + label_8->setObjectName(QString::fromUtf8("label_8")); + + formLayout_2->setWidget(1, QFormLayout::LabelRole, label_8); + + l_patientID_2 = new QLabel(frame_2); + l_patientID_2->setObjectName(QString::fromUtf8("l_patientID_2")); + l_patientID_2->setMinimumSize(QSize(91, 0)); + + formLayout_2->setWidget(1, QFormLayout::FieldRole, l_patientID_2); + + + label_12 = new QLabel(frame_2); + label_12->setObjectName(QString::fromUtf8("label_12")); + + formLayout_2->setWidget(5, QFormLayout::LabelRole, label_12); + + l_patientID_6 = new QLabel(frame_2); + l_patientID_6->setObjectName(QString::fromUtf8("l_patientID_6")); + l_patientID_6->setMinimumSize(QSize(91, 0)); + + formLayout_2->setWidget(5, QFormLayout::FieldRole, l_patientID_6); + + label_isoV = new QLabel(frame_2); + label_isoV->setObjectName(QString::fromUtf8("label_isoV")); + label_isoV->setText("Virtual Iso:"); + label_isoV->setMinimumSize(QSize(91, 0)); + formLayout_2->setWidget(6, QFormLayout::LabelRole, label_isoV); + + l_isoV = new QLabel(frame_2); + l_isoV->setObjectName(QString::fromUtf8("l_isoV")); + l_isoV->setText("N.a."); + l_isoV->setMinimumSize(QSize(91, 0)); + formLayout_2->setWidget(6, QFormLayout::FieldRole, l_isoV); + +#ifdef MULTIPLE_REF + a_DCMref=new QRadioButton(frame_2); + a_DCMref->setCheckable(true); + a_DCMref->setText("DCM"); + a_RTref=new QRadioButton(frame_2); + a_RTref->setCheckable(true); + a_RTref->setText("RT"); + a_OTSref=new QRadioButton(frame_2); + a_OTSref->setText("OTS"); + a_OTSref->setCheckable(true); + a_DCMref->setChecked(true); + + refGroup = new QButtonGroup(frame_2); + refGroup->addButton (a_DCMref); + refGroup->addButton(a_RTref); + refGroup->addButton(a_OTSref); + + QHBoxLayout* reflay=new QHBoxLayout; + reflay->addWidget(a_DCMref); + reflay->addWidget(a_RTref); + reflay->addWidget(a_OTSref); + + a_RTref->setDisabled(true); + + formLayout_2->setItem(6, QFormLayout::FieldRole, reflay); +#endif + verticalLayout_2->addLayout(formLayout_2); + + + verticalLayout_5->addWidget(frame_2); + + frame_5 = new QFrame(widget); + frame_5->setObjectName(QString::fromUtf8("frame_5")); + frame_5->setFrameShape(QFrame::StyledPanel); + frame_5->setFrameShadow(QFrame::Raised); + verticalLayout_4 = new QVBoxLayout(frame_5); + verticalLayout_4->setObjectName(QString::fromUtf8("verticalLayout_4")); + horizontalLayout_7 = new QHBoxLayout(); + horizontalLayout_7->setObjectName(QString::fromUtf8("horizontalLayout_7")); + label_7 = new QLabel(frame_5); + label_7->setObjectName(QString::fromUtf8("label_7")); + + horizontalLayout_7->addWidget(label_7); + + pushButton_5 = new QPushButton(frame_5); + pushButton_5->setObjectName(QString::fromUtf8("pushButton_5")); + pushButton_5->setEnabled(true); + + horizontalLayout_7->addWidget(pushButton_5); + + + verticalLayout_4->addLayout(horizontalLayout_7); + + + QHBoxLayout* manualMask_lay = new QHBoxLayout(); + manualMask_lay->setObjectName(QString::fromUtf8("manualMask_lay")); + QLabel* l_manualMask = new QLabel(frame_5); + l_manualMask->setObjectName(QString::fromUtf8("l_manualMask")); + l_manualMask->setText("Manual volume clip"); + + manualMask_lay->addWidget(l_manualMask); + + pb_manualMask = new QPushButton(frame_5); + pb_manualMask->setObjectName(QString::fromUtf8("pb_manualMask")); + pb_manualMask->setEnabled(true); + pb_manualMask->setText("View"); + pb_manualMask->setCheckable(true); + + manualMask_lay->addWidget(pb_manualMask); + verticalLayout_4->addLayout(manualMask_lay); + + + verticalLayout_5->addWidget(frame_5); + + frame_3 = new QFrame(widget); + frame_3->setObjectName(QString::fromUtf8("frame_3")); + frame_3->setEnabled(true); + frame_3->setFrameShape(QFrame::StyledPanel); + frame_3->setFrameShadow(QFrame::Raised); + verticalLayout = new QVBoxLayout(frame_3); + verticalLayout->setObjectName(QString::fromUtf8("verticalLayout")); + horizontalLayout = new QHBoxLayout(); + horizontalLayout->setObjectName(QString::fromUtf8("horizontalLayout")); + label = new QLabel(frame_3); + label->setObjectName(QString::fromUtf8("label")); + + horizontalLayout->addWidget(label); + + horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + + horizontalLayout->addItem(horizontalSpacer); + + spinBox = new QSpinBox(frame_3); + spinBox->setObjectName(QString::fromUtf8("spinBox")); + spinBox->setMinimumSize(QSize(71, 0)); + spinBox->setMaximumSize(QSize(71, 16777215)); + spinBox->setMinimum(-1024); + spinBox->setMaximum(3000); + spinBox->setValue(1800); + + horizontalLayout->addWidget(spinBox); + + + verticalLayout->addLayout(horizontalLayout); + + horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->setObjectName(QString::fromUtf8("horizontalLayout_2")); + checkBox_2 = new QCheckBox(frame_3); + checkBox_2->setObjectName(QString::fromUtf8("checkBox_2")); + checkBox_2->setChecked(true); + + horizontalLayout_2->addWidget(checkBox_2); + + label_2 = new QLabel(frame_3); + label_2->setObjectName(QString::fromUtf8("label_2")); + + horizontalLayout_2->addWidget(label_2); + + horizontalSpacer_2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + + horizontalLayout_2->addItem(horizontalSpacer_2); + + doubleSpinBox_2 = new QDoubleSpinBox(frame_3); + doubleSpinBox_2->setObjectName(QString::fromUtf8("doubleSpinBox_2")); + doubleSpinBox_2->setMinimumSize(QSize(71, 0)); + doubleSpinBox_2->setMaximumSize(QSize(71, 16777215)); + + horizontalLayout_2->addWidget(doubleSpinBox_2); + + doubleSpinBox_2bis = new QDoubleSpinBox(frame_3); + doubleSpinBox_2bis->setObjectName(QString::fromUtf8("doubleSpinBox_2bis")); + doubleSpinBox_2bis->setMinimumSize(QSize(71, 0)); + doubleSpinBox_2bis->setMaximumSize(QSize(71, 16777215)); + + horizontalLayout_2->addWidget(doubleSpinBox_2bis); + + + + verticalLayout->addLayout(horizontalLayout_2); + + horizontalLayout_3 = new QHBoxLayout(); + horizontalLayout_3->setObjectName(QString::fromUtf8("horizontalLayout_3")); + checkBox_3 = new QCheckBox(frame_3); + checkBox_3->setObjectName(QString::fromUtf8("checkBox_3")); + checkBox_3->setChecked(true); + + horizontalLayout_3->addWidget(checkBox_3); + + label_3 = new QLabel(frame_3); + label_3->setObjectName(QString::fromUtf8("label_3")); + + horizontalLayout_3->addWidget(label_3); + + horizontalSpacer_3 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + + horizontalLayout_3->addItem(horizontalSpacer_3); + + doubleSpinBox_3 = new QDoubleSpinBox(frame_3); + doubleSpinBox_3->setObjectName(QString::fromUtf8("doubleSpinBox_3")); + doubleSpinBox_3->setMinimumSize(QSize(71, 0)); + doubleSpinBox_3->setMaximumSize(QSize(71, 16777215)); + doubleSpinBox_3->setMaximum(999999); + doubleSpinBox_3->setValue(22); + + horizontalLayout_3->addWidget(doubleSpinBox_3); + + + verticalLayout->addLayout(horizontalLayout_3); + + horizontalLayout_5 = new QHBoxLayout(); + horizontalLayout_5->setObjectName(QString::fromUtf8("horizontalLayout_5")); + checkBox_5 = new QCheckBox(frame_3); + checkBox_5->setObjectName(QString::fromUtf8("checkBox_5")); + checkBox_5->setChecked(true); + + horizontalLayout_5->addWidget(checkBox_5); + + label_5 = new QLabel(frame_3); + label_5->setObjectName(QString::fromUtf8("label_5")); + + horizontalLayout_5->addWidget(label_5); + + horizontalSpacer_5 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + + horizontalLayout_5->addItem(horizontalSpacer_5); + + doubleSpinBox_5 = new QDoubleSpinBox(frame_3); + doubleSpinBox_5->setObjectName(QString::fromUtf8("doubleSpinBox_5")); + doubleSpinBox_5->setMinimumSize(QSize(71, 0)); + doubleSpinBox_5->setMaximumSize(QSize(71, 16777215)); + + horizontalLayout_5->addWidget(doubleSpinBox_5); + + + verticalLayout->addLayout(horizontalLayout_5); + + horizontalLayout_6 = new QHBoxLayout(); + horizontalLayout_6->setObjectName(QString::fromUtf8("horizontalLayout_6")); + pushButton_3 = new QPushButton(frame_3); + pushButton_3->setObjectName(QString::fromUtf8("pushButton_3")); + pushButton_3->setEnabled(true); + + horizontalLayout_6->addWidget(pushButton_3); + + pushButton_4 = new QPushButton(frame_3); + pushButton_4->setObjectName(QString::fromUtf8("pushButton_4")); + pushButton_4->setEnabled(false); + + horizontalLayout_6->addWidget(pushButton_4); + + + verticalLayout->addLayout(horizontalLayout_6); + + + verticalLayout_5->addWidget(frame_3); + + frame_4 = new QFrame(widget); + frame_4->setObjectName(QString::fromUtf8("frame_4")); + frame_4->setEnabled(true); + frame_4->setMaximumSize(QSize(16777215, 171)); + frame_4->setFrameShape(QFrame::StyledPanel); + frame_4->setFrameShadow(QFrame::Raised); + verticalLayout_3 = new QVBoxLayout(frame_4); + verticalLayout_3->setObjectName(QString::fromUtf8("verticalLayout_3")); + treeView = new QTreeView(frame_4); + //new QTreeWidgetItem(treeWidget); + treeView->setObjectName(QString::fromUtf8("treeView")); + treeView->setEnabled(true); + treeView->setFrameShape(QFrame::StyledPanel); + // treeView->setAlternatingRowColors(true); + treeView->setUniformRowHeights(true); + treeView->setSortingEnabled(false); + treeView->setAnimated(true); + treeView->setAllColumnsShowFocus(false); + //treeView->setColumnCount(5); + treeView->header()->setVisible(true); + treeView->header()->setCascadingSectionResizes(false); + treeView->header()->setHighlightSections(true); + treeView->header()->setStretchLastSection(true); + + verticalLayout_3->addWidget(treeView); + + formLayout = new QHBoxLayout(); + formLayout->setObjectName(QString::fromUtf8("formLayout")); + pushButton = new QPushButton(frame_4); + pushButton->setObjectName(QString::fromUtf8("pushButton")); + pushButton->setEnabled(false); + + formLayout->addWidget(pushButton); + + pushButton_2 = new QPushButton(frame_4); + pushButton_2->setObjectName(QString::fromUtf8("pushButton_2")); + pushButton_2->setEnabled(false); + + formLayout->addWidget(pushButton_2); + + pb_save= new QPushButton(frame_4); + pb_save->setObjectName(QString::fromUtf8("pb_save")); + pb_save->setText("Save .tac"); + formLayout->addWidget(pb_save); + pb_save->setEnabled(false); + + verticalLayout_3->addLayout(formLayout); + + verticalLayout_5->addWidget(frame_4); + + + horizontalLayout_8->addWidget(widget); + + frame = new QFrame(centralwidget); + frame->setObjectName(QString::fromUtf8("frame")); + frame->setMinimumSize(QSize(561, 0)); + frame->setFrameShape(QFrame::StyledPanel); + frame->setFrameShadow(QFrame::Raised); + + QHBoxLayout* renLay= new QHBoxLayout(frame); + qvtk= new QVTKWidget(frame); + renLay->addWidget(qvtk); + horizontalLayout_8->addWidget(frame); + + MainWindow->setCentralWidget(centralwidget); + statusbar = new QStatusBar(MainWindow); + statusbar->setObjectName(QString::fromUtf8("statusbar")); + MainWindow->setStatusBar(statusbar); + toolBar = new QToolBar(MainWindow); + toolBar->setObjectName(QString::fromUtf8("toolBar")); + MainWindow->addToolBar(Qt::TopToolBarArea, toolBar); + + toolBar->addAction(actionLoad); + toolBar->addSeparator(); + toolBar->addAction(actionVolRen); + toolBar->addAction(actionOrthoslice); + toolBar->addSeparator(); + QWidget *widget = new QWidget; + QHBoxLayout *spacerLayout = new QHBoxLayout; + QSpacerItem *spacer = + new QSpacerItem(1,1,QSizePolicy::Expanding,QSizePolicy::Minimum); + spacerLayout->addSpacerItem(spacer); + widget->setLayout(spacerLayout); + toolBar->addWidget(widget); + toolBar->addAction(actionQuit); + + progBar = new QProgressBar(MainWindow); + progBar->setMinimum(0); + progBar->setMaximum(100); + progBar->setMaximumHeight(15); + statusbar->addPermanentWidget(progBar); + + } // setupUi + + + +void MainWindow::onSkullMaskingUpdt(QString msg, double val){ + Ui->progBar->setValue(val*100); + QMetaObject::invokeMethod(Ui->statusbar, "showMessage", Qt::QueuedConnection, + Q_ARG(QString, msg ), + Q_ARG(int, 0)); +} + + +void MainWindow::call_quit(){ + + loadPatient_thread->quit(); + localize_thread->quit(); + skull_thread->quit(); + Sleep(100); + cout<< "Bye bye" <l_isoV->clear(); + (IsoV == true? Ui->l_isoV->setText("Yes") : Ui->l_isoV->setText("No") ); +} + +MainWindow::MainWindow(QString p_loadPath){ + + + + + Ui = new Ui_MainWindow; + Ui->setupUi(this); + this->retranslateUi(Ui,this); + this->connectUi(Ui); + Ui->actionVolRen->setEnabled(false); + Ui->actionOrthoslice->setEnabled(false); + Ui->pushButton_3->setEnabled(false); + Ui->pb_manualMask->setEnabled(false); + Ui->pushButton_5->setEnabled(false); + + Visualizer= new gRen(Ui->qvtk); + + loadPatient= new gLoadPatient; + loadPatient_thread = new QThread; + loadPatient->moveToThread(loadPatient_thread); + loadPatient_thread->start(); + + qRegisterMetaType< gPatientRTGeneralInfos* >("gPatientRTGeneralInfos *"); + qRegisterMetaType< std::vector >("std::vector"); + qRegisterMetaType< std::string >("std::string"); + qRegisterMetaType< vtkImageData* >("vtkImageData*"); + qRegisterMetaType < QList > ("QList"); + qRegisterMetaType < QList > ("QList"); + + connect(loadPatient, SIGNAL(loadEnd(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*))); + connect(loadPatient, SIGNAL(loadedVolBBox(double*, double*, int*)),this,SLOT(onCTVolumeAvailable(double*,double*,int*))); + connect(loadPatient, SIGNAL(virtualIsoTested(bool)),this,SLOT(onVirtualIsoTested(bool))); + + + localize_thread = new QThread; + localize_marker = new gLocalize; + localize_marker->moveToThread(localize_thread); + localize_thread->start(); + connect(localize_marker,SIGNAL(localizationEnd(QList )),this,SLOT(onLocalizationEnd(QList ))); + connect(localize_marker,SIGNAL(localizationProgress(int)), this->Ui->progBar ,SLOT(setValue(int))); + + skullRemoval= new gSkullRemoval; + skull_thread= new QThread; + skullRemoval->moveToThread(skull_thread); + skull_thread->start(); + connect(skullRemoval, SIGNAL(skull_mask_end(vtkImageData *)),Visualizer,SLOT(onAutoSkullMaskEnd(vtkImageData* ))); + connect(skullRemoval, SIGNAL(skull_mask_end(vtkImageData *)),this,SLOT(onAutoSkullMaskEnd(vtkImageData* ))); + + connect(skullRemoval, SIGNAL(errMsg(QString)),this,SLOT(onAutoMaskError(QString ))); + connect(skullRemoval, SIGNAL(skull_mask_upd(QString , double )), this, SLOT(onSkullMaskingUpdt(QString,double))); + + + treeModel = new QStandardItemModel; + connect(treeModel,SIGNAL(itemChanged(QStandardItem*)),this,SLOT(onItemChanged(QStandardItem*))); + connect(Ui->viewGroup, SIGNAL(triggered(QAction*)), this, SLOT(onVisualizationTrigger(QAction* ))); + connect(Ui->pb_manualMask, SIGNAL(toggled ( bool )), this, SLOT(onManualMaskTrigger(bool))); +#ifdef MULTIPLE_REF + connect(Ui->refGroup, SIGNAL(buttonClicked(QAbstractButton*)), this, SLOT(onRefTrigger(QAbstractButton*))); +#endif + loadDialog = new gLoadPatDialog; + connect(Visualizer, SIGNAL(PatientLoaded()),this,SLOT(onPatientlLoaded())); + + + QSettings *config = new QSettings ("config.ini",QSettings::IniFormat); + pathIn = config->value("Path/inDir").toString(); + pathOut = config->value("Path/outDir").toString(); + + /*default values*/ + Ui->spinBox->setValue(config->value("fiducialsLocalize/markerIntensity").toInt()); + Ui->doubleSpinBox_2->setValue(config->value("fiducialsLocalize/boxDiag_down").toDouble()); + Ui->doubleSpinBox_2bis->setValue(config->value("fiducialsLocalize/boxDiag_up").toDouble()); + Ui->doubleSpinBox_3->setValue(config->value("fiducialsLocalize/hausdDist").toDouble()); + Ui->doubleSpinBox_5->setValue(config->value("fiducialsLocalize/boxSidesDiff").toDouble()); + regGrownParameters[0]=config->value("skullMask/regGrow_up").toDouble(); + regGrownParameters[1]=config->value("skullMask/regGrow_down").toDouble(); + + + + delete config; + + + connect(localize_marker, SIGNAL(localizationAborted()), + this,SLOT(onLocalizationAborted())); + connect(loadPatient,SIGNAL(referenceChange(double, double, double )), + Visualizer,SLOT(onReferenceChange(double, double , double))); + connect(loadPatient,SIGNAL(referenceChange(double, double, double )), + this,SLOT(updateMarkerPos(double, double , double))); + + /*load passed workdir if any*/ + loadPath.clear(); + /*first check for argv [1]*/ + if(p_loadPath.isEmpty()) { + /*then check for ini inDir*/ + if(!pathIn.isEmpty()) + loadPath=pathIn; + } else + loadPath=p_loadPath; + + if(loadPath.isEmpty()){ + cout<< "loadpath is emty" <show(); + cout<< "loadPath: "< pippoReader= vtkSmartPointer::New(); + //pippoReader->SetDirectoryName("D:\\testIn"); + ////pippoReader->SetFileLowerLeft(1); + //pippoReader->Update(); + //pippoReader->Print(std::cout); + + //Visualizer->loadVolume(pippoReader->GetOutput()); + //cout<< "height" <GetHeight()<GetWidth()<GetOutput()->GetBounds(); + //cout<< "VolBounds: "<statusbar, "showMessage", Qt::QueuedConnection, + Q_ARG(QString, QString("Masking Done") ), + Q_ARG(int, 3000)); + + Ui->progBar->reset(); + QApplication::restoreOverrideCursor(); + + Ui->pb_manualMask->setEnabled(true); + Ui->pushButton_5->setEnabled(true); + Ui->frame_3->setEnabled(true); + Ui->frame_4->setEnabled(true); + +} + +void MainWindow::onPatientlLoaded(){ + QApplication::restoreOverrideCursor(); + + Ui->pushButton_5->setEnabled(true); + Ui->pb_manualMask->setEnabled(true); + Ui->actionVolRen->setEnabled(true); + Ui->actionOrthoslice->setEnabled(true); + Ui->pushButton_3->setEnabled(true); + + loadDialog->close(); + Ui->actionLoad->setEnabled(false); + + Ui->pb_manualMask->setEnabled(true); + Ui->pushButton_5->setEnabled(true); + Ui->pushButton_4->setEnabled(false); + Ui->pushButton_3->setEnabled(true); +} + +void MainWindow::onParsedEmptyFolder(){ + QApplication::restoreOverrideCursor(); + loadDialog->close(); + QMessageBox msgBox; + msgBox.setIcon(QMessageBox::Icon::Critical); + msgBox.setText("Load failed..."); + msgBox.setInformativeText("Parsed directory is empty or does not contain CT."); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setDefaultButton(QMessageBox::Ok); + int ret = msgBox.exec(); +} + +void MainWindow::setStatusMsg(QString msg, int timeout){ + QMetaObject::invokeMethod(Ui->statusbar, "showMessage", Qt::QueuedConnection, + Q_ARG(QString, msg ), + Q_ARG(int, timeout)); +} + +void MainWindow::retranslateUi(Ui_MainWindow* Ui, QMainWindow *MainWindow) { + MainWindow->setWindowTitle(QApplication::translate("MainWindow", "gLocalize - Fondazione CNAO - Politecnico di Milano - 2014", 0, QApplication::UnicodeUTF8)); + Ui->actionLoad->setText(QApplication::translate("MainWindow", "Load", 0, QApplication::UnicodeUTF8)); + Ui->actionLoad->setIcon(QIcon(":/icons/pb_load.png")); +#ifndef QT_NO_TOOLTIP + Ui->actionLoad->setToolTip(QApplication::translate("MainWindow", "Load patient data", 0, QApplication::UnicodeUTF8)); +#endif // QT_NO_TOOLTIP + Ui->actionQuit->setText(QApplication::translate("MainWindow", "Quit", 0, QApplication::UnicodeUTF8)); + Ui->actionQuit->setIcon(QIcon(":/icons/pb_quit.png")); +#ifndef QT_NO_TOOLTIP + Ui->actionQuit->setToolTip(QApplication::translate("MainWindow", "Quit application", 0, QApplication::UnicodeUTF8)); +#endif // QT_NO_TOOLTIP + Ui->actionVolRen->setText(QApplication::translate("MainWindow", "VolRen", 0, QApplication::UnicodeUTF8)); +#ifndef QT_NO_TOOLTIP + Ui->actionVolRen->setToolTip(QApplication::translate("MainWindow", "Volume rendering", 0, QApplication::UnicodeUTF8)); +#endif // QT_NO_TOOLTIP + Ui->actionVolRen->setIcon(QIcon(":/icons/volrenIco.png")); + Ui->actionOrthoslice->setText(QApplication::translate("MainWindow", "Orthoslice", 0, QApplication::UnicodeUTF8)); + Ui->actionOrthoslice->setIcon(QIcon(":/icons/ortoIco.png")); + Ui->label_6->setText(QApplication::translate("MainWindow", "Patient ID:", 0, QApplication::UnicodeUTF8)); + Ui->l_patientID->setText(QString()); + Ui->label_8->setText(QApplication::translate("MainWindow", "Patient position:", 0, QApplication::UnicodeUTF8)); + Ui->l_patientID_2->setText(QString()); + Ui->label_12->setText(QApplication::translate("MainWindow", "RT Isocenter:", 0, QApplication::UnicodeUTF8)); + Ui->l_patientID_6->setText(QString()); + Ui->label_7->setText(QApplication::translate("MainWindow", "Automatic skull mask", 0, QApplication::UnicodeUTF8)); + Ui->pushButton_5->setText(QApplication::translate("MainWindow", "Execute", 0, QApplication::UnicodeUTF8)); + Ui->label->setText(QApplication::translate("MainWindow", "Marker intensity", 0, QApplication::UnicodeUTF8)); + Ui->checkBox_2->setText(QString()); + Ui->label_2->setText(QApplication::translate("MainWindow", "Box diagonal", 0, QApplication::UnicodeUTF8)); + Ui->checkBox_3->setText(QString()); + Ui->label_3->setText(QApplication::translate("MainWindow", "Hausdorff distance", 0, QApplication::UnicodeUTF8)); + Ui->checkBox_5->setText(QString()); + Ui->label_5->setText(QApplication::translate("MainWindow", "Box sides distance", 0, QApplication::UnicodeUTF8)); + Ui->pushButton_3->setText(QApplication::translate("MainWindow", "Localize", 0, QApplication::UnicodeUTF8)); + Ui->pushButton_4->setText(QApplication::translate("MainWindow", "Cancel", 0, QApplication::UnicodeUTF8)); + /* QTreeWidgetItem *___qtreewidgetitem = Ui->treeWidget->headerItem(); + ___qtreewidgetitem->setText(4, QApplication::translate("MainWindow", "Z", 0, QApplication::UnicodeUTF8)); + ___qtreewidgetitem->setText(3, QApplication::translate("MainWindow", "Y", 0, QApplication::UnicodeUTF8)); + ___qtreewidgetitem->setText(2, QApplication::translate("MainWindow", "X", 0, QApplication::UnicodeUTF8)); + ___qtreewidgetitem->setText(0, QApplication::translate("MainWindow", "ID", 0, QApplication::UnicodeUTF8));*/ + + /* const bool __sortingEnabled = Ui->treeWidget->isSortingEnabled(); + Ui->treeWidget->setSortingEnabled(false); + QTreeWidgetItem *___qtreewidgetitem1 = Ui->treeWidget->topLevelItem(0); + ___qtreewidgetitem1->setText(4, QApplication::translate("MainWindow", "0", 0, QApplication::UnicodeUTF8)); + ___qtreewidgetitem1->setText(3, QApplication::translate("MainWindow", "0", 0, QApplication::UnicodeUTF8)); + ___qtreewidgetitem1->setText(2, QApplication::translate("MainWindow", "0", 0, QApplication::UnicodeUTF8)); + ___qtreewidgetitem1->setText(0, QApplication::translate("MainWindow", "1", 0, QApplication::UnicodeUTF8)); + Ui->treeWidget->setSortingEnabled(__sortingEnabled); +*/ + Ui->pushButton->setText(QApplication::translate("MainWindow", "Select all", 0, QApplication::UnicodeUTF8)); + Ui->pushButton_2->setText(QApplication::translate("MainWindow", "Uncheck all", 0, QApplication::UnicodeUTF8)); + Ui->toolBar->setWindowTitle(QApplication::translate("MainWindow", "toolBar", 0, QApplication::UnicodeUTF8)); + + } // retranslateUi + + void MainWindow::connectUi(Ui_MainWindow* Ui){ + QObject::connect(Ui->pushButton_3, SIGNAL(released()), this, SLOT(call_localize_start())); + QObject::connect(Ui->pushButton_4, SIGNAL(released()), this, SLOT(call_localize_cancel())); + QObject::connect(Ui->pushButton, SIGNAL(released()), this, SLOT(call_visualize_all())); + QObject::connect(Ui->pushButton_2, SIGNAL(released()), this, SLOT(call_hide_all())); + QObject::connect(Ui->toolBar, SIGNAL(actionTriggered(QAction*)), this, SLOT(call_ToolbarAction(QAction*))); + QObject::connect(Ui->pushButton_5, SIGNAL(released()), this, SLOT(call_skullMask())); + QObject::connect(Ui->pb_save, SIGNAL(released()), this, SLOT(call_saveDotCtFile())); + + + QMetaObject::connectSlotsByName(this); + } + + + +void MainWindow::onLocalizationAborted(){ + Ui->pushButton_4->setEnabled(false); + Ui->pushButton_3->setEnabled(true); + Ui->progBar->reset(); + QMetaObject::invokeMethod(Ui->statusbar, "showMessage", Qt::QueuedConnection, + Q_ARG(QString, "Localization aborted..." ), + Q_ARG(int, 3000)); + +} + +void MainWindow::call_localize_start(){ + + //bool selected[3]; + QList selected; + selected.clear(); + selected.append(Ui->checkBox_2->isChecked()); + selected.append(Ui->checkBox_3->isChecked()); + selected.append(Ui->checkBox_5->isChecked()); + + + QMetaObject::invokeMethod(localize_marker, "localize", Qt::QueuedConnection, + Q_ARG(vtkImageData*, Visualizer->getVolume()), + Q_ARG(int,Ui->spinBox->text().toInt()), + Q_ARG(double,Ui->doubleSpinBox_2->text().toDouble()), + Q_ARG(double,Ui->doubleSpinBox_2bis->text().toDouble()), + Q_ARG(double,Ui->doubleSpinBox_3->text().toDouble()), + Q_ARG(double,Ui->doubleSpinBox_5->text().toDouble()), + Q_ARG(QList ,selected)); + + + QMetaObject::invokeMethod(Ui->statusbar, "showMessage", Qt::QueuedConnection, + Q_ARG(QString, "Localizing..." ), + Q_ARG(int, 0)); + Ui->pushButton_4->setEnabled(true); + Ui->pushButton_3->setEnabled(false); +} + +void MainWindow::call_localize_cancel(){ + + QMetaObject::invokeMethod(localize_marker, "callAbort", Qt::QueuedConnection); + Ui->pushButton_4->setEnabled(false); +} + +void MainWindow::onItemChanged(QStandardItem* item){ + + + QMetaObject::invokeMethod(Visualizer, "onSingleMarkerChange", Qt::QueuedConnection, + Q_ARG(int, item->text().toInt() ), + Q_ARG(bool, (item->checkState() == Qt::Checked ? true : false))); + +} + +void MainWindow::updateMarkerPos(double dx, double dy, double dz){ + treeModel->blockSignals(true); + for(int ii=0; ii< treeModel->rowCount() ; ii++){ + treeModel->item(ii,0)->child(0,0)->setText(QString::number( + treeModel->item(ii,0)->child(0,0)->text().toDouble()-dx)); + treeModel->item(ii,0)->child(1,0)->setText(QString::number( + treeModel->item(ii,0)->child(1,0)->text().toDouble()-dy)); + treeModel->item(ii,0)->child(2,0)->setText(QString::number( + treeModel->item(ii,0)->child(2,0)->text().toDouble()-dz)); + } + treeModel->blockSignals(false); +} + +void MainWindow::onLocalizationEnd(QList marker_list){ + + Ui->treeView->reset(); + treeModel->clear(); + Ui->pb_save->setEnabled(true); + Ui->pushButton_4->setEnabled(false); + Ui->pushButton_3->setEnabled(true); + + if(marker_list.size() == 0){ + Ui->pb_save->setEnabled(false); + QList marker_list; + QMetaObject::invokeMethod(Visualizer, "renderMarkers", Qt::QueuedConnection, + Q_ARG(QList,marker_list)); + + QMetaObject::invokeMethod(Ui->statusbar, "showMessage", Qt::QueuedConnection, + Q_ARG(QString, "Localization done. No markers found." ), + Q_ARG(int, 3000)); + + this->Ui->progBar->reset(); + this->Ui->pushButton->setEnabled(false); + this->Ui->pushButton_2->setEnabled(false); + + return; + } + + QStandardItem *parentItem = treeModel->invisibleRootItem(); + + for (int i = 0; i < marker_list.size() ; i++) { + QStandardItem *item = new QStandardItem(QString("%0").arg(i)); + item->setCheckable(true); + item->setCheckState(Qt::Checked); + item->setEditable(false); + + QList columnList; + columnList.clear(); + columnList.append(new QStandardItem(QString("%0").arg(marker_list.at(i).centroid.x*1000))); + columnList.append(new QStandardItem(QString("%0").arg(marker_list.at(i).centroid.y*1000))); + columnList.append(new QStandardItem(QString("%0").arg(marker_list.at(i).centroid.z*1000))); + item->appendRows(columnList); + +// cout<< "Scritto tree: " <appendRow(item); + treeModel->setItem(i, 0, item); + } + + Ui->treeView->setModel (treeModel); + Visualizer->renderMarkers(marker_list); + Ui->statusbar->showMessage("Localization done.",3); + + QMetaObject::invokeMethod(Ui->statusbar, "showMessage", Qt::QueuedConnection, + Q_ARG(QString, "Localization done." ), + Q_ARG(int, 3000)); + + this->Ui->progBar->reset(); + this->Ui->pushButton->setEnabled(true); + this->Ui->pushButton_2->setEnabled(true); +} + +void MainWindow::call_saveDotCtFile(){ + + /*QString filename = QFileDialog::getSaveFileName ( + this, + tr("Save .tac file"), + ".", + tr("Reference marker file (*.tac)") );*/ + + QString filename; + filename = pathOut + + "\\" + + Ui->l_patientID->text().split(" ").first() + + + ".tac"; + + QFile file(filename); + file.open(QIODevice::WriteOnly | QIODevice::Text); + QTextStream out(&file); +#ifdef MULTIPLE_REF + if(Ui->a_OTSref->isChecked() ){ +#endif + /*divide by volumeSpacing*/ + for(int ii=0; ii< treeModel->rowCount() ; ii++) + if( treeModel->item(ii,0)->checkState() == Qt::Checked) + out << treeModel->item(ii,0)->child(0,0)->text().toDouble()/Visualizer->getSpacing()[0]<<" " + << treeModel->item(ii,0)->child(1,0)->text().toDouble()/Visualizer->getSpacing()[1]<<" " + << treeModel->item(ii,0)->child(2,0)->text().toDouble()/Visualizer->getSpacing()[2]<rowCount() ; ii++) + if( treeModel->item(ii,0)->checkState() == Qt::Checked) + out << treeModel->item(ii,0)->child(0,0)->text()<<" " + << treeModel->item(ii,0)->child(1,0)->text()<<" " + << treeModel->item(ii,0)->child(2,0)->text()<l_isoV->text() == QString("Yes")) + out<< 1; + else if (Ui->l_isoV->text() == QString("No")) + out<< 0; + + // optional, as QFile destructor will already do it: + file.close(); + + + QMessageBox msgBox; + msgBox.setText("File saved"); + msgBox.setIcon(QMessageBox::Icon::Information); + msgBox.setInformativeText(filename); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setDefaultButton(QMessageBox::Ok); + msgBox.setFixedSize(400,150); + int ret = msgBox.exec(); +} + + +void MainWindow::call_visualize_all(){ +// //VISUALIZATION and Countour shift + for(int ii=0; ii< treeModel->rowCount() ; ii++) + treeModel->item(ii,0)->setCheckState(Qt::Checked); +} + +void MainWindow::call_hide_all(){ + for(int ii=0; ii< treeModel->rowCount() ; ii++) + treeModel->item(ii,0)->setCheckState(Qt::Unchecked); +} + +void MainWindow::call_ToolbarAction(QAction* action){ + + if(action == Ui->actionQuit) + this->call_quit(); +// QApplication::quit(); + + if(action == Ui->actionLoad){ + QString dirName = QFileDialog::getExistingDirectory(this,tr("Chose DICOM Directory"), "wrkDir",QFileDialog::ShowDirsOnly); + if(!dirName.isEmpty()) { + QMetaObject::invokeMethod(loadPatient, "load", Qt::QueuedConnection, + Q_ARG(QString, dirName)); + loadDialog->show(); + + QMetaObject::invokeMethod(Ui->statusbar, "showMessage", Qt::QueuedConnection, + Q_ARG(QString, "Loading patient..." ), + Q_ARG(int, 3000)); + + } + //patientLoader->parse(dirName); + } + + +} + +void MainWindow::call_skullMask(){ + + QApplication::setOverrideCursor(Qt::WaitCursor); + + QMetaObject::invokeMethod(Ui->statusbar, "showMessage", Qt::QueuedConnection, + Q_ARG(QString, "Skull masking..." ), + Q_ARG(int, 3000)); + + QMetaObject::invokeMethod(skullRemoval, "runFilter", Qt::QueuedConnection, + Q_ARG(vtkImageData*, Visualizer->getVolume()), + Q_ARG(double, regGrownParameters[1]), + Q_ARG(double,regGrownParameters[0])); + + Ui->pb_manualMask->setEnabled(false); + Ui->pushButton_5->setEnabled(false); + Ui->frame_3->setEnabled(false); + Ui->frame_4->setEnabled(false); + +} + + +void MainWindow::onAutoMaskError(QString msg){ + QApplication::restoreOverrideCursor(); + QMessageBox msgBox; + msgBox.setText("The automatic masking failed..."); + msgBox.setInformativeText("Manual volume clipping is the way to handle this error."); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setDefaultButton(QMessageBox::Ok); + int ret = msgBox.exec(); +} + +void MainWindow:: onRTIsoAvailable(double* iso){ + +#ifdef MULTIPLE_REF + Ui->a_RTref->setEnabled(true); +#endif + Ui->l_patientID_6->setText(QString("%0 %1 %2") + .arg(iso[0]) + .arg(iso[1]) + .arg(iso[2])); + +} + +void MainWindow:: onCTVolumeAvailable(double* bounds, double* spacing, int* dim){ + +} + +void MainWindow:: onParsedOK(int result, gPatientRTGeneralInfos* infos){ + + + if(result != NOERRORS) + return; + + if( QString(infos->PatientID).isEmpty() ) + Ui->l_patientID->setText(QString("RTPlan not available")); + else + Ui->l_patientID->setText(QString(infos->PatientID)); + + if( QString(infos->PatientOrientation).isEmpty() ) + Ui->l_patientID_2->setText(QString("RTPlan not available")); + else + Ui->l_patientID_2->setText(QString(infos->PatientOrientation)); + + + + QMetaObject::invokeMethod(Ui->statusbar, "showMessage", Qt::QueuedConnection, + Q_ARG(QString, "Dir parser: Ok." ), + Q_ARG(int, 3000)); + + //QMetaObject::invokeMethod(load2vtk, "loadInput", Qt::QueuedConnection, + // Q_ARG(std::vector, infos->CTfiles), + // Q_ARG(std::string,infos->rtIonPlanPath.toStdString()) + // ); + + +} + +void MainWindow::call_loadPatient(){ + + + +// Ui->gw_patientWidget->PatientAge->setText(infos->PatientAge); + +// QMetaObject::invokeMethod(load2vtk, "loadInput", Qt::QueuedConnection, +// Q_ARG(std::vector, infos->CTfiles), +// Q_ARG(std::string,infos->rtIonPlanPath.toStdString()) +// ); +// questo deve chiamare il loadVolume in Gren +} + + +#ifdef MULTIPLE_REF + +void MainWindow::onRefTrigger(QAbstractButton* trigger_button){ + + QRadioButton* clickedButton= reinterpret_cast (trigger_button); + + if(clickedButton == Ui->a_DCMref){ + QMetaObject::invokeMethod(loadPatient, "changeRef", Qt::QueuedConnection, + Q_ARG(int,0)); + } + + if(clickedButton == Ui->a_RTref){ + QMetaObject::invokeMethod(loadPatient, "changeRef", Qt::QueuedConnection, + Q_ARG(int,1)); + } + + if(clickedButton == Ui->a_OTSref){ + QMetaObject::invokeMethod(loadPatient, "changeRef", Qt::QueuedConnection, + Q_ARG(int,2)); + } + +} +#endif + +void MainWindow::onVisualizationTrigger(QAction* trigger_action){ + + if(trigger_action == Ui->actionOrthoslice ){ + this->Visualizer->onRequestRenderChange(1); + this->Ui->qvtk->GetInteractor()->Render(); + } + + if(trigger_action == Ui->actionVolRen ){ + this->Visualizer->onRequestRenderChange(0); + this->Ui->qvtk->GetInteractor()->Render(); + } + +} + + +void MainWindow::onManualMaskTrigger(bool state){ + + if(state){ + this->Visualizer->onRequestManualMask(0); + }else{ + this->Visualizer->onRequestManualMask(1); + } + +} + + diff --git a/mainw.h b/mainw.h new file mode 100755 index 0000000..2e42fca --- /dev/null +++ b/mainw.h @@ -0,0 +1,237 @@ +#ifndef _MAINW_H_ +#define _MAINW_H_ + +//#define MULTIPLE_REF + + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +#include "gRen.h" + +//#include "wrkDirParser.h" +//#include "loadDCM2VTK.h" +#include "gPatientRTGeneralInfos.h" +#include "dicomUtils.h" +#include "connectITKVTK.h" +#include +#include "gLoadPatient.h" + +#include "gLocalize.h" + +#include +#include + +#include + +#include "gSkullRemoval.h" + +#include + +class gLoadPatDialog: public QDialog{ + public: + gLoadPatDialog(QWidget *parent = 0) + : QDialog(parent){ + QVBoxLayout * lay = new QVBoxLayout(this); + this->setModal(true); + + message = new QLabel(this); + lay->addWidget(message); + + message->setText("Loading patient from wrkDir... Please wait."); + }; + +private: + QLabel *message; + +}; + +class Ui_MainWindow { +public: + Ui_MainWindow(){}; + + QVTKWidget* qvtk; + QAction *actionLoad; + QAction *actionQuit; + QAction *actionVolRen; + QAction *actionOrthoslice; + QWidget *centralwidget; + QHBoxLayout *horizontalLayout_8; + QWidget *widget; + QVBoxLayout *verticalLayout_5; + QFrame *frame_2; + QVBoxLayout *verticalLayout_2; + QFormLayout *formLayout_2; + QLabel *label_6; + QLabel *l_patientID; + QLabel *label_8; + QLabel *l_patientID_2; + QLabel *label_12; + QLabel *l_patientID_6; + QLabel *label_isoV; + QLabel *l_isoV; + + + QFrame *frame_5; + QVBoxLayout *verticalLayout_4; + QHBoxLayout *horizontalLayout_7; + QLabel *label_7; + QPushButton *pushButton_5; + QFrame *frame_3; + QVBoxLayout *verticalLayout; + QHBoxLayout *horizontalLayout; + QLabel *label; + QSpacerItem *horizontalSpacer; + QSpinBox *spinBox; + QHBoxLayout *horizontalLayout_2; + QCheckBox *checkBox_2; + QLabel *label_2; + QSpacerItem *horizontalSpacer_2; + QDoubleSpinBox *doubleSpinBox_2; + QDoubleSpinBox *doubleSpinBox_2bis; + QHBoxLayout *horizontalLayout_3; + QCheckBox *checkBox_3; + QLabel *label_3; + QSpacerItem *horizontalSpacer_3; + QDoubleSpinBox *doubleSpinBox_3; + QHBoxLayout *horizontalLayout_4; + QSpacerItem *horizontalSpacer_4; + QHBoxLayout *horizontalLayout_5; + QCheckBox *checkBox_5; + QLabel *label_5; + QSpacerItem *horizontalSpacer_5; + QDoubleSpinBox *doubleSpinBox_5; + QHBoxLayout *horizontalLayout_6; + QPushButton *pushButton_3; + QPushButton *pushButton_4; + QFrame *frame_4; + QVBoxLayout *verticalLayout_3; + QTreeView *treeView; + QHBoxLayout *formLayout; + QPushButton *pushButton; + QPushButton *pushButton_2; + QFrame *frame; + QStatusBar *statusbar; + QToolBar *toolBar; + QProgressBar *progBar; + QActionGroup* viewGroup; + QPushButton* pb_manualMask; + QPushButton* pb_save; +#ifdef MULTIPLE_REF + QButtonGroup* refGroup; + QRadioButton* a_DCMref; + QRadioButton* a_RTref; + QRadioButton* a_OTSref; +#endif + void setupUi(QMainWindow *MainWindow); + +}; + + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QString p_loadPath); + ~MainWindow(); + +private: + Ui_MainWindow* Ui; + void retranslateUi(Ui_MainWindow* Ui, QMainWindow *MainWindow); + void connectUi(Ui_MainWindow* Ui); + + gRen * Visualizer; + //gPatientLoader* patientLoader; + gLoadPatient* loadPatient; + QThread* loadPatient_thread; + + //loadDCM2VTK* load2vtk; + //QThread* load2VtkThread; + gLocalize* localize_marker; + QThread* localize_thread; + gSkullRemoval* skullRemoval; + QThread* skull_thread; + + QStandardItemModel* treeModel; + QString loadPath; + gLoadPatDialog* loadDialog; + + double regGrownParameters[2]; + + QString pathIn; + QString pathOut; + + + +private slots: + void call_localize_start(); + void call_localize_cancel(); + void call_visualize_all(); + void call_hide_all(); + void call_ToolbarAction(QAction* action); + void call_skullMask(); + void call_loadPatient(); + void call_saveDotCtFile(); + void call_quit(); + void call_quit_nothreads(); + + void onParsedOK(int result, gPatientRTGeneralInfos* data); + void onParsedEmptyFolder(); + void onLocalizationEnd(QList marker_list); + void onLocalizationAborted(); + void onVisualizationTrigger(QAction* trigger_action); + void onManualMaskTrigger(bool state); + void onAutoMaskError(QString msg); + void onItemChanged(QStandardItem* item); + void setStatusMsg(QString msg, int timeout); + void onVirtualIsoTested(bool IsoV); + + +#ifdef MULTIPLE_REF + void onRefTrigger(QAbstractButton* trigger_button); +#endif + void onPatientlLoaded(); + void onRTIsoAvailable(double* iso); + void onCTVolumeAvailable(double* bounds, double* spacing, int* dim); + void updateMarkerPos(double dx, double dy, double dz); + + void onSkullMaskingUpdt(QString msg, double val); + void onAutoSkullMaskEnd(vtkImageData* img); +}; + + + + +#endif \ No newline at end of file