From 0f2c94b5b83d2c8088c2858b230660a2e3b29dc8 Mon Sep 17 00:00:00 2001 From: Raedlr Date: Wed, 28 Aug 2024 14:37:31 +0200 Subject: [PATCH 01/28] Refactor of the dicom import to object oriented structure --- .../matRad_DicomImporter.m | 136 ++++++++++++ .../matRad_calcHU.m | 10 +- .../@matRad_DicomImporter/matRad_createCst.m | 83 +++++++ .../matRad_dummyCst.m | 26 +-- .../matRad_importDicom.m | 203 ++++++++++++++++++ .../matRad_importDicomCt.m | 117 +++++----- .../matRad_importDicomRTDose.m | 36 ++-- .../matRad_importDicomRTPlan.m | 85 +++++--- .../matRad_importDicomRtss.m | 37 ++-- .../matRad_importDicomSteeringParticles.m | 32 +-- .../matRad_importDicomSteeringPhotons.m | 56 ++--- .../matRad_interpDicomDoseCube.m | 70 +++--- .../matRad_scanDicomImportFolder.m | 127 ++++++----- .../dicom/matRad_convRtssContours2Indices.m | 2 +- matRad/dicom/matRad_createCst.m | 83 ------- matRad/dicom/matRad_importDicom.m | 180 ---------------- matRad/dicom/matRad_importFieldShapes.m | 41 ++-- matRad/dicom/matRad_interpDicomCtCube.m | 14 +- matRad/gui/widgets/matRad_importDicomWidget.m | 143 ++---------- 19 files changed, 799 insertions(+), 682 deletions(-) create mode 100644 matRad/dicom/@matRad_DicomImporter/matRad_DicomImporter.m rename matRad/dicom/{ => @matRad_DicomImporter}/matRad_calcHU.m (78%) create mode 100644 matRad/dicom/@matRad_DicomImporter/matRad_createCst.m rename matRad/dicom/{ => @matRad_DicomImporter}/matRad_dummyCst.m (66%) create mode 100644 matRad/dicom/@matRad_DicomImporter/matRad_importDicom.m rename matRad/dicom/{ => @matRad_DicomImporter}/matRad_importDicomCt.m (62%) rename matRad/dicom/{ => @matRad_DicomImporter}/matRad_importDicomRTDose.m (80%) rename matRad/dicom/{ => @matRad_DicomImporter}/matRad_importDicomRTPlan.m (58%) rename matRad/dicom/{ => @matRad_DicomImporter}/matRad_importDicomRtss.m (75%) rename matRad/dicom/{ => @matRad_DicomImporter}/matRad_importDicomSteeringParticles.m (93%) rename matRad/dicom/{ => @matRad_DicomImporter}/matRad_importDicomSteeringPhotons.m (56%) rename matRad/dicom/{ => @matRad_DicomImporter}/matRad_interpDicomDoseCube.m (51%) rename matRad/dicom/{ => @matRad_DicomImporter}/matRad_scanDicomImportFolder.m (58%) delete mode 100644 matRad/dicom/matRad_createCst.m delete mode 100644 matRad/dicom/matRad_importDicom.m diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_DicomImporter.m b/matRad/dicom/@matRad_DicomImporter/matRad_DicomImporter.m new file mode 100644 index 000000000..bade5523e --- /dev/null +++ b/matRad/dicom/@matRad_DicomImporter/matRad_DicomImporter.m @@ -0,0 +1,136 @@ +classdef matRad_DicomImporter < handle + % matRad_DicomImporter matRad class to handle a dicom import. + % + % Example on how to use the matRad_DicomImport class + % + % dcmImpObj = matRad_DicomImporter('pathToFolder'); % create instance of matRad_DicomImporter + % dcmImpObj.matRad_importDicom(dcmImpObj); % run the import + % + % + % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % + % Copyright 2020 the matRad development team. + % + % This file is part of the matRad project. It is subject to the license + % terms in the LICENSE file found in the top-level directory of this + % distribution and at https://github.com/e0404/matRad/LICENSE.md. No part + % of the matRad project, including this file, may be copied, modified, + % propagated, or distributed except according to the terms contained in the + % LICENSE file. + % + % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + properties + + % path to DICOM file + patDir; + + % lists of all files + allfiles; + patients; + importFiles; % all the names (directories) of files, that will be imported + + % properties with data for import functions + importCT; + importRtss; + importRTDose; + + % structures for .mat file + ct = []; + cst = []; + stf = []; + pln = []; + resultGUI = []; + + doseGrid; + + % bools + dicomMetaBool; + visBool; + + + + end + + methods + + function obj = matRad_DicomImporter(pathToFolder) + + %matRad_DicomImporter Construct an instance of this class + % Can be called with the structures. If no argument is given, + % all structures will be read from the base workspace + + obj.patDir = pathToFolder; + matRad_cfg = MatRad_Config.instance(); + + env = matRad_getEnvironment(); + if strcmp(env,'OCTAVE') + %Octave needs the DICOM package + try + pkg load dicom; + catch + matRad_cfg.dispError('The DICOM export requires the octave-forge package "dicom"!\n'); + end + end + disp(pathToFolder) + if isempty(pathToFolder) + obj.patDir = uigetdir(pwd); + end + + obj = matRad_scanDicomImportFolder(obj); + + %MatRad will also be able to separate multiple patients, but this example will only work if there's only a single patient in the folder. + + ctFiles = strcmp(obj.allfiles(:,2),'CT'); + rtssFiles = strcmpi(obj.allfiles(:,2),'rtstruct'); %note we can have multiple RT structure sets, matRad will always import the firstit finds + rtPlanFiles = strcmpi(obj.allfiles(:,2),'rtplan'); + rtDoseFiles = strcmpi(obj.allfiles(:,2),'rtdose'); + + obj.importFiles.ct = obj.allfiles(ctFiles,:);%All CT slice filepaths stored in a cell array like {'CTSlice1.dcm','CTSlice2.dcm'}; + obj.importFiles.rtss = obj.allfiles(rtssFiles,1); %will also be a cell array like {'RTStruct.dcm'}; + obj.importFiles.rtplan = obj.allfiles(rtPlanFiles,1); + obj.importFiles.rtdose = obj.allfiles(rtDoseFiles,1); + + for i = numel(obj.allfiles(:,1)):-1:1 + if strcmp(obj.allfiles(i,2),'CT') + obj.importFiles.resx = obj.allfiles{i,9}; + obj.importFiles.resy = obj.allfiles{i,10}; + obj.importFiles.resz = obj.allfiles{i,11}; %some CT dicoms do not follow the standard and use SpacingBetweenSlices + break + end + end + + %We need to set one more variable I forgot to mention above + obj.importFiles.useDoseGrid = false; + + + + end + + matRad_importDicom(obj) + + obj = matRad_importDicomCt(obj) + + obj = matRad_importDicomRTDose(obj) + + obj = matRad_importDicomRTPlan(obj) + + obj = matRad_importDicomRtss(obj) + + obj = matRad_importDicomSteeringPhotons(obj) + + obj = matRad_importDicomSteeringParticles(obj) + + obj = matRad_scanDicomImportFolder(obj) + + obj = matRad_calcHU(obj) + + obj = matRad_createCst(obj) + + obj = matRad_dummyCst(obj) + + matRad_saveImport(obj); + + end + +end + diff --git a/matRad/dicom/matRad_calcHU.m b/matRad/dicom/@matRad_DicomImporter/matRad_calcHU.m similarity index 78% rename from matRad/dicom/matRad_calcHU.m rename to matRad/dicom/@matRad_DicomImporter/matRad_calcHU.m index ef6365eba..8f74cf5d4 100644 --- a/matRad/dicom/matRad_calcHU.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_calcHU.m @@ -1,9 +1,9 @@ -function ct = matRad_calcHU(ct) +function obj = matRad_calcHU(obj) % matRad function to calculate Hounsfield units from a dicom ct % that originally uses intensity values % % call -% ct = matRad_calcHU(ct) +% obj = matRad_calcHU(obj) % % input % ct: unprocessed dicom ct data which are stored as intensity values (IV) @@ -29,10 +29,10 @@ % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -for i = 1:ct.numOfCtScen - ct.cubeHU{i} = double(ct.cubeIV{i}) * double(ct.dicomInfo.RescaleSlope) + double(ct.dicomInfo.RescaleIntercept); +for i = 1:obj.ct.numOfCtScen + obj.ct.cubeHU{i} = double(obj.ct.cubeIV{i}) * double(obj.ct.dicomInfo.RescaleSlope) + double(obj.ct.dicomInfo.RescaleIntercept); end -ct = rmfield(ct,'cubeIV'); +obj.ct = rmfield(obj.ct,'cubeIV'); end diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_createCst.m b/matRad/dicom/@matRad_DicomImporter/matRad_createCst.m new file mode 100644 index 000000000..94bfa3571 --- /dev/null +++ b/matRad/dicom/@matRad_DicomImporter/matRad_createCst.m @@ -0,0 +1,83 @@ +function obj = matRad_createCst(obj) +% matRad function to create a cst struct upon dicom import +% +% call +% obj = matRad_createCst(obj) +% +% input +% structures: matlab struct containing information about rt structure +% set (generated with matRad_importDicomRtss and +% matRad_convRtssContours2Indices) +% +% output +% cst: matRad cst struct +% +% References +% - +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Copyright 2015 the matRad development team. +% +% This file is part of the matRad project. It is subject to the license +% terms in the LICENSE file found in the top-level directory of this +% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part +% of the matRad project, including this file, may be copied, modified, +% propagated, or distributed except according to the terms contained in the +% LICENSE file. +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +matRad_cfg = MatRad_Config.instance(); + +nStructures = size(obj.importRtss.structures,2); +obj.cst = cell(nStructures,6); + +%Create set of default colors +defaultColors = colorcube(nStructures); + +for i = 1:size(obj.importRtss.structures,2) + obj.cst{i,1} = i - 1; % first organ has number 0 + obj.cst{i,2} = obj.importRtss.structures(i).structName; + + if ~isempty(regexpi(obj.cst{i,2},'tv', 'once')) || ... + ~isempty(regexpi(obj.cst{i,2},'target', 'once')) || ... + ~isempty(regexpi(obj.cst{i,2},'gtv', 'once')) || ... + ~isempty(regexpi(obj.cst{i,2},'ctv', 'once')) || ... + ~isempty(regexpi(obj.cst{i,2},'ptv', 'once')) || ... + ~isempty(regexpi(obj.cst{i,2},'boost', 'once')) || ... + ~isempty(regexpi(obj.cst{i,2},'tumor', 'once')) + + obj.cst{i,3} = 'TARGET'; + + obj.cst{i,5}.Priority = 1; + + % default objectives for targets + objective = DoseObjectives.matRad_SquaredDeviation; + objective.penalty = 800; + objective.parameters = {30}; %Default reference Dose + obj.cst{i,6}{1} = struct(objective); + + else + + obj.cst{i,3} = 'OAR'; + + obj.cst{i,5}.Priority = 2; + + obj.cst{i,6} = []; % define no OAR dummy objcetives + + end + + obj.cst{i,4}{1} = obj.importRtss.structures(i).indices; + + % set default parameter for biological planning + obj.cst{i,5}.alphaX = 0.1; + obj.cst{i,5}.betaX = 0.05; + obj.cst{i,5}.Visible = 1; + if isfield(obj.importRtss.structures(i),'structColor') && ~isempty(obj.importRtss.structures(i).structColor) + obj.cst{i,5}.visibleColor = obj.importRtss.structures(i).structColor' ./ 255; + else + obj.cst{i,5}.visibleColor = defaultColors(i,:); + matRad_cfg.dispInfo('No color information for structure %d "%s". Assigned default color [%f %f %f]\n',i,obj.cst{i,2},defaultColors(i,1),defaultColors(i,2),defaultColors(i,3)); + end +end diff --git a/matRad/dicom/matRad_dummyCst.m b/matRad/dicom/@matRad_DicomImporter/matRad_dummyCst.m similarity index 66% rename from matRad/dicom/matRad_dummyCst.m rename to matRad/dicom/@matRad_DicomImporter/matRad_dummyCst.m index 842eb0bc4..92a4bc0f6 100644 --- a/matRad/dicom/matRad_dummyCst.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_dummyCst.m @@ -1,8 +1,8 @@ -function cst = matRad_dummyCst(ct) +function obj = matRad_dummyCst(obj) % matRad function to create a dummy cst struct for a ct % % call -% cst = matRad_dummyCst(ct) +% obj = matRad_dummyCst(obj) % % input % ct: matRad ct struct @@ -29,21 +29,21 @@ warning('Did not find RTSS. Creating dummy segmentation for matRad.'); % allocate -cst = cell(1,6); +obj.cst = cell(1,6); % fill -cst{1,1} = 0; % first organ has number 0 -cst{1,2} = 'dummyContour'; -cst{1,3} = 'OAR'; -cst{1,4}{1} = find(ct.cubeHU{1}>0.1); -cst{1,5}.Priority = 1; +obj.cst{1,1} = 0; % first organ has number 0 +obj.cst{1,2} = 'dummyContour'; +obj.cst{1,3} = 'OAR'; +obj.cst{1,4}{1} = find(obj.ct.cubeHU{1}>0.1); +obj.cst{1,5}.Priority = 1; % set default parameter for biological planning -cst{1,5}.alphaX = 0.1; -cst{1,5}.betaX = 0.05; -cst{1,5}.Visible = 1; -cst{1,5}.visibleColor = [0 0 0]; +obj.cst{1,5}.alphaX = 0.1; +obj.cst{1,5}.betaX = 0.05; +obj.cst{1,5}.Visible = 1; +obj.cst{1,5}.visibleColor = [0 0 0]; % define no objcetives -cst{1,6} = []; +obj.cst{1,6} = []; diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_importDicom.m b/matRad/dicom/@matRad_DicomImporter/matRad_importDicom.m new file mode 100644 index 000000000..bdf363a72 --- /dev/null +++ b/matRad/dicom/@matRad_DicomImporter/matRad_importDicom.m @@ -0,0 +1,203 @@ +function matRad_importDicom(obj) +% matRad wrapper function to import a predefined set of dicom files files +% into matRad's native data formats +% +% call +% matRad_importDicom(obj) +% +% input +% importFiles: list of files to be imported (will contain cts and rt +% structure set) +% dicomMetaBool: (boolean, optional) import complete dicomInfo and +% patientName +% +% output +% ct: matRad ct struct +% cst: matRad cst struct +% pln: matRad plan struct +% stf: matRad stf struct +% resultGUI: matRad result struct holding data for visualization in GUI +% +% References +% - +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Copyright 2015 the matRad development team. +% +% This file is part of the matRad project. It is subject to the license +% terms in the LICENSE file found in the top-level directory of this +% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part +% of the matRad project, including this file, may be copied, modified, +% propagated, or distributed except according to the terms contained in the +% LICENSE file. +% +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +matRad_cfg = MatRad_Config.instance(); + +%% +if ~exist('dicomMetaBool','var') + obj.dicomMetaBool = true; +end + +%% +h = waitbar(0,'Please wait...','Color',matRad_cfg.gui.backgroundColor,'DefaultTextColor',matRad_cfg.gui.textColor); +matRad_applyThemeToWaitbar(h); +%h.WindowStyle = 'Modal'; +steps = 2; + +%% import ct-cube +waitbar(1 / steps) +obj.importCT.resolution.x = str2double(obj.importFiles.resx); +obj.importCT.resolution.y = str2double(obj.importFiles.resy); +obj.importCT.resolution.z = str2double(obj.importFiles.resz); % [mm] / lps coordinate system +if obj.importFiles.useDoseGrid && isfield(obj.importFiles,'rtdose') + % get grid from dose cube + if matRad_cfg.isOctave || verLessThan('matlab','9') + doseInfo = dicominfo(obj.importFiles.rtdose{1,1}); + else + doseInfo = dicominfo(obj.importFiles.rtdose{1,1},'UseDictionaryVR',true); + end + obj.doseGrid{1} = doseInfo.ImagePositionPatient(1) + doseInfo.ImageOrientationPatient(1) * ... + doseInfo.PixelSpacing(1) * double(0:doseInfo.Columns - 1); + obj.doseGrid{2} = doseInfo.ImagePositionPatient(2) + doseInfo.ImageOrientationPatient(5) * ... + doseInfo.PixelSpacing(2) * double(0:doseInfo.Rows - 1); + obj.doseGrid{3} = doseInfo.ImagePositionPatient(3) + doseInfo.GridFrameOffsetVector(:)'; + + % get ct on grid + obj = matRad_importDicomCt(obj); + +else + obj = matRad_importDicomCt(obj); +end + +if ~isempty(obj.importFiles.rtss) + + %% import structure data + waitbar(2 / steps) + obj = matRad_importDicomRtss(obj); + close(h) + + %% creating structure cube + h = waitbar(0,'Please wait...','Color',matRad_cfg.gui.backgroundColor,'DefaultTextColor',matRad_cfg.gui.textColor); + matRad_applyThemeToWaitbar(h); + %h.WindowStyle = 'Modal'; + steps = numel(obj.importRtss.structures); + + + % The x- & y-direction in lps-coordinates are specified in: + % ImageOrientationPatient + +obj.importRtss.xDir = obj.ct.dicomInfo.ImageOrientationPatient(1:3); % lps: [1;0;0] +obj.importRtss.yDir = obj.ct.dicomInfo.ImageOrientationPatient(4:6); % lps: [0;1;0] + + +if ~(obj.importRtss.xDir(1) == 1 && obj.importRtss.xDir(2) == 0 && obj.importRtss.xDir(3) == 0) + matRad_cfg.dispInfo('\nNonstandard image orientation: tring to Mirror RTSS x-direction...') +end + +if ~(obj.importRtss.yDir(1) == 0 && obj.importRtss.yDir(2) == 1 && obj.importRtss.yDir(3) == 0) + matRad_cfg.dispInfo('\nNonstandard image orientation: trying to Mirror RTSS y direction...') +end + for i = 1:numel(obj.importRtss.structures) + % computations take place here + waitbar(i / steps) + fprintf('creating cube for %s volume... ', obj.importRtss.structures(i).structName); + try + obj.importRtss.structures(i).indices = matRad_convRtssContours2Indices(obj.importRtss.structures(i),obj.ct); + fprintf('\n'); + catch ME + warning('matRad:dicomImport','could not be imported: %s',ME.message); + obj.importRtss.structures(i).indices = []; + end + end + fprintf('finished!\n'); + close(h) + + %% creating cst + obj = matRad_createCst(obj); + +else + + obj = matRad_dummyCst(obj); + +end + +%% determine pln parameters +if ~isempty(obj.importFiles.rtplan) + if ~(cellfun(@isempty,obj.importFiles.rtplan(1,:))) + obj = matRad_importDicomRTPlan(obj); + end +else + obj.pln = struct([]); +end + +%% import stf +if ~isempty(obj.importFiles.rtplan) + if ~(cellfun(@isempty,obj.importFiles.rtplan(1,:))) + if (strcmp(obj.pln.radiationMode,'protons') || strcmp(obj.pln.radiationMode,'carbon')) + %% import steering file + % pln output because bixelWidth is determined via the stf + obj = matRad_importDicomSteeringParticles(obj); + elseif strcmp(obj.pln.radiationMode, 'photons') && isfield(obj.pln.propStf,'collimation') + % return correct angles in pln + obj = matRad_importDicomSteeringPhotons(obj); + else + warning('No support for DICOM import of steering information for this modality.'); + end + end +else + obj.stf = struct([]); +end + +%% import dose cube +if ~isempty(obj.importFiles.rtdose) + % check if obj.importFiles.rtdose contains a path and is labeld as RTDose + % only the first two elements are relevant for loading the rt dose + if ~(cellfun(@isempty,obj.importFiles.rtdose(1,1))) + fprintf('loading dose files...\n'); + % parse plan in order to scale dose cubes to a fraction based dose + obj = matRad_importDicomRTDose(obj); + if size(obj.resultGUI) == 0 + obj.resultGUI = struct([]); + end + end + fprintf('finished!\n'); +else + obj.resultGUI = struct([]); + fprintf('There are no dose files!\n'); +end + +%% put weight also into resultGUI +if ~isempty(obj.stf) && ~isempty(obj.resultGUI) + obj.resultGUI.w = []; + for i = 1:size(obj.stf,2) + obj.resultGUI.w = [obj.resultGUI.w; [obj.stf(i).ray.weight]']; + end +end + +%% put ct, cst, pln, stf, resultGUI to the workspace +ct = obj.ct; +cst = obj.cst; +pln = obj.pln; +stf = obj.stf; +resultGUI = obj.resultGUI; + +assignin('base', 'ct', ct); +assignin('base', 'cst', cst); + +if ~isempty(obj.pln) + assignin('base', 'pln', pln); +end + +if ~isempty(obj.stf) + assignin('base', 'stf', stf); +end + +if ~isempty(obj.resultGUI) +assignin('base', 'resultGUI', resultGUI); +end + +end + + diff --git a/matRad/dicom/matRad_importDicomCt.m b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomCt.m similarity index 62% rename from matRad/dicom/matRad_importDicomCt.m rename to matRad/dicom/@matRad_DicomImporter/matRad_importDicomCt.m index 768bea7b1..a8027c3e0 100644 --- a/matRad/dicom/matRad_importDicomCt.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomCt.m @@ -1,17 +1,15 @@ -function ct = matRad_importDicomCt(ctList, resolution, dicomMetaBool, grid, visBool) +function obj = matRad_importDicomCt(obj) % matRad function to import dicom ct data % % call -% ct = matRad_importDicomCt(ctList, resolution, dicomMetaBool) -% ct = matRad_importDicomCt(ctList, resolution, dicomMetaBool, grid) -% ct = matRad_importDicomCt(ctList, resolution, dicomMetaBool, grid, visBool) +% matRad_importDicomCt(obj) % % input -% ctList: list of dicom ct files +% ct: list of dicom ct files % resolution: resolution of the imported ct cube, i.e. this function % will interpolate to a different resolution if desired % dicomMetaBool: store complete dicom information if true -% grid: optional: a priori grid specified for interpolation +% doseGrid: optional: a priori grid specified for interpolation % visBool: optional: turn on/off visualization % % output @@ -42,48 +40,48 @@ %% processing input variables if ~exist('visBool','var') - visBool = 0; + obj.visBool = 0; end matRad_checkEnvDicomRequirements(matRad_cfg.env); % creation of ctInfo list -numOfSlices = size(ctList,1); +numOfSlices = size(obj.importFiles.ct, 1); matRad_cfg.dispInfo('\ncreating info...') sliceThicknessStandard = true; for i = 1:numOfSlices if matRad_cfg.isOctave || verLessThan('matlab','9') - tmpDicomInfo = dicominfo(ctList{i,1}); + tmpDicomInfo = dicominfo(obj.importFiles.ct{i,1}); else - tmpDicomInfo = dicominfo(ctList{i,1},'UseDictionaryVR',true); + tmpDicomInfo = dicominfo(obj.importFiles.ct{i,1},'UseDictionaryVR',true); end % remember relevant dicom info - do not record everything as some tags % might not been defined for individual files - ctInfo(i).PixelSpacing = tmpDicomInfo.PixelSpacing; - ctInfo(i).ImagePositionPatient = tmpDicomInfo.ImagePositionPatient; - ctInfo(i).SliceThickness = tmpDicomInfo.SliceThickness; - ctInfo(i).ImageOrientationPatient = tmpDicomInfo.ImageOrientationPatient; - ctInfo(i).PatientPosition = tmpDicomInfo.PatientPosition; - ctInfo(i).Rows = tmpDicomInfo.Rows; - ctInfo(i).Columns = tmpDicomInfo.Columns; - ctInfo(i).Width = tmpDicomInfo.Columns;%tmpDicomInfo.Width; - ctInfo(i).Height = tmpDicomInfo.Rows;%tmpDicomInfo.Height; - ctInfo(i).RescaleSlope = tmpDicomInfo.RescaleSlope; - ctInfo(i).RescaleIntercept = tmpDicomInfo.RescaleIntercept; + obj.importCT.ctInfo(i).PixelSpacing = tmpDicomInfo.PixelSpacing; + obj.importCT.ctInfo(i).ImagePositionPatient = tmpDicomInfo.ImagePositionPatient; + obj.importCT.ctInfo(i).SliceThickness = obj.importFiles.resz; + obj.importCT.ctInfo(i).ImageOrientationPatient = tmpDicomInfo.ImageOrientationPatient; + obj.importCT.ctInfo(i).PatientPosition = tmpDicomInfo.PatientPosition; + obj.importCT.ctInfo(i).Rows = tmpDicomInfo.Rows; + obj.importCT.ctInfo(i).Columns = tmpDicomInfo.Columns; + obj.importCT.ctInfo(i).Width = tmpDicomInfo.Columns;%tmpDicomInfo.Width; + obj.importCT.ctInfo(i).Height = tmpDicomInfo.Rows;%tmpDicomInfo.Height; + obj.importCT.ctInfo(i).RescaleSlope = tmpDicomInfo.RescaleSlope; + obj.importCT.ctInfo(i).RescaleIntercept = tmpDicomInfo.RescaleIntercept; %Problem due to some CT files using non-standard SpacingBetweenSlices - if isempty(ctInfo(i).SliceThickness) + if isempty(obj.importCT.ctInfo(i).SliceThickness) %Print warning once if sliceThicknessStandard matRad_cfg.dispWarning('Non-standard use of SliceThickness Attribute (empty), trying to overwrite with SpacingBetweenSlices'); sliceThicknessStandard = false; end - ctInfo(i).SliceThickness = tmpDicomInfo.SpacingBetweenSlices; + obj.importCT.ctInfo(i).SliceThickness = tmpDicomInfo.SpacingBetweenSlices; end if i == 1 @@ -98,25 +96,25 @@ % adjusting sequence of slices (filenames may not be ordered propperly.... % e.g. CT1.dcm, CT10.dcm, CT100zCoordList = [ctInfo.ImagePositionPatient(1,3)]';.dcm, CT101.dcm,... -CoordList = [ctInfo.ImagePositionPatient]'; -[~, indexing] = sort(CoordList(:,3)); % get sortation from z-coordinates +CoordList = [obj.importCT.ctInfo.ImagePositionPatient]'; +[~, indexing] = unique(CoordList(:,3)); % get sortation from z-coordinates -ctList = ctList(indexing); -ctInfo = ctInfo(indexing); +obj.importFiles.ct = obj.importFiles.ct(indexing'); +obj.importCT.ctInfo = obj.importCT.ctInfo(indexing'); %% check data set for consistency -if size(unique([ctInfo.PixelSpacing]','rows'),1) > 1 +if size(unique([obj.importCT.ctInfo.PixelSpacing]','rows'),1) > 1 matRad_cfg.dispError('Different pixel size in different CT slices'); end -coordsOfFirstPixel = [ctInfo.ImagePositionPatient]; +coordsOfFirstPixel = [obj.importCT.ctInfo.ImagePositionPatient]; if numel(unique(coordsOfFirstPixel(1,:))) > 1 || numel(unique(coordsOfFirstPixel(2,:))) > 1 matRad_cfg.dispError('Ct slices are not aligned'); end if sum(diff(coordsOfFirstPixel(3,:))<=0) > 0 matRad_cfg.dispError('Ct slices not monotonically increasing'); end -if numel(unique([ctInfo.Rows])) > 1 || numel(unique([ctInfo.Columns])) > 1 +if numel(unique([obj.importCT.ctInfo.Rows])) > 1 || numel(unique([obj.importCT.ctInfo.Columns])) > 1 matRad_cfg.dispError('Ct slice sizes inconsistent'); end @@ -135,7 +133,7 @@ % FFP Feet First-Prone (supported) % FFS Feet First-Supine (supported) -if isempty(regexp(ctInfo(1).PatientPosition,{'S','P'}, 'once')) +if isempty(regexp(obj.importCT.ctInfo(1).PatientPosition,{'S','P'}, 'once')) matRad_cfg.dispError(['This Patient Position is not supported by matRad.'... ' As of now only ''HFS'' (Head First-Supine), ''FFS'''... ' (Feet First-Supine), '... @@ -145,9 +143,9 @@ %% creation of ct-cube matRad_cfg.dispInfo('reading slices...') -origCt = zeros(ctInfo(1).Height, ctInfo(1).Width, numOfSlices); +origCt = zeros(obj.importCT.ctInfo(1).Height, obj.importCT.ctInfo(1).Width, numOfSlices); for i = 1:numOfSlices - currentFilename = ctList{i}; + currentFilename = obj.importFiles.ct{i}; if matRad_cfg.isOctave currentImage = dicomread(currentFilename); map = []; @@ -157,7 +155,7 @@ origCt(:,:,i) = currentImage(:,:); % creation of the ct cube % draw current ct-slice - if visBool + if obj.visBool if ~isempty(map) image(ind2rgb(uint8(63*currentImage/max(currentImage(:))),map)); xlabel('x [voxelnumber]') @@ -186,8 +184,8 @@ % The x- & y-direction in lps-coordinates are specified in: % ImageOrientationPatient -xDir = ctInfo(1).ImageOrientationPatient(1:3); % lps: [1;0;0] -yDir = ctInfo(1).ImageOrientationPatient(4:6); % lps: [0;1;0] +xDir = obj.importCT.ctInfo(1).ImageOrientationPatient(1:3); % lps: [1;0;0] +yDir = obj.importCT.ctInfo(1).ImageOrientationPatient(4:6); % lps: [0;1;0] nonStandardDirection = false; % correct x- & y-direction @@ -219,48 +217,49 @@ %% interpolate cube matRad_cfg.dispInfo('\nInterpolating CT cube...'); -if exist('grid','var') - ct = matRad_interpDicomCtCube(origCt, ctInfo, resolution, grid); +if ~isempty(obj.doseGrid) + obj.ct = matRad_interpDicomCtCube(origCt, obj.importCT.ctInfo, obj.importCT.resolution, obj.doseGrid); else - ct = matRad_interpDicomCtCube(origCt, ctInfo, resolution); + obj.ct = matRad_interpDicomCtCube(origCt, obj.importCT.ctInfo, obj.importCT.resolution); end matRad_cfg.dispInfo('finished!\n'); %% remember some parameters of original dicom -ct.dicomInfo.PixelSpacing = ctInfo(1).PixelSpacing; - tmp = [ctInfo.ImagePositionPatient]; -ct.dicomInfo.SlicePositions = tmp(3,:); -ct.dicomInfo.SliceThickness = [ctInfo.SliceThickness]; -ct.dicomInfo.ImagePositionPatient = ctInfo(1).ImagePositionPatient; -ct.dicomInfo.ImageOrientationPatient = ctInfo(1).ImageOrientationPatient; -ct.dicomInfo.PatientPosition = ctInfo(1).PatientPosition; -ct.dicomInfo.Width = ctInfo(1).Width; -ct.dicomInfo.Height = ctInfo(1).Height; -ct.dicomInfo.RescaleSlope = ctInfo(1).RescaleSlope; -ct.dicomInfo.RescaleIntercept = ctInfo(1).RescaleIntercept; +tmp = [obj.importCT.ctInfo.ImagePositionPatient]; + +obj.ct.dicomInfo.PixelSpacing = obj.importCT.ctInfo(1).PixelSpacing; +obj.ct.dicomInfo.SlicePositions = tmp(3,:); +obj.ct.dicomInfo.SliceThickness = str2double(obj.importCT.ctInfo(1).SliceThickness); +obj.ct.dicomInfo.ImagePositionPatient = obj.importCT.ctInfo(1).ImagePositionPatient; +obj.ct.dicomInfo.ImageOrientationPatient = obj.importCT.ctInfo(1).ImageOrientationPatient; +obj.ct.dicomInfo.PatientPosition = obj.importCT.ctInfo(1).PatientPosition; +obj.ct.dicomInfo.Width = obj.importCT.ctInfo(1).Width; +obj.ct.dicomInfo.Height = obj.importCT.ctInfo(1).Height; +obj.ct.dicomInfo.RescaleSlope = obj.importCT.ctInfo(1).RescaleSlope; +obj.ct.dicomInfo.RescaleIntercept = obj.importCT.ctInfo(1).RescaleIntercept; if isfield(completeDicom, 'Manufacturer') -ct.dicomInfo.Manufacturer = completeDicom.Manufacturer; +obj.ct.dicomInfo.Manufacturer = completeDicom.Manufacturer; end if isfield(completeDicom, 'ManufacturerModelName') -ct.dicomInfo.ManufacturerModelName = completeDicom.ManufacturerModelName; +obj.ct.dicomInfo.ManufacturerModelName = completeDicom.ManufacturerModelName; end if isfield(completeDicom, 'ConvolutionKernel') -ct.dicomInfo.ConvolutionKernel = completeDicom.ConvolutionKernel; +obj.ct.dicomInfo.ConvolutionKernel = completeDicom.ConvolutionKernel; end % store patientName only if user wants to -if isfield(completeDicom,'PatientName') && dicomMetaBool == true - ct.dicomInfo.PatientName = completeDicom.PatientName; +if isfield(completeDicom,'PatientName') && obj.dicomMetaBool == true + obj.ct.dicomInfo.PatientName = completeDicom.PatientName; end -if dicomMetaBool == true - ct.dicomMeta = completeDicom; +if obj.dicomMetaBool == true + obj.ct.dicomMeta = completeDicom; end -ct.timeStamp = datestr(clock); +obj.ct.timeStamp = datestr(clock); % convert to Hounsfield units matRad_cfg.dispInfo('\nconversion of ct-Cube to Hounsfield units...'); -ct = matRad_calcHU(ct); +obj = matRad_calcHU(obj); matRad_cfg.dispInfo('finished!\n'); end diff --git a/matRad/dicom/matRad_importDicomRTDose.m b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomRTDose.m similarity index 80% rename from matRad/dicom/matRad_importDicomRTDose.m rename to matRad/dicom/@matRad_DicomImporter/matRad_importDicomRTDose.m index 16c4dee35..ff5f531e3 100644 --- a/matRad/dicom/matRad_importDicomRTDose.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomRTDose.m @@ -1,14 +1,13 @@ -function [resultGUI] = matRad_importDicomRTDose(ct, rtDoseFiles, pln) +function obj = matRad_importDicomRTDose(obj) % matRad function to import dicom RTDOSE data % % call -% resultGUI = matRad_importDicomRTDose(ct, rtDoseFiles) -% resultGUI = matRad_importDicomRTDose(ct, rtDoseFiles, pln) +% obj = matRad_importDicomRTDose(obj) % -% input -% ct: ct imported by the matRad_importDicomCt function -% rtDoseFiles: cell array of RTDOSE Dicom files -% pln: (optional) matRad pln struct +% input +% obj.ct: ct imported by the matRad_importDicomCt function +% obj.importFiles.rtdose: cell array of RTDOSE Dicom files +% obj.pln: (optional) matRad pln struct % % output % resultGUI: matRad resultGUI struct with different beams. Note that @@ -35,12 +34,13 @@ %% import and interpolate dose files % number of dosefiles -numDoseFiles = size(rtDoseFiles,1); +numDoseFiles = size(obj.importFiles.rtdose,1); for i = 1 : numDoseFiles - currDose = rtDoseFiles(i,:); + obj.importRTDose.currDose = obj.importFiles.rtdose(i,:); itemName = strcat('Item_',num2str(i)); - dose.(itemName) = matRad_interpDicomDoseCube( ct, currDose); + obj = matRad_interpDicomDoseCube(obj); + dose.(itemName) = obj.importRTDose.dose; end %% put dose information and dose meta information to resultGUI @@ -48,7 +48,7 @@ countBeamNumberRBExDose = 1; countBeamNumberOther = 1; -resultGUI = struct(); +obj.resultGUI = struct(); for i = 1 : numDoseFiles itemName = strcat('Item_',num2str(i)); @@ -70,8 +70,8 @@ %If given as plan and not per fraction if strcmpi(doseSumHelper,'PLAN') || strcmpi(doseSumHelper,'BEAM') - if exist('pln','var') - dose.(itemName).cube = dose.(itemName).cube / pln.numOfFractions; + if ~isempty(obj.pln) + dose.(itemName).cube = dose.(itemName).cube / obj.pln.numOfFractions; else matRad_cfg.dispWarning('DICOM dose given as PLAN, but no pln struct available to compute fraction dose! Assuming 1 fraction!'); end @@ -108,11 +108,11 @@ resultName = strcat(doseTypeHelper,instanceSuffix,beamSuffix); - if isfield(resultGUI,resultName) + if isfield(obj.resultGUI,resultName) count = 1; addSuffix = ['_' num2str(count)]; resultNameNew = [resultName addSuffix]; - while isfield(resultGUI,resultNameNew) + while isfield(obj.resultGUI,resultNameNew) count = count + 1; addSuffix = ['_' num2str(count)]; resultNameNew = [resultName addSuffix]; @@ -122,10 +122,10 @@ resultName = resultNameNew; end - resultGUI.(resultName) = dose.(itemName).cube; - resultGUI.doseMetaInfo.(resultName) = dose.(itemName).dicomInfo; + obj.resultGUI.(resultName) = dose.(itemName).cube; + obj.resultGUI.doseMetaInfo.(resultName) = dose.(itemName).dicomInfo; end % save timeStamp -resultGUI.doseMetaInfo.timeStamp = datestr(clock); +obj.resultGUI.doseMetaInfo.timeStamp = datestr(clock); end diff --git a/matRad/dicom/matRad_importDicomRTPlan.m b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomRTPlan.m similarity index 58% rename from matRad/dicom/matRad_importDicomRTPlan.m rename to matRad/dicom/@matRad_DicomImporter/matRad_importDicomRTPlan.m index e65825031..76594ca1e 100644 --- a/matRad/dicom/matRad_importDicomRTPlan.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomRTPlan.m @@ -1,17 +1,17 @@ -function pln = matRad_importDicomRTPlan(ct, rtPlanFiles, dicomMetaBool) +function obj = matRad_importDicomRTPlan(obj) % matRad function to import dicom RTPLAN data % % call -% pln = matRad_importDicomRTPlan(ct, rtPlanFiles, dicomMetaBool) +% obj = matRad_importDicomRTPlan(obj) % % input -% ct: ct imported by the matRad_importDicomCt function -% rtPlanFiles: list of RTPlan Dicom files -% dicomMetaBool: import whole dicom information +% ct: ct imported by the matRad_importDicomCt function +% importFiles.rtplan: list of RTPlan Dicom files +% dicomMetaBool: import whole dicom information % % output -% pln: matRad pln struct with meta information. Note that -% bixelWidth is determined via the importSteering function. +% pln: matRad pln struct with meta information. Note that +% bixelWidth is determined via the importSteering function. % % References % - @@ -34,15 +34,15 @@ %% load plan file % check size of RT Plan -if size(rtPlanFiles,1) ~= 1 +if size(obj.importFiles.rtplan,1) ~= 1 errordlg('Too few or to many RTPlan files') end % read information out of the RT file if matRad_cfg.isOctave || verLessThan('matlab','9') - planInfo = dicominfo(rtPlanFiles{1}); + planInfo = dicominfo(obj.importFiles.rtplan{1}); else - planInfo = dicominfo(rtPlanFiles{1},'UseDictionaryVR',true); + planInfo = dicominfo(obj.importFiles.rtplan{1},'UseDictionaryVR',true); end % check which type of Radiation is used @@ -88,10 +88,13 @@ gantryAngles{i} = currBeamSeq.(ControlParam).Item_1.GantryAngle; PatientSupportAngle{i} = currBeamSeq.(ControlParam).Item_1.PatientSupportAngle; isoCenter(i,:) = currBeamSeq.(ControlParam).Item_1.IsocenterPosition'; + if ~ismember(isoCenter(i,1), obj.ct.x) || ~ismember(isoCenter(i,2), obj.ct.y) || ~ismember(isoCenter(i,3), obj.ct.z) + isoCenter(i,:) = matRad_getIsoCenter(obj.cst, obj.ct); + end end % transform iso. At the moment just this way for HFS -if ct.dicomInfo.ImageOrientationPatient ~= [1;0;0;0;1;0] +if obj.ct.dicomInfo.ImageOrientationPatient ~= [1;0;0;0;1;0] matRad_cfg.dispError('This Orientation is not yet supported.'); end @@ -121,50 +124,66 @@ if strcmp(radiationMode, 'photons') fractionSequence = planInfo.FractionGroupSequence.Item_1; - pln.propStf.collimation = matRad_importFieldShapes(BeamSequence,fractionSequence); + obj.pln.propStf.collimation = matRad_importFieldShapes(BeamSequence,fractionSequence); end %% write parameters found to pln variable -pln.radiationMode = radiationMode; % either photons / protons / carbon -pln.numOfFractions = planInfo.FractionGroupSequence.Item_1.NumberOfFractionsPlanned; +obj.pln.radiationMode = radiationMode; % either photons / protons / carbon +obj.pln.numOfFractions = planInfo.FractionGroupSequence.Item_1.NumberOfFractionsPlanned; % set handling of multiple scenarios -> default: only nominal -pln.multScen = matRad_multScen(ct,'nomScen'); -pln.machine = BeamSequence.Item_1.TreatmentMachineName; - +obj.pln.multScen = matRad_multScen(obj.ct,'nomScen'); +if isfield(BeamSequence.Item_1, 'TreatmentMachineName') + obj.pln.machine = BeamSequence.Item_1.TreatmentMachineName; +else + obj.pln.machine = 'Generic'; +end % set bio model parameters (default physical opt, no bio model) -pln.bioParam = matRad_bioModel(pln.radiationMode,'physicalDose','none'); +obj.pln.bioParam = matRad_bioModel(obj.pln.radiationMode,'physicalDose','none'); % set properties for steering -pln.propStf.isoCenter = isoCenter; -pln.propStf.bixelWidth = NaN; % [mm] / also corresponds to lateral spot spacing for particles -pln.propStf.gantryAngles = [gantryAngles{1:length(BeamSeqNames)}]; -pln.propStf.couchAngles = [PatientSupportAngle{1:length(BeamSeqNames)}]; % [??] -pln.propStf.numOfBeams = length(BeamSeqNames); +obj.pln.propStf.isoCenter = isoCenter; +obj.pln.propStf.bixelWidth = NaN; % [mm] / also corresponds to lateral spot spacing for particles +obj.pln.propStf.gantryAngles = [gantryAngles{1:length(BeamSeqNames)}]; +obj.pln.propStf.couchAngles = [PatientSupportAngle{1:length(BeamSeqNames)}]; % [??] +obj.pln.propStf.numOfBeams = length(BeamSeqNames); +numOfVoxels = 1; +for i = 1:length(obj.ct.cubeDim) + numOfVoxels = numOfVoxels*obj.ct.cubeDim(i); +end +obj.pln.numOfVoxels = numOfVoxels; +obj.pln.VoxelDimentions = obj.ct.cubeDim; + +%if there is not special doseGrid for rtdose +if ~obj.importFiles.useDoseGrid && isfield(obj.importFiles,'rtdose') + obj.pln.propDoseCalc.doseGrid.resolution.x = obj.ct.resolution.x; + obj.pln.propDoseCalc.doseGrid.resolution.y = obj.ct.resolution.y; + obj.pln.propDoseCalc.doseGrid.resolution.z = obj.ct.resolution.z; +end % turn off sequerncing an DAO by default -pln.propOpt.runSequencing = false; % 1/true: run sequencing, 0/false: don't / will be ignored for particles and also triggered by runDAO below -pln.propOpt.runDAO = false; % 1/true: run DAO, 0/false: don't / will be ignored for particles +obj.pln.propOpt.runSequencing = false; % 1/true: run sequencing, 0/false: don't / will be ignored for particles and also triggered by runDAO below +obj.pln.propOpt.runDAO = false; % 1/true: run DAO, 0/false: don't / will be ignored for particles % if we imported field shapes then let's trigger field based dose calc by % setting the bixelWidth to 'field' -if isfield(pln.propStf,'collimation') - pln.propStf.bixelWidth = 'field'; +if isfield(obj.pln.propStf,'collimation') + obj.pln.propStf.bixelWidth = 'field'; end % timestamp -pln.DicomInfo.timeStamp = datestr(clock); +obj.pln.DicomInfo.timeStamp = datestr(clock); try - pln.DicomInfo.SOPClassUID = planInfo.SOPClassUID; - pln.DicomInfo.SOPInstanceUID = planInfo.SOPInstanceUID; - pln.DicomInfo.ReferencedDoseSequence = planInfo.ReferencedDoseSequence; + obj.pln.DicomInfo.SOPClassUID = planInfo.SOPClassUID; + obj.pln.DicomInfo.SOPInstanceUID = planInfo.SOPInstanceUID; + obj.pln.DicomInfo.ReferencedDoseSequence = planInfo.ReferencedDoseSequence; catch end % safe entire dicomInfo -if dicomMetaBool == true - pln.DicomInfo.Meta = planInfo; +if obj.dicomMetaBool == true + obj.pln.DicomInfo.Meta = planInfo; end end diff --git a/matRad/dicom/matRad_importDicomRtss.m b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomRtss.m similarity index 75% rename from matRad/dicom/matRad_importDicomRtss.m rename to matRad/dicom/@matRad_DicomImporter/matRad_importDicomRtss.m index aaa7652a4..d7cb25b8f 100644 --- a/matRad/dicom/matRad_importDicomRtss.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomRtss.m @@ -1,10 +1,9 @@ -function structures = matRad_importDicomRtss(filename,dicomInfo,visBool) +function obj = matRad_importDicomRtss(obj) % matRad function to read the data of the selected dicomRT structure set file % into a matlab struct % % call -% structures = matRad_importDicomRtss(filename,dicomInfo) -% structures = matRad_importDicomRtss(filename,dicomInfo,visBool) +% obj = matRad_importDicomRtss(obj) % % input % filename: name of the rtss file @@ -37,7 +36,7 @@ matRad_cfg.dispInfo('\nReading structures...'); if nargin < 3 - visBool = 0; + obj.visBool = 0; end matRad_checkEnvDicomRequirements(matRad_cfg.env); @@ -46,10 +45,10 @@ % read dicom info (this includes already all data for the rtss) if matRad_cfg.isOctave || verLessThan('matlab','9') - structInfo = dicominfo(filename); + structInfo = dicominfo(obj.importFiles.rtss{1}); else % apply 'UseVRHeuristic' option when available to use a to help read certain % noncompliant files which switch value representation (VR) modes incorrectly - structInfo = dicominfo(filename,'UseVRHeuristic',false,'UseDictionaryVR',true); + structInfo = dicominfo(obj.importFiles.rtss{1},'UseVRHeuristic',false,'UseDictionaryVR',true); end % list the defined structures @@ -78,14 +77,14 @@ break; end end - structures(i).structName = regexprep(... % replace nonregular characters by whitespace + obj.importRtss.structures(i).structName = regexprep(... % replace nonregular characters by whitespace structInfo.StructureSetROISequence.(listOfDefStructs{j}).ROIName,... '[^a-zA-Z0-9]',' '); - structures(i).structNumber = structInfo.ROIContourSequence.(... + obj.importRtss.structures(i).structNumber = structInfo.ROIContourSequence.(... listOfContStructs{i}).ReferencedROINumber; if isfield(structInfo.ROIContourSequence.(listOfContStructs{i}),'ROIDisplayColor') - structures(i).structColor = structInfo.ROIContourSequence.(... + obj.importRtss.structures(i).structColor = structInfo.ROIContourSequence.(... listOfContStructs{i}).ROIDisplayColor; end @@ -96,11 +95,11 @@ listOfSlices = fieldnames(structInfo.ROIContourSequence.(... listOfContStructs{i}).ContourSequence); else - matRad_cfg.dispWarning(['Contour ' structures(i).structName ' is empty']) + matRad_cfg.dispWarning(['Contour ' obj.importRtss.structures(i).structName ' is empty']) continue; end else - matRad_cfg.dispWarning(['Contour ' structures(i).structName ' is empty']) + matRad_cfg.dispWarning(['Contour ' obj.importRtss.structures(i).structName ' is empty']) continue; end @@ -127,10 +126,10 @@ end % sanity check 2 - if unique(structZ) > max(dicomInfo.SlicePositions) || unique(structZ) < min(dicomInfo.SlicePositions) - matRad_cfg.dispWarning(['Omitting contour data for ' structures(i).structName ' at slice position ' num2str(unique(structZ)) 'mm - no ct data available.\n']); + if unique(structZ) > max(obj.ct.dicomInfo.SlicePositions) || unique(structZ) < min(obj.ct.dicomInfo.SlicePositions) + matRad_cfg.dispWarning(['Omitting contour data for ' obj.importRtss.structures(i).structName ' at slice position ' num2str(unique(structZ)) 'mm - no ct data available.\n']); else - structures(i).item(j).points = [structX, structY, structZ]; + obj.importRtss.structures(i).item(j).points = [structX, structY, structZ]; end end @@ -139,13 +138,13 @@ %% visualization % show all structure points in a single plot -if visBool +if obj.visBool figure; hold on - for i = 1:numel(structures) - plot3(structures(i).points(:,1),structures(i).points(:,2),... - structures(i).points(:,3),'-',... - 'Color',structures(i).structColor ./ 255,'Displayname',structures(i).structName); + for i = 1:numel(obj.importRtss.structures) + plot3(obj.importRtss.structures(i).points(:,1),obj.importRtss.structures(i).points(:,2),... + obj.importRtss.structures(i).points(:,3),'-',... + 'Color',obj.importRtss.structures(i).structColor ./ 255,'Displayname',obj.importRtss.structures(i).structName); end legend('show') end diff --git a/matRad/dicom/matRad_importDicomSteeringParticles.m b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringParticles.m similarity index 93% rename from matRad/dicom/matRad_importDicomSteeringParticles.m rename to matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringParticles.m index 9c8597701..3b1e21166 100644 --- a/matRad/dicom/matRad_importDicomSteeringParticles.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringParticles.m @@ -1,13 +1,13 @@ -function [stf, pln] = matRad_importDicomSteeringParticles(ct, pln, rtPlanFile) +function obj = matRad_importDicomSteeringParticles(obj) % matRad function to import a matRad stf struct from dicom RTPLAN data % % call -% [stf, pln] = matRad_importDicomSteeringParticles(ct, pln, rtPlanFile) +% obj = matRad_importDicomSteeringParticles(obj) % % input % ct: ct imported by the matRad_importDicomCt function % pln: matRad pln struct with meta information -% rtPlanFile: name of RTPLAN DICOM file +% importFiles.rtplan: name of RTPLAN DICOM file % % output % stf matRad stf struct @@ -42,27 +42,27 @@ matRad_checkEnvDicomRequirements(matRad_cfg.env); dlgBaseDataText = ['Import steering information from DICOM Plan.','Choose corresponding matRad base data for ', ... - pln.radiationMode, '.']; + obj.pln.radiationMode, '.']; % messagebox only necessary for non windows users if ~ispc - uiwait(helpdlg(dlgBaseDataText,['DICOM import - ', pln.radiationMode, ' base data' ])); + uiwait(helpdlg(dlgBaseDataText,['DICOM import - ', obj.pln.radiationMode, ' base data' ])); end [fileName,pathName] = uigetfile([matRad_cfg.matRadSrcRoot filesep 'basedata' filesep '*.mat'], dlgBaseDataText); load([pathName filesep fileName]); ix = find(fileName == '_'); -pln.machine = fileName(ix(1)+1:end-4); +obj.pln.machine = fileName(ix(1)+1:end-4); % RT Plan consists only on meta information if matRad_cfg.isOctave || verLessThan('matlab','9') - rtPlanInfo = dicominfo(rtPlanFile{1}); + rtPlanInfo = dicominfo(obj.importFiles.rtplan{1}); else - rtPlanInfo = dicominfo(rtPlanFile{1},'UseDictionaryVR',true); + rtPlanInfo = dicominfo(obj.importFiles.rtplan{1},'UseDictionaryVR',true); end BeamSeq = rtPlanInfo.IonBeamSequence; BeamSeqNames = fieldnames(BeamSeq); % Number of Beams from plan -numOfBeamsPlan = length(pln.propStf.gantryAngles); +numOfBeamsPlan = length(obj.pln.propStf.gantryAngles); % use only the treatment beams for i = 1:length(BeamSeqNames) @@ -121,16 +121,16 @@ for i = 1:length(BeamSeqNames) currBeamSeq = BeamSeq.(BeamSeqNames{i}); ControlPointSeq = currBeamSeq.IonControlPointSequence; - stf(i).gantryAngle = pln.propStf.gantryAngles(i); - stf(i).couchAngle = pln.propStf.couchAngles(i); - stf(i).bixelWidth = pln.propStf.bixelWidth; - stf(i).radiationMode = pln.radiationMode; + stf(i).gantryAngle = obj.pln.propStf.gantryAngles(i); + stf(i).couchAngle = obj.pln.propStf.couchAngles(i); + stf(i).bixelWidth = obj.pln.propStf.bixelWidth; + stf(i).radiationMode = obj.pln.radiationMode; % there might be several SAD's, e.g. compensator? stf(i).SAD_x = currBeamSeq.VirtualSourceAxisDistances(1); stf(i).SAD_y = currBeamSeq.VirtualSourceAxisDistances(1); %stf(i).SAD = machine.meta.SAD; %we write the SAD later when we check machine match %stf(i).sourcePoint_bev = [0 -stf(i).SAD 0]; - stf(i).isoCenter = pln.propStf.isoCenter(i,:); + stf(i).isoCenter = obj.pln.propStf.isoCenter(i,:); % now loop over ControlPointSequences ControlPointSeqNames = fieldnames(ControlPointSeq); @@ -345,9 +345,9 @@ end if any(isnan([stf(:).bixelWidth])) || numel(unique([stf(:).bixelWidth])) > 1 - pln.propStf.bixelWidth = NaN; + obj.pln.propStf.bixelWidth = NaN; else - pln.propStf.bixelWidth = stf(1).bixelWidth; + obj.pln.propStf.bixelWidth = stf(1).bixelWidth; end end diff --git a/matRad/dicom/matRad_importDicomSteeringPhotons.m b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringPhotons.m similarity index 56% rename from matRad/dicom/matRad_importDicomSteeringPhotons.m rename to matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringPhotons.m index f937eabe2..57757f8b7 100644 --- a/matRad/dicom/matRad_importDicomSteeringPhotons.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringPhotons.m @@ -1,8 +1,8 @@ -function [stf, pln] = matRad_importDicomSteeringPhotons(pln) +function obj = matRad_importDicomSteeringPhotons(obj) % matRad function to import a matRad stf struct from dicom RTPLAN data % % call -% [stf, pln] = matRad_importDicomSteeringPhotons(pln) +% obj = matRad_importDicomSteeringPhotons(obj) % % input % pln: matRad pln struct with meta information (collimation @@ -27,61 +27,61 @@ % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -stf = struct; -if ~isfield(pln.propStf.collimation,'Fields') +obj.stf = struct; +if ~isfield(obj.pln.propStf.collimation,'Fields') return end % get fields possessing a field weight vector greater than 0 -Fields = pln.propStf.collimation.Fields([pln.propStf.collimation.Fields(:).Weight] > 0); +Fields = obj.pln.propStf.collimation.Fields([obj.pln.propStf.collimation.Fields(:).Weight] > 0); [UniqueComb,ia,ib] = unique( vertcat([Fields(:).GantryAngle], [Fields(:).CouchAngle])','rows'); % return corret angles to pln, because some angle derivations might be % only in the control point sequences -pln.propStf.gantryAngles = UniqueComb(:,1)'; -pln.propStf.couchAngles = UniqueComb(:,2)'; +obj.pln.propStf.gantryAngles = UniqueComb(:,1)'; +obj.pln.propStf.couchAngles = UniqueComb(:,2)'; -stf = struct; +obj.stf = struct; % loop over all fields for i = 1:size(UniqueComb,1) % set necessary steering information - stf(i).gantryAngle = UniqueComb(i,1); - stf(i).couchAngle = UniqueComb(i,2); - stf(i).isoCenter = pln.propStf.isoCenter(i,:); + obj.stf(i).gantryAngle = UniqueComb(i,1); + obj.stf(i).couchAngle = UniqueComb(i,2); + obj.stf(i).isoCenter = obj.pln.propStf.isoCenter(i,:); % bixelWidth = 'field' as keyword for whole field dose calc - stf(i).bixelWidth = 'field'; - stf(i).radiationMode = 'photons'; + obj.stf(i).bixelWidth = 'field'; + obj.stf(i).radiationMode = 'photons'; % only one bixel per ray and one ray for photon dose calc based on % fields - stf(i).numOfBixelsPerRay = 1; - stf(i).numOfRays = 1; - stf(i).totalNumOfBixels = stf(i).numOfRays; - stf(i).SAD = Fields(ia(i)).SAD; - stf(i).sourcePoint_bev = [0 -stf(i).SAD 0]; + obj.stf(i).numOfBixelsPerRay = 1; + obj.stf(i).numOfRays = 1; + obj.stf(i).totalNumOfBixels = obj.stf(i).numOfRays; + obj.stf(i).SAD = Fields(ia(i)).SAD; + obj.stf(i).sourcePoint_bev = [0 -obj.stf(i).SAD 0]; % coordinate transformation with rotation matrix. % use transpose matrix because we are working with row vectors - rotMat_vectors_T = transpose(matRad_getRotationMatrix(stf(i).gantryAngle,stf(i).couchAngle)); + rotMat_vectors_T = transpose(matRad_getRotationMatrix(obj.stf(i).gantryAngle,obj.stf(i).couchAngle)); % Rotated Source point (1st gantry, 2nd couch) - stf(i).sourcePoint = stf(i).sourcePoint_bev*rotMat_vectors_T; + obj.stf(i).sourcePoint = obj.stf(i).sourcePoint_bev*rotMat_vectors_T; % only one ray in center position - stf(i).ray.rayPos_bev = [0 0 0]; - stf(i).ray.rayPos = stf(i).ray.rayPos_bev*rotMat_vectors_T; + obj.stf(i).ray.rayPos_bev = [0 0 0]; + obj.stf(i).ray.rayPos = obj.stf(i).ray.rayPos_bev*rotMat_vectors_T; % target point is for ray in center position at - stf(i).ray.targetPoint_bev = [0 stf(i).SAD 0]; - stf(i).ray.targetPoint = stf(i).ray.targetPoint_bev*rotMat_vectors_T; + obj.stf(i).ray.targetPoint_bev = [0 obj.stf(i).SAD 0]; + obj.stf(i).ray.targetPoint = obj.stf(i).ray.targetPoint_bev*rotMat_vectors_T; % set weight for output field - stf(i).ray.weight = 1; % weighting incorporated into primary fluence --> finalShape - %stf(i).ray.SSD = Fields(ia(i)).SSD; - stf(i).ray.energy = Fields(ia(i)).Energy; + obj.stf(i).ray.weight = 1; % weighting incorporated into primary fluence --> finalShape + %obj.stf(i).ray.SSD = Fields(ia(i)).SSD; + obj.stf(i).ray.energy = Fields(ia(i)).Energy; ix = (ib == i); currFieldSeq = Fields(ix); @@ -91,7 +91,7 @@ for j = 1:sum(ix) finalShape = finalShape + currFieldSeq(j).Weight * currFieldSeq(j).Shape; end - stf(i).ray.shape = finalShape; + obj.stf(i).ray.shape = finalShape; end diff --git a/matRad/dicom/matRad_interpDicomDoseCube.m b/matRad/dicom/@matRad_DicomImporter/matRad_interpDicomDoseCube.m similarity index 51% rename from matRad/dicom/matRad_interpDicomDoseCube.m rename to matRad/dicom/@matRad_DicomImporter/matRad_interpDicomDoseCube.m index 5937515bd..c53ce9255 100644 --- a/matRad/dicom/matRad_interpDicomDoseCube.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_interpDicomDoseCube.m @@ -1,8 +1,8 @@ -function [ dose ] = matRad_interpDicomDoseCube( ct, currDose ) +function obj = matRad_interpDicomDoseCube(obj) % matRad function to interpolate a given Dicom Dose Cube dicom RTDOSE data % % call -% [ dose ] = matRad_interpDicomDoseCube( ct, currDose ) +% obj = matRad_interpDicomDoseCube(obj) % % input % ct: ct imported by the matRad_importDicomCt function @@ -21,7 +21,7 @@ % % This file is part of the matRad project. It is subject to the license % terms in the LICENSE file found in the top-level directory of this -% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part +% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part % of the matRad project, including this file, may be copied, modified, % propagated, or distributed except according to the terms contained in the % LICENSE file. @@ -33,8 +33,7 @@ matRad_cfg = MatRad_Config.instance(); matRad_checkEnvDicomRequirements(matRad_cfg.env); - -dosefile = currDose{1}; +dosefile = obj.importRTDose.currDose{1}; if matRad_cfg.isOctave || verLessThan('matlab','9') doseInfo = dicominfo(dosefile); @@ -44,66 +43,65 @@ % read the dosefile itself dosedata = dicomread(dosefile); -dose.cube = double(dosedata); -% dose.cube = single(dosedata); +obj.importRTDose.dose.cube = double(dosedata); +% importRTDose.dose.cube = single(dosedata); % give it an internal name -dose.internalName = currDose{12}; + % obj.dose.internalName = obj.currDose{12};%????? % read out the resolution -dose.resolution.x = doseInfo.PixelSpacing(1); -dose.resolution.y = doseInfo.PixelSpacing(2); -dose.resolution.z = doseInfo.SliceThickness; +obj.importRTDose.dose.resolution.x = doseInfo.PixelSpacing(1); +obj.importRTDose.dose.resolution.y = doseInfo.PixelSpacing(2); +obj.importRTDose.dose.resolution.z = obj.importFiles.resz; % target resolution is ct.resolution -target_resolution = ct.resolution; +target_resolution = obj.ct.resolution; % convert dosedata to 3-D cube -dose.cube = squeeze(dose.cube(:,:,1,:)); +obj.importRTDose.dose.cube = squeeze(obj.importRTDose.dose.cube(:,:,1,:)); % ct resolution is target resolution, now convert to new cube; % generating grid vectors x = doseInfo.ImagePositionPatient(1) + doseInfo.ImageOrientationPatient(1) * ... - doseInfo.PixelSpacing(1) * double([0:doseInfo.Columns - 1]); + doseInfo.PixelSpacing(1) * double(0:doseInfo.Columns - 1); y = doseInfo.ImagePositionPatient(2) + doseInfo.ImageOrientationPatient(5) * ... - doseInfo.PixelSpacing(2) * double([0:doseInfo.Rows - 1]); -z = [doseInfo.ImagePositionPatient(3) + doseInfo.GridFrameOffsetVector]; + doseInfo.PixelSpacing(2) * double(0:doseInfo.Rows - 1); +z = doseInfo.ImagePositionPatient(3) + doseInfo.GridFrameOffsetVector; % set up grid matrices - implicit dimension permuation (X Y Z-> Y X Z) % Matlab represents internally in the first matrix dimension the % ordinate axis and in the second matrix dimension the abscissas axis [ X, Y, Z] = meshgrid(x,y,z); -[Xq, Yq, Zq] = meshgrid(ct.x,ct.y,ct.z); +[Xq, Yq, Zq] = meshgrid(obj.ct.x,obj.ct.y,obj.ct.z); % get GridScalingFactor gridScale = double(doseInfo.DoseGridScaling); -% rescale dose.cube -dose.cube = gridScale * dose.cube; +% rescale importRTDose.dose.cube +obj.importRTDose.dose.cube = gridScale * obj.importRTDose.dose.cube; % interpolation to ct grid - cube is now stored in Y X Z -dose.cube = interp3(X,Y,Z,dose.cube,Xq,Yq,Zq,'linear',0); +obj.importRTDose.dose.cube = interp3(X,Y,Z,obj.importRTDose.dose.cube,Xq,Yq,Zq,'linear',0); % write new parameters -dose.resolution = ct.resolution; -dose.x = ct.x; -dose.y = ct.y; -dose.z = ct.z; +obj.importRTDose.dose.resolution = obj.ct.resolution; +obj.importRTDose.dose.x = obj.ct.x; +obj.importRTDose.dose.y = obj.ct.y; +obj.importRTDose.dose.z = obj.ct.z; % write Dicom-Tags -dose.dicomInfo.PixelSpacing = [target_resolution.x; ... +obj.importRTDose.dose.dicomInfo.PixelSpacing = [target_resolution.x; ... target_resolution.y]; -dose.dicomInfo.ImagePositionPatient = [min(dose.x); min(dose.y); min(dose.z)]; -dose.dicomInfo.SliceThickness = target_resolution.z; -dose.dicomInfo.ImageOrientationPatient = doseInfo.ImageOrientationPatient; -dose.dicomInfo.DoseType = doseInfo.DoseType; -dose.dicomInfo.DoseSummationType = doseInfo.DoseSummationType; -%dose.dicomInfo.InstanceNumber = doseInfo.InstanceNumber; %Not +obj.importRTDose.dose.dicomInfo.ImagePositionPatient = [min(obj.importRTDose.dose.x); min(obj.importRTDose.dose.y); min(obj.importRTDose.dose.z)]; +obj.importRTDose.dose.dicomInfo.SliceThickness = target_resolution.z; +obj.importRTDose.dose.dicomInfo.ImageOrientationPatient = doseInfo.ImageOrientationPatient; +obj.importRTDose.dose.dicomInfo.DoseType = doseInfo.DoseType; +obj.importRTDose.dose.dicomInfo.DoseSummationType = doseInfo.DoseSummationType; +%importRTDose.dose.dicomInfo.InstanceNumber = doseInfo.InstanceNumber; %Not %always given -dose.dicomInfo.SOPClassUID = doseInfo.SOPClassUID; -dose.dicomInfo.SOPInstanceUID = doseInfo.SOPInstanceUID; -if isfield(doseInfo,'ReferencedRTPlanSequence') - dose.dicomInfo.ReferencedRTPlanSequence = doseInfo.ReferencedRTPlanSequence; -end +obj.importRTDose.dose.dicomInfo.SOPClassUID = doseInfo.SOPClassUID; +obj.importRTDose.dose.dicomInfo.SOPInstanceUID = doseInfo.SOPInstanceUID; +obj.importRTDose.dose.dicomInfo.ReferencedRTPlanSequence = doseInfo.ReferencedRTPlanSequence; end + diff --git a/matRad/dicom/matRad_scanDicomImportFolder.m b/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m similarity index 58% rename from matRad/dicom/matRad_scanDicomImportFolder.m rename to matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m index c890054f6..3240ac2b5 100644 --- a/matRad/dicom/matRad_scanDicomImportFolder.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m @@ -1,14 +1,14 @@ -function [ fileList, patientList ] = matRad_scanDicomImportFolder( patDir ) +function obj = matRad_scanDicomImportFolder(obj) % matRad function to scan a folder for dicom data % % call -% [ fileList, patientList ] = matRad_scanDicomImportFolder( patDir ) +% obj = matRad_scanDicomImportFolder(obj) % % input % patDir: folder to be scanned % % output -% fileList: matlab struct with a list of dicom files including meta +% allfiles: matlab struct with a list of dicom files including meta % infomation (type, series number etc.) % patientList: list of patients with dicom data in the folder % @@ -46,11 +46,17 @@ matRad_cfg.dispError('Image processing toolbox / packages not available!'); end -fileList = matRad_listAllFiles(patDir); +obj.allfiles = matRad_listAllFiles(obj.patDir); -if ~isempty(fileList) +if ~isempty(obj.allfiles) %% check for dicom files and differentiate patients, types, and series - numOfFiles = numel(fileList(:,1)); + + % Arrays of slice locations to find z resolution + % if it is not given initially in the files + LocationsArray1 = ones(1, numel(obj.allfiles(:,1))); + LocationsArray = LocationsArray1*1000; + ThBool = []; + numOfFiles = numel(obj.allfiles(:,1)); h = waitbar(0,'Please wait...','Color',matRad_cfg.gui.backgroundColor,'DefaultTextColor',matRad_cfg.gui.textColor); matRad_applyThemeToWaitbar(h); % precision value for double to string conversion @@ -61,12 +67,12 @@ waitbar((numOfFiles+1-i) / steps) try % try to get DicomInfo if matRad_cfg.isOctave || verLessThan('matlab','9') - info = dicominfo(fileList{i}); + info = dicominfo(obj.allfiles{i}); else - info = dicominfo(fileList{i},'UseDictionaryVR',true); + info = dicominfo(obj.allfiles{i},'UseDictionaryVR',true); end catch - fileList(i,:) = []; + obj.allfiles(i,:) = []; % Show progress if matRad_cfg.logLevel > 2 @@ -76,95 +82,102 @@ continue; end try - fileList{i,2} = info.Modality; + obj.allfiles{i,2} = info.Modality; catch - fileList{i,2} = NaN; + obj.allfiles{i,2} = NaN; end - fileList = parseDicomTag(fileList,info,'PatientID',i,3); + obj.allfiles = parseDicomTag(obj.allfiles,info,'PatientID',i,3); - switch fileList{i,2} + switch obj.allfiles{i,2} case 'CT' - fileList = parseDicomTag(fileList,info,'SeriesInstanceUID',i,4); + obj.allfiles = parseDicomTag(obj.allfiles,info,'SeriesInstanceUID',i,4); case {'RTPLAN','RTDOSE','RTSTRUCT'} - fileList = parseDicomTag(fileList,info,'SOPInstanceUID',i,4); + obj.allfiles = parseDicomTag(obj.allfiles,info,'SOPInstanceUID',i,4); otherwise - fileList = parseDicomTag(fileList,info,'SeriesInstanceUID',i,4); + obj.allfiles = parseDicomTag(obj.allfiles,info,'SeriesInstanceUID',i,4); end - fileList = parseDicomTag(fileList,info,'SeriesNumber',i,5,@seriesnum2str); %We want to make sure the series number is stored as string - fileList = parseDicomTag(fileList,info,'FamilyName',i,6); - fileList = parseDicomTag(fileList,info,'GivenName',i,7); - fileList = parseDicomTag(fileList,info,'PatientBirthDate',i,8); + obj.allfiles = parseDicomTag(obj.allfiles,info,'SeriesNumber',i,5,@seriesnum2str); %We want to make sure the series number is stored as string + obj.allfiles = parseDicomTag(obj.allfiles,info,'FamilyName',i,6); + obj.allfiles = parseDicomTag(obj.allfiles,info,'GivenName',i,7); + obj.allfiles = parseDicomTag(obj.allfiles,info,'PatientBirthDate',i,8); try if strcmp(info.Modality,'CT') - fileList{i,9} = num2str(info.PixelSpacing(1),str2numPrc); + obj.allfiles{i,9} = num2str(info.PixelSpacing(1),str2numPrc); else - fileList{i,9} = NaN; + obj.allfiles{i,9} = NaN; end catch - fileList{i,9} = NaN; + obj.allfiles{i,9} = NaN; end try if strcmp(info.Modality,'CT') - fileList{i,10} = num2str(info.PixelSpacing(2),str2numPrc); + + obj.allfiles{i,10} = num2str(info.PixelSpacing(2),str2numPrc); else - fileList{i,10} = NaN; + obj.allfiles{i,10} = NaN; end catch - fileList{i,10} = NaN; + obj.allfiles{i,10} = NaN; end try if strcmp(info.Modality,'CT') %usually the Attribute should be SliceThickness, but it - %seems like some data uses "SpacingBetweenSlices" instead. - if isfield(info,'SliceThickness') && ~isempty(info.SliceThickness) - fileList{i,11} = num2str(info.SliceThickness,str2numPrc); - elseif isfield(info,'SpacingBetweenSlices') - fileList{i,11} = num2str(info.SpacingBetweenSlices,str2numPrc); + %seems like some data uses "SpacingBetweenSlices" instead, + %but if there is neither this nor that attribute, + %resolution will be calculated based on SliceLocations + if isfield(info,'SliceThickness') && info.SliceThickness ~= 0 + obj.allfiles{i,11} = num2str(info.SliceThickness,str2numPrc); + ThBool = 1; + elseif isfield(info,'SpacingBetweenSlices') && ~isempty(info.SpacingBetweenSlices) + obj.allfiles{i,11} = num2str(info.SpacingBetweenSlices,str2numPrc); + ThBool = 1; else - matRad_cfg.dispError('Could not identify spacing between slices since neither ''SliceThickness'' nor ''SpacingBetweenSlices'' are specified'); + LocationsArray(i) = info.SliceLocation; + end else - fileList{i,11} = NaN; + obj.allfiles{i,11} = NaN; end catch - fileList{i,11} = NaN; + obj.allfiles{i,11} = NaN; end try + if strcmp(info.Modality,'RTDOSE') dosetext_helper = strcat('Instance','_', num2str(info.InstanceNumber),'_', ... info.DoseSummationType, '_', info.DoseType); - fileList{i,12} = dosetext_helper; + obj.allfiles{i,12} = dosetext_helper; else - fileList{i,12} = NaN; + obj.allfiles{i,12} = NaN; end catch - fileList{i,12} = NaN; + obj.allfiles{i,12} = NaN; end % writing corresponding dose dist. try - if strcmp(fileList{i,2},'RTPLAN') + if strcmp(obj.allfiles{i,2},'RTPLAN') corrDose = []; numDose = length(fieldnames(info.ReferencedDoseSequence)); for j = 1:numDose fieldName = strcat('Item_',num2str(j)); corrDose{j} = info.ReferencedDoseSequence.(fieldName).ReferencedSOPInstanceUID; end - fileList{i,13} = corrDose; + obj.allfiles{i,13} = corrDose; else - fileList{i,13} = {'NaN'}; + obj.allfiles{i,13} = {'NaN'}; end catch - fileList{i,13} = {'NaN'}; + obj.allfiles{i,13} = {'NaN'}; end % Show progress @@ -173,12 +186,26 @@ end end + + % Filtration, getting and assigning z resolution to all CT files + FiltredLocArray = unique(LocationsArray); + FiltredLocArray(end) = []; + numOfFiles = numel(obj.allfiles(:,1)); + + if isempty(ThBool) + for i = numOfFiles:-1:1 + if strcmp(obj.allfiles{i,2},'CT') + obj.allfiles{i,11} = num2str(unique(diff(FiltredLocArray))); + end + end + end + close(h) - if ~isempty(fileList) - patientList = unique(fileList(:,3)); + if ~isempty(obj.allfiles) + obj.patients = unique(obj.allfiles(:,3)); - if isempty(patientList) + if isempty(obj.patients) msgbox('No patient found with DICOM CT _and_ RT structure set in patient directory!', 'Error','error'); end else @@ -195,7 +222,7 @@ end -function fileList = parseDicomTag(fileList,info,tag,row,column,parsefcn) +function allfiles = parseDicomTag(allfiles,info,tag,row,column,parsefcn) global warnDlgDICOMtagShown; @@ -208,18 +235,18 @@ try if isfield(info,tag) if ~isempty(info.(tag)) - fileList{row,column} = parsefcn(info.(tag)); + allfiles{row,column} = parsefcn(info.(tag)); else - fileList{row,column} = defaultPlaceHolder; + allfiles{row,column} = defaultPlaceHolder; end else - fileList{row,column} = defaultPlaceHolder; + allfiles{row,column} = defaultPlaceHolder; end catch - fileList{row,column} = NaN; + allfiles{row,column} = NaN; end -if ~warnDlgDICOMtagShown && strcmp(fileList{row,column},defaultPlaceHolder) && (column == 3 || column == 4) +if ~warnDlgDICOMtagShown && strcmp(allfiles{row,column},defaultPlaceHolder) && (column == 3 || column == 4) dlgTitle = 'Dicom Tag import'; dlgQuestion = ['matRad_scanDicomImportFolder: Could not parse dicom tag: ' tag '. Using placeholder ' defaultPlaceHolder ' instead. Please check imported data carefully! Do you want to continue?']; diff --git a/matRad/dicom/matRad_convRtssContours2Indices.m b/matRad/dicom/matRad_convRtssContours2Indices.m index ef46186ee..4d830b310 100644 --- a/matRad/dicom/matRad_convRtssContours2Indices.m +++ b/matRad/dicom/matRad_convRtssContours2Indices.m @@ -44,7 +44,7 @@ end round2 = @(a,b) round(a*10^b)/10^b; - dicomCtSliceThickness = ct.dicomInfo.SliceThickness(round2(ct.dicomInfo.SlicePositions,1)==round2(dicomCtSlicePos,1)); + dicomCtSliceThickness = ct.dicomInfo.SliceThickness; %Sanity check msg = checkSliceThickness(dicomCtSliceThickness); diff --git a/matRad/dicom/matRad_createCst.m b/matRad/dicom/matRad_createCst.m deleted file mode 100644 index f4742095f..000000000 --- a/matRad/dicom/matRad_createCst.m +++ /dev/null @@ -1,83 +0,0 @@ -function cst = matRad_createCst(structures) -% matRad function to create a cst struct upon dicom import -% -% call -% cst = matRad_createCst(structures) -% -% input -% structures: matlab struct containing information about rt structure -% set (generated with matRad_importDicomRtss and -% matRad_convRtssContours2Indices) -% -% output -% cst: matRad cst struct -% -% References -% - -% -% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Copyright 2015 the matRad development team. -% -% This file is part of the matRad project. It is subject to the license -% terms in the LICENSE file found in the top-level directory of this -% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part -% of the matRad project, including this file, may be copied, modified, -% propagated, or distributed except according to the terms contained in the -% LICENSE file. -% -% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -matRad_cfg = MatRad_Config.instance(); - -nStructures = size(structures,2); -cst = cell(nStructures,6); - -%Create set of default colors -defaultColors = colorcube(nStructures); - -for i = 1:size(structures,2) - cst{i,1} = i - 1; % first organ has number 0 - cst{i,2} = structures(i).structName; - - if ~isempty(regexpi(cst{i,2},'tv')) || ... - ~isempty(regexpi(cst{i,2},'target')) || ... - ~isempty(regexpi(cst{i,2},'gtv')) || ... - ~isempty(regexpi(cst{i,2},'ctv')) || ... - ~isempty(regexpi(cst{i,2},'ptv')) || ... - ~isempty(regexpi(cst{i,2},'boost')) || ... - ~isempty(regexpi(cst{i,2},'tumor')) - - cst{i,3} = 'TARGET'; - - cst{i,5}.Priority = 1; - - % default objectives for targets - objective = DoseObjectives.matRad_SquaredDeviation; - objective.penalty = 800; - objective.parameters = {30}; %Default reference Dose - cst{i,6}{1} = struct(objective); - - else - - cst{i,3} = 'OAR'; - - cst{i,5}.Priority = 2; - - cst{i,6} = []; % define no OAR dummy objcetives - - end - - cst{i,4}{1} = structures(i).indices; - - % set default parameter for biological planning - cst{i,5}.alphaX = 0.1; - cst{i,5}.betaX = 0.05; - cst{i,5}.Visible = 1; - if isfield(structures(i),'structColor') && ~isempty(structures(i).structColor) - cst{i,5}.visibleColor = structures(i).structColor' ./ 255; - else - cst{i,5}.visibleColor = defaultColors(i,:); - matRad_cfg.dispInfo('No color information for structure %d "%s". Assigned default color [%f %f %f]\n',i,cst{i,2},defaultColors(i,1),defaultColors(i,2),defaultColors(i,3)); - end -end diff --git a/matRad/dicom/matRad_importDicom.m b/matRad/dicom/matRad_importDicom.m deleted file mode 100644 index d0f28b340..000000000 --- a/matRad/dicom/matRad_importDicom.m +++ /dev/null @@ -1,180 +0,0 @@ -function [ct,cst,pln,stf,resultGUI] = matRad_importDicom( files, dicomMetaBool ) -% matRad wrapper function to import a predefined set of dicom files -% into matRad's native data formats -% -% call -% [ct, cst, pln, stf, resultGUI] = matRad_importDicom( files ) -% [ct, cst, pln, stf, resultGUI] = matRad_importDicom( files, dicomMetaBool ) -% -% input -% files: list of files to be imported (will contain cts and rt -% structure set) -% dicomMetaBool: (boolean, optional) import complete dicomInfo and -% patientName -% -% output -% ct: matRad ct struct -% cst: matRad cst struct -% pln: matRad plan struct -% stf: matRad stf struct -% resultGUI: matRad result struct holding data for visualization in GUI -% -% References -% - -% -% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% Copyright 2015 the matRad development team. -% -% This file is part of the matRad project. It is subject to the license -% terms in the LICENSE file found in the top-level directory of this -% distribution and at https://github.com/e0404/matRad/LICENSE.md. No part -% of the matRad project, including this file, may be copied, modified, -% propagated, or distributed except according to the terms contained in the -% LICENSE file. -% -% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -matRad_cfg = MatRad_Config.instance(); - -%% -if ~exist('dicomMetaBool','var') - dicomMetaBool = true; -end - -%% -h = waitbar(0,'Please wait...','Color',matRad_cfg.gui.backgroundColor,'DefaultTextColor',matRad_cfg.gui.textColor); -matRad_applyThemeToWaitbar(h); -%h.WindowStyle = 'Modal'; -steps = 2; - -%% import ct-cube -waitbar(1 / steps) -resolution.x = files.resx; -resolution.y = files.resy; -resolution.z = files.resz; % [mm] / lps coordinate system -if files.useDoseGrid && isfield(files,'rtdose') - % get grid from dose cube - if matRad_cfg.isOctave || verLessThan('matlab','9') - doseInfo = dicominfo(files.rtdose{1,1}); - else - doseInfo = dicominfo(files.rtdose{1,1},'UseDictionaryVR',true); - end - doseGrid{1} = doseInfo.ImagePositionPatient(1) + doseInfo.ImageOrientationPatient(1) * ... - doseInfo.PixelSpacing(1) * double(0:doseInfo.Columns - 1); - doseGrid{2} = doseInfo.ImagePositionPatient(2) + doseInfo.ImageOrientationPatient(5) * ... - doseInfo.PixelSpacing(2) * double(0:doseInfo.Rows - 1); - doseGrid{3} = doseInfo.ImagePositionPatient(3) + doseInfo.GridFrameOffsetVector(:)'; - - % get ct on grid - ct = matRad_importDicomCt(files.ct, resolution, dicomMetaBool,doseGrid); - -else - ct = matRad_importDicomCt(files.ct, resolution, dicomMetaBool); -end - -if ~isempty(files.rtss) - - %% import structure data - waitbar(2 / steps) - structures = matRad_importDicomRtss(files.rtss{1},ct.dicomInfo); - close(h) - - %% creating structure cube - h = waitbar(0,'Please wait...','Color',matRad_cfg.gui.backgroundColor,'DefaultTextColor',matRad_cfg.gui.textColor); - matRad_applyThemeToWaitbar(h); - %h.WindowStyle = 'Modal'; - steps = numel(structures); - - - % The x- & y-direction in lps-coordinates are specified in: - % ImageOrientationPatient - -xDir = ct.dicomInfo.ImageOrientationPatient(1:3); % lps: [1;0;0] -yDir = ct.dicomInfo.ImageOrientationPatient(4:6); % lps: [0;1;0] - - -if ~(xDir(1) == 1 && xDir(2) == 0 && xDir(3) == 0) - matRad_cfg.dispInfo('\nNonstandard image orientation: tring to Mirror RTSS x-direction...') -end - -if ~(yDir(1) == 0 && yDir(2) == 1 && yDir(3) == 0) - matRad_cfg.dispInfo('\nNonstandard image orientation: trying to Mirror RTSS y direction...') -end - for i = 1:numel(structures) - % computations take place here - waitbar(i / steps) - fprintf('creating cube for %s volume... ', structures(i).structName); - try - structures(i).indices = matRad_convRtssContours2Indices(structures(i),ct); - fprintf('\n'); - catch ME - warning('matRad:dicomImport','could not be imported: %s',ME.message); - structures(i).indices = []; - end - end - fprintf('finished!\n'); - close(h) - - %% creating cst - cst = matRad_createCst(structures); - -else - - cst = matRad_dummyCst(ct); - -end - -%% determine pln parameters -if isfield(files,'rtplan') - if ~(cellfun(@isempty,files.rtplan(1,:))) - pln = matRad_importDicomRTPlan(ct, files.rtplan, dicomMetaBool); - end -else - pln = struct([]); -end - -%% import stf -if isfield(files,'rtplan') - if ~(cellfun(@isempty,files.rtplan(1,:))) - if (strcmp(pln.radiationMode,'protons') || strcmp(pln.radiationMode,'carbon')) - %% import steering file - % pln output because bixelWidth is determined via the stf - [stf, pln] = matRad_importDicomSteeringParticles(ct, pln, files.rtplan); - elseif strcmp(pln.radiationMode, 'photons') && isfield(pln.propStf,'collimation') - % return correct angles in pln - [stf, pln] = matRad_importDicomSteeringPhotons(pln); - else - warning('No support for DICOM import of steering information for this modality.'); - end - end -else - stf = struct([]); -end - -%% import dose cube -if isfield(files,'rtdose') - % check if files.rtdose contains a path and is labeld as RTDose - % only the first two elements are relevant for loading the rt dose - if ~(cellfun(@isempty,files.rtdose(1,1:2))) - fprintf('loading Dose files \n', structures(i).structName); - % parse plan in order to scale dose cubes to a fraction based dose - if exist('pln','var') && ~isempty(pln) && isfield(pln,'numOfFractions') - resultGUI = matRad_importDicomRTDose(ct, files.rtdose, pln); - else - resultGUI = matRad_importDicomRTDose(ct, files.rtdose); - end - if size(resultGUI) == 0 - resultGUI = struct([]); - end - end -else - resultGUI = struct([]); -end - -%% put weight also into resultGUI -if ~isempty(stf) && ~isempty(resultGUI) - resultGUI.w = []; - for i = 1:size(stf,2) - resultGUI.w = [resultGUI.w; [stf(i).ray.weight]']; - end -end \ No newline at end of file diff --git a/matRad/dicom/matRad_importFieldShapes.m b/matRad/dicom/matRad_importFieldShapes.m index bb001cb4c..2450e7376 100644 --- a/matRad/dicom/matRad_importFieldShapes.m +++ b/matRad/dicom/matRad_importFieldShapes.m @@ -53,10 +53,21 @@ % set device specific parameters device = struct; + for j = 1:length(currDeviceSeqNames) currLimitsSeq = currDeviceSeq.(currDeviceSeqNames{j}); device(j).DeviceType = currLimitsSeq.RTBeamLimitingDeviceType; - device(j).NumOfLeafs = currLimitsSeq.NumberOfLeafJawPairs; + + % Check a type of collimator + if isfield(currLimitsSeq, 'NumberOfLeafJawPairs') + device(j).NumOfLeafs = currLimitsSeq.NumberOfLeafJawPairs; + elseif any(strcmpi(device(j).DeviceType, {'X', 'Y', 'ASYMX', 'ASYMY'} )) + device(j).NumOfLeafs = 1; + currLimitsSeq.NumberOfLeafJawPairs = device(j).NumOfLeafs; + else + error('Number of leafs/jaws is not determined'); + end + % Check for nonstandard double collimators if strncmpi(device(j).DeviceType,'MLC',3) device(j).Limits = currLimitsSeq.LeafPositionBoundaries; @@ -80,7 +91,7 @@ return; end end - + for j = 1:length(currControlPointSeqNames)-1 counter = counter + 1; currControlPointElement = currBeamSeq.ControlPointSequence.(currControlPointSeqNames{j}); @@ -91,7 +102,7 @@ matRad_cfg.dispWarning('First Control Point in Beam %d already has a CumulativeMeterset bigger than 0.',i) cumWeight = currControlPointElement.CumulativeMetersetWeight; end - + if isfield(currControlPointElement, 'BeamLimitingDevicePositionSequence') % get the leaf position for every device tmpCollimation.Fields(counter).LeafPos{length(currDeviceSeqNames),1} = []; @@ -100,8 +111,8 @@ % the first control point and has to be defined on following % points only if it changes -> default initilation if counter > 1 if counter > 1 - for k = 1:length(currDeviceSeqNames) - tmpCollimation.Fields(counter).LeafPos{k} = tmpCollimation.Fields(counter-1).LeafPos{k}; + for n = 1:length(currDeviceSeqNames) + tmpCollimation.Fields(counter).LeafPos{n} = tmpCollimation.Fields(counter-1).LeafPos{n}; end end @@ -113,13 +124,13 @@ deviceIx = find(strcmp({device(:).DeviceType}, ... currControlPointElement.BeamLimitingDevicePositionSequence.(currDeviceSeqNames{k}).RTBeamLimitingDeviceType)); - if (length(currLeafPos) ~= 2 * device(deviceIx).NumOfLeafs) + if length(currLeafPos) ~= 2*device(deviceIx).NumOfLeafs warning(['Number of leafs/jaws does not match given number of leaf/jaw positions in control point sequence ' ... currControlPointSeqNames{j} ' on beam sequence ' beamSeqNames{i} ' for device ' ... device(deviceIx).DeviceType '. No field shape import performed!']); return; end - + % set left and right leaf positions tmpCollimation.Fields(counter).LeafPos{deviceIx}(:,1) = currLeafPos(1:device(deviceIx).NumOfLeafs); tmpCollimation.Fields(counter).LeafPos{deviceIx}(:,2) = currLeafPos(device(deviceIx).NumOfLeafs+1:end); @@ -133,9 +144,9 @@ end else tmpCollimation.Fields(counter) = tmpCollimation.Fields(counter - 1); - end - - % get field meta information + end + + % get field meta information if isfield(nextControlPointElement, 'CumulativeMetersetWeight') newCumWeight = nextControlPointElement.CumulativeMetersetWeight; relativeShapeWeight = (newCumWeight - cumWeight) / currBeamSeq.FinalCumulativeMetersetWeight; @@ -148,7 +159,7 @@ return; end tmpCollimation.Fields(counter).SAD = currBeamSeq.SourceAxisDistance; - + % other meta information is only included in all control point % sequences if it changes during treatment, otherwise use FieldMeta for k = 1:length(meta) @@ -158,7 +169,7 @@ tmpCollimation.Fields(counter).(meta{k,2}) = FieldMeta.(meta{k,2}); end end - % save information which control point sequence belongs to which beam sequence + % % save information which control point sequence belongs to which beam sequence tmpCollimation.Fields(counter).BeamIndex = i; end end @@ -200,10 +211,10 @@ firstLeafStart = ceil(tmpCollimation.Devices{beamIndex}(j).Limits(1)/convResolution)+shapeLimit+1; lastLeafEnd = ceil(tmpCollimation.Devices{beamIndex}(j).Limits(end)/convResolution)+shapeLimit; - if strcmpi(tmpCollimation.Devices{beamIndex}(j).Direction, 'X') + if ismember(tmpCollimation.Devices{beamIndex}(j).Direction, {'X', 'X1', 'X2'}, 'legacy') shape(1:firstLeafStart-1,:) = 0; shape(lastLeafEnd+1:end,:) = 0; - elseif strcmpi(tmpCollimation.Devices{beamIndex}(j).Direction, 'Y') + elseif ismember(tmpCollimation.Devices{beamIndex}(j).Direction, {'Y', 'Y1', 'Y2'}, 'legacy') shape(:,1:firstLeafStart-1) = 0; shape(:,lastLeafEnd+1:end) = 0; else @@ -269,3 +280,5 @@ end collimation = tmpCollimation; end + + diff --git a/matRad/dicom/matRad_interpDicomCtCube.m b/matRad/dicom/matRad_interpDicomCtCube.m index 27658fd53..171e1bb96 100644 --- a/matRad/dicom/matRad_interpDicomCtCube.m +++ b/matRad/dicom/matRad_interpDicomCtCube.m @@ -1,16 +1,16 @@ -function interpCt = matRad_interpDicomCtCube(origCt, origCtInfo, resolution, grid) +function interpCt = matRad_interpDicomCtCube(origCt, origCtInfo, resolution, doseGrid) % matRad function to interpolate a 3D ct cube to a different resolution % % call % interpCt = matRad_interpDicomCtCube(origCt, origCtInfo, resolution) -% interpCt = matRad_interpDicomCtCube(origCt, origCtInfo, resolution, grid) +% interpCt = matRad_interpDicomCtCube(origCt, origCtInfo, resolution, doseGrid) % % input % origCt: original CT as matlab 3D array % origCtInfo: meta information about the geometry of the orgiCt cube % resolution: target resolution [mm] in x, y, an z direction for the % new cube -% grid: optional: externally specified grid vector +% doseGrid: optional: externally specified grid vector % % output % interpCt: interpolated ct cube as matlab 3D array @@ -51,10 +51,10 @@ y = flip(y,2); end -if exist('grid','var') - xq = grid{1}; - yq = grid{2}; - zq = grid{3}; +if exist('doseGrid','var') + xq = doseGrid{1}; + yq = doseGrid{2}; + zq = doseGrid{3}; % calculate intersection of regions to avoid interpolation issues xqRe = coordsOfFirstPixel(1,1):origCtInfo(1).ImageOrientationPatient(1)*resolution.x: ... diff --git a/matRad/gui/widgets/matRad_importDicomWidget.m b/matRad/gui/widgets/matRad_importDicomWidget.m index e4693c18c..a4232a7aa 100644 --- a/matRad/gui/widgets/matRad_importDicomWidget.m +++ b/matRad/gui/widgets/matRad_importDicomWidget.m @@ -20,6 +20,7 @@ % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% properties + importer; end methods @@ -121,35 +122,16 @@ % this gets a list of ct series for this patient set(handles.ctseries_listbox,'Value',1); % set dummy value to one set(handles.ctseries_listbox,'String',unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient),4))); - - selectedDoseSeriesString = get(handles.doseseries_listbox,'String'); - % this gets a resolution for this patient - selectedCtSeriesString = get(handles.ctseries_listbox,'String'); - if ~isempty(selectedCtSeriesString) - res_x = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,4), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),9)); - res_y = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,4), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),10)); - res_z = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,4), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),11)); - else - res_x = NaN; res_y = NaN; res_z = NaN; - end else set(handles.ctseries_listbox,'Value',1); % set dummy value to one set(handles.ctseries_listbox,'String',unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient),5))); - selectedCtSeriesString = get(handles.ctseries_listbox,'String'); - if ~isempty(selectedCtSeriesString) - res_x = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,5), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),9)); - res_y = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,5), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),10)); - res_z = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,5), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),11)); - else - res_x = NaN; res_y = NaN; res_z = NaN; - end end - set(handles.resx_edit,'String',res_x); - set(handles.resy_edit,'String',res_y); - if numel(res_z) > 1 + set(handles.resx_edit,'String',this.importer.importFiles.resx); + set(handles.resy_edit,'String',this.importer.importFiles.resy); + if numel(this.importer.importFiles.resz) > 1 set(handles.resz_edit,'String','not equi'); else - set(handles.resz_edit,'String',res_z); + set(handles.resz_edit,'String',this.importer.importFiles.resz); end % Update handles structure % guidata(hObject, handles); @@ -160,78 +142,18 @@ % H22 IMPORT BUTTON CALLBACK function this = import_button_Callback(this, hObject, eventdata) handles = this.handles; - patient_listbox = get(handles.patient_listbox,'String'); - ctseries_listbox = get(handles.ctseries_listbox,'String'); - rtplan_listbox = get(handles.rtplan_listbox,'String'); - doseseries_listbox = get(handles.rtplan_listbox,'String'); - if ~isempty(patient_listbox) - selected_patient = patient_listbox(get(handles.patient_listbox,'Value')); - end - if ~isempty(ctseries_listbox) - selected_ctseries = ctseries_listbox(get(handles.ctseries_listbox,'Value')); - end - if ~isempty(rtplan_listbox) - selected_rtplan = rtplan_listbox(get(handles.rtplan_listbox,'Value')); - end - - if get(handles.SeriesUID_radiobutton,'Value') == 1 - files.ct = handles.fileList(strcmp(handles.fileList(:,3), selected_patient) & ... - strcmp(handles.fileList(:,4), selected_ctseries),:); - - %files.rtss = handles.fileList(strcmp(handles.fileList(:,3), selected_patient) & ... - % strcmp(handles.fileList(:,4), selected_rtseries),:); - else - files.ct = handles.fileList(strcmp(handles.fileList(:,3), selected_patient) & ... - strcmp(cellfun(@num2str,handles.fileList(:,5),'UniformOutput',false), selected_ctseries) & strcmp(handles.fileList(:,2),'CT'),:); - - %files.rtss = handles.fileList(strcmp(handles.fileList(:,3), selected_patient) & ... - % strcmp(handles.fileList(:,5), selected_rtseries),:); - end - - allRtss = handles.fileList(strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,2),'RTSTRUCT'),:); - if ~isempty(allRtss) - files.rtss = allRtss(get(handles.rtseries_listbox,'Value'),:); - else - files.rtss = []; - end - files.resx = str2double(get(handles.resx_edit,'String')); - files.resy = str2double(get(handles.resy_edit,'String')); - % check if valid assignment is made when z slices are not equi-distant - if strcmp(get(handles.resz_edit,'String'),'not equi') - this.showError('Ct data not sliced equi-distantly in z direction! Chose uniform resolution.'); - return; - else - files.resz = str2double(get(handles.resz_edit,'String')); - end - % selected RT Plan - rtplan = handles.fileList(strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,2),'RTPLAN'),:); - if ~isempty(rtplan) && ~isempty(get(handles.rtplan_listbox,'Value')) - files.rtplan = rtplan(get(handles.rtplan_listbox,'Value'),:); - end - - % selected RT Dose - rtdose = handles.fileList(strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,2),'RTDOSE'),:); - if ~isempty(rtdose) && ~isempty(get(handles.doseseries_listbox,'Value')) - selectedRtDose = get(handles.doseseries_listbox,'String'); - selectedRtDoseIx = NaN*ones(1,numel(selectedRtDose)); - for i = 1:numel(selectedRtDose) - selectedRtDoseIx(i) = find(strcmp(rtdose(:,4),selectedRtDose{i})); - end - files.rtdose = rtdose(selectedRtDoseIx,:); - end - - % check if we should use the dose grid resolution - files.useDoseGrid = get(handles.checkbox3,'Value'); - - % dicomMetaBool: store complete DICOM information and patientName or not - dicomMetaBool = logical(get(handles.checkPatientName,'Value')); - [ct, cst, pln, stf, resultGUI] = matRad_importDicom(files, dicomMetaBool); + this.importer.matRad_importDicom(); %% save ct, cst, pln, dose matRad_cfg = MatRad_Config.instance(); - matRadFileName = fullfile(matRad_cfg.userfolders{1},[files.ct{1,3} '.mat']); % use default from dicom + matRadFileName = fullfile(matRad_cfg.userfolders{1},[this.importer.allfiles{1,3} '.mat']); % use default from dicom [FileName,PathName] = uiputfile('*.mat','Save as...',matRadFileName); + ct = this.importer.ct; + cst = this.importer.cst; + pln = this.importer.pln; + stf = this.importer.stf; + resultGUI = this.importer.resultGUI; if ischar(FileName) variableNames = {'ct','cst','pln','stf','resultGUI'}; @@ -310,39 +232,19 @@ function this = doseseries_listbox_Callback(this, hObject, eventdata) handles = this.handles; - if ~isempty(get(hObject,'Value')) + if ~isempty(get(hObject,'Value')) set(handles.checkbox3,'Enable','on'); else set(handles.checkbox3,'Value',0); set(handles.checkbox3,'Enable','off'); % retrieve and display resolution for DICOM ct cube - patient_listbox = get(handles.patient_listbox,'String'); - selected_patient = patient_listbox(get(handles.patient_listbox,'Value')); - selectedCtSeriesString = get(handles.ctseries_listbox,'String'); - if get(handles.SeriesUID_radiobutton,'Value') == 1 - if ~isempty(selectedCtSeriesString) - res_x = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,4), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),9)); - res_y = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,4), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),10)); - res_z = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,4), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),11)); - else - res_x = NaN; res_y = NaN; res_z = NaN; - end - else - if ~isempty(selectedCtSeriesString) - res_x = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,5), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),9)); - res_y = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,5), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),10)); - res_z = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient) & strcmp(handles.fileList(:,5), selectedCtSeriesString{get(handles.ctseries_listbox,'Value')}),11)); - else - res_x = NaN; res_y = NaN; res_z = NaN; - end - end - set(handles.resx_edit,'String',res_x); - set(handles.resy_edit,'String',res_y); + set(handles.resx_edit,'String',this.importer.importFiles.resx); + set(handles.resy_edit,'String',this.importer.importFiles.resy); if numel(res_z) > 1 set(handles.resz_edit,'String','not equi'); else - set(handles.resz_edit,'String',res_z); - end + set(handles.resz_edit,'String',this.importer.importFiles.resz); + end end @@ -993,14 +895,15 @@ end methods (Access = private) - % SCAN FUNKTION + % SCAN FUNKTION function this = scan(this, hObject, eventdata) handles = this.handles; - [fileList, patient_listbox] = matRad_scanDicomImportFolder(get(handles.dir_path_field,'String')); - if iscell(patient_listbox) - handles.fileList = fileList; + this.importer = matRad_DicomImporter(get(handles.dir_path_field,'String')); + + if iscell(this.importer.patients) + handles.fileList = this.importer.allfiles; %handles.patient_listbox.String = patient_listbox; - set(handles.patient_listbox,'String',patient_listbox,'Value',1); + set(handles.patient_listbox,'String',this.importer.patients,'Value',1); % guidata(hObject, handles); this.handles = handles; end From 417d96737c87fadb6970aeb78cd52d0071d9f076 Mon Sep 17 00:00:00 2001 From: Raedlr Date: Wed, 28 Aug 2024 14:38:47 +0200 Subject: [PATCH 02/28] Small bug fixes to Plan Widget and stf checker --- matRad/gui/widgets/matRad_PlanWidget.m | 3 ++- matRad/util/matRad_comparePlnStf.m | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/matRad/gui/widgets/matRad_PlanWidget.m b/matRad/gui/widgets/matRad_PlanWidget.m index 1bf3b57a9..f7132d0a2 100644 --- a/matRad/gui/widgets/matRad_PlanWidget.m +++ b/matRad/gui/widgets/matRad_PlanWidget.m @@ -849,12 +849,13 @@ else set(handles.btnRunSequencing,'Value', 0 ); end + if isfield (pln.propOpt, 'conf3D') set(handles.radiobutton3Dconf,'Value',pln.propOpt.conf3D); end if ~isfield(pln,'propDoseCalc') || ~isfield(pln.propDoseCalc,'doseGrid') - pln.propDoseCalc.doseGrid.resolution = matRad_cfg.defaults.propDoseCalc.resolution; + pln.propDoseCalc.doseGrid.resolution = matRad_cfg.defaults.propDoseCalc.doseGrid.resolution; end set(handles.editDoseX,'String',num2str(pln.propDoseCalc.doseGrid.resolution.x)); diff --git a/matRad/util/matRad_comparePlnStf.m b/matRad/util/matRad_comparePlnStf.m index df780cd5e..bf6ed8974 100644 --- a/matRad/util/matRad_comparePlnStf.m +++ b/matRad/util/matRad_comparePlnStf.m @@ -64,7 +64,13 @@ end %% compare Bixel width in stf and pln -if ~isfield(pln.propStf,'bixelWidth') || stf(1).bixelWidth ~= pln.propStf.bixelWidth +if ischar(stf(1).bixelWidth) + LogVal = strcmp(stf(1).radiationMode, pln.radiationMode); +else + LogVal = logical(stf(1).bixelWidth == pln.propStf.bixelWidth); +end + +if ~isfield(pln.propStf,'bixelWidth') || ~LogVal allMatch=false; msg= 'Bixel width does not match'; return From be6a1fa92bbc5f97837ebb4ed457e6abdde00c4d Mon Sep 17 00:00:00 2001 From: Raedlr Date: Wed, 28 Aug 2024 15:06:54 +0200 Subject: [PATCH 03/28] Added name to authors list --- AUTHORS.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.txt b/AUTHORS.txt index 114eae08f..ae4fcc780 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -19,6 +19,7 @@ * Emily Heath * Cindy Hermann * Noa Homolka +* Raed Ibragim * Fabian Jäger * Fernando Hueso-González * Navid Khaledi From 2cbd7b1f5af8de586a8adbd41324ed5807085c8f Mon Sep 17 00:00:00 2001 From: Raedlr Date: Wed, 28 Aug 2024 15:21:02 +0200 Subject: [PATCH 04/28] fixed small bag --- matRad/util/matRad_comparePlnStf.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/matRad/util/matRad_comparePlnStf.m b/matRad/util/matRad_comparePlnStf.m index bf6ed8974..203f5d2ad 100644 --- a/matRad/util/matRad_comparePlnStf.m +++ b/matRad/util/matRad_comparePlnStf.m @@ -64,9 +64,9 @@ end %% compare Bixel width in stf and pln -if ischar(stf(1).bixelWidth) +if isfield(pln.propStf,'bixelWidth') && ischar(stf(1).bixelWidth) LogVal = strcmp(stf(1).radiationMode, pln.radiationMode); -else +elseif isfield(pln.propStf,'bixelWidth') && ~ischar(stf(1).bixelWidth) LogVal = logical(stf(1).bixelWidth == pln.propStf.bixelWidth); end From c0043a6eb7c4f54cef5a646b0d5f1af6998d5503 Mon Sep 17 00:00:00 2001 From: Raedlr Date: Fri, 30 Aug 2024 15:59:35 +0200 Subject: [PATCH 05/28] Fixed bug with resolution --- matRad/gui/widgets/matRad_importDicomWidget.m | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/matRad/gui/widgets/matRad_importDicomWidget.m b/matRad/gui/widgets/matRad_importDicomWidget.m index a4232a7aa..5ba01447b 100644 --- a/matRad/gui/widgets/matRad_importDicomWidget.m +++ b/matRad/gui/widgets/matRad_importDicomWidget.m @@ -142,7 +142,17 @@ % H22 IMPORT BUTTON CALLBACK function this = import_button_Callback(this, hObject, eventdata) handles = this.handles; - + + % case the resolution was changed manually + if handles.resx_edit.String ~= this.importer.importFiles.resx + this.importer.importFiles.resx = handles.resx_edit.String; + end + if handles.resy_edit.String ~= this.importer.importFiles.resy + this.importer.importFiles.resy = handles.resy_edit.String; + end + if handles.resz_edit.String ~= this.importer.importFiles.resz + this.importer.importFiles.resz = handles.resz_edit.String; + end this.importer.matRad_importDicom(); %% save ct, cst, pln, dose From d5451701742a0bcbbc727d985a90cfcff4c0c05a Mon Sep 17 00:00:00 2001 From: Raedlr Date: Wed, 4 Sep 2024 14:30:17 +0200 Subject: [PATCH 06/28] Added the possibility of selecting patients and structures within a singe patient --- matRad/gui/widgets/matRad_importDicomWidget.m | 78 ++++++++++++++++++- 1 file changed, 74 insertions(+), 4 deletions(-) diff --git a/matRad/gui/widgets/matRad_importDicomWidget.m b/matRad/gui/widgets/matRad_importDicomWidget.m index 5ba01447b..359012b2a 100644 --- a/matRad/gui/widgets/matRad_importDicomWidget.m +++ b/matRad/gui/widgets/matRad_importDicomWidget.m @@ -106,7 +106,27 @@ % 11. res_z % 12. detailed dose description - currently not in use for GUI user patient_listbox = get(handles.patient_listbox,'String'); - selected_patient = patient_listbox(get(handles.patient_listbox,'Value')); + if ~isempty(patient_listbox) + selected_patient = patient_listbox(get(handles.patient_listbox,'Value')); + end + + if get(handles.SeriesUID_radiobutton,'Value') == 1 + % this gets a list of ct series for this patient + set(handles.ctseries_listbox,'Value',1); % set dummy value to one + + set(handles.ctseries_listbox,'String',unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient),4))); + + else + set(handles.ctseries_listbox,'Value',1); % set dummy value to one + set(handles.ctseries_listbox,'String',unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient),5))); + end + % for i = 1:numel(this.handles.ctseries_listbox.String) + % if i == handles.ctseries_listbox.Value + % SeriesInstUID = unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient),15)) + % if + % + % end + % end % this gets a list of rtss series for this patient set(handles.rtseries_listbox,'Value',1); % set dummy value to one set(handles.rtseries_listbox,'String',handles.fileList(strcmp(handles.fileList(:,2), 'RTSTRUCT') & strcmp(handles.fileList(:,3), selected_patient),4)); @@ -121,6 +141,7 @@ if get(handles.SeriesUID_radiobutton,'Value') == 1 % this gets a list of ct series for this patient set(handles.ctseries_listbox,'Value',1); % set dummy value to one + set(handles.ctseries_listbox,'String',unique(handles.fileList(strcmp(handles.fileList(:,2), 'CT') & strcmp(handles.fileList(:,3), selected_patient),4))); else set(handles.ctseries_listbox,'Value',1); % set dummy value to one @@ -153,11 +174,59 @@ if handles.resz_edit.String ~= this.importer.importFiles.resz this.importer.importFiles.resz = handles.resz_edit.String; end + + % to pass only selected objects to the matRad_importDicom + % selected patient + if ~isempty(handles.patient_listbox) + selected_patient = this.importer.patient{get(handles.patient_listbox,'Value')}; + this.importer.patient = selected_patient; + end + + % selected CT serie + allCTSeries = this.importer.importFiles.ct; + if ~isempty(handles.ctseries_listbox) && get(handles.SeriesUID_radiobutton,'Value') == 1 + UIDSelected_ctserie = handles.ctseries_listbox.String{get(handles.ctseries_listbox,'Value'), 1}; + selectedCTSerie = allCTSeries(strcmp(allCTSeries(:, 4), UIDSelected_ctserie), :); + this.importer.importFiles.ct = selectedCTSerie; + elseif ~isempty(handles.ctseries_listbox) && get(handles.SeriesNumber_radiobutton,'Value') == 1 + SeriesNumSelected_ctserie = handles.ctseries_listbox.String{get(handles.ctseries_listbox,'Value'), 1}; + selectedCTSerie = allCTSeries(strcmp(allCTSeries(:, 5), SeriesNumSelected_ctserie), :); + this.importer.importFiles.ct = selectedCTSerie; + end + + % selected RTStruct + allRtss = this.importer.importFiles.rtss; + if ~isempty(allRtss) + UIDSelected_rtserie = handles.rtseries_listbox.String{get(handles.rtseries_listbox,'Value'), 1}; + selectedRTStructSerie = allRtss(strcmp(allRtss(:, 4), UIDSelected_rtserie), :); + this.importer.importFiles.rtss = selectedRTStructSerie; + end + + % selected RTPlan + allRTPlans = this.importer.importFiles.rtplan; + if ~isempty(allRTPlans) && ~isempty(handles.rtplan_listbox.Value) + UIDSelected_rtplan = handles.rtplan_listbox.String{get(handles.rtplan_listbox,'Value'), 1}; + selectedRTPlan = allRTPlans(strcmp(allRTPlans(:, 4), UIDSelected_rtplan), :); + this.importer.importFiles.rtplan = selectedRTPlan; + else + this.importer.importFiles.rtplan = []; + end + + % selected dose serie + allRTDoses = this.importer.importFiles.rtdose; + if ~isempty(allRTDoses) && ~isempty(handles.doseseries_listbox.Value) + UIDSelected_rtdose = handles.doseseries_listbox.String{get(handles.doseseries_listbox,'Value'), 1}; + selectedRTDose = allRTDoses(strcmp(allRTDoses(:, 4), UIDSelected_rtdose), :); + this.importer.importFiles.rtdose = selectedRTDose; + else + this.importer.importFiles.rtdose = []; + end + this.importer.matRad_importDicom(); %% save ct, cst, pln, dose matRad_cfg = MatRad_Config.instance(); - matRadFileName = fullfile(matRad_cfg.userfolders{1},[this.importer.allfiles{1,3} '.mat']); % use default from dicom + matRadFileName = fullfile(matRad_cfg.userfolders{1},[this.importer.patient '.mat']); % use default from dicom [FileName,PathName] = uiputfile('*.mat','Save as...',matRadFileName); ct = this.importer.ct; cst = this.importer.cst; @@ -908,12 +977,13 @@ % SCAN FUNKTION function this = scan(this, hObject, eventdata) handles = this.handles; + this.importer = matRad_DicomImporter(get(handles.dir_path_field,'String')); - if iscell(this.importer.patients) + if iscell(this.importer.patient) handles.fileList = this.importer.allfiles; %handles.patient_listbox.String = patient_listbox; - set(handles.patient_listbox,'String',this.importer.patients,'Value',1); + set(handles.patient_listbox,'String',this.importer.patient,'Value',1); % guidata(hObject, handles); this.handles = handles; end From aee6991e3502e00bd36e3f7d75ddca6fdc2ae360 Mon Sep 17 00:00:00 2001 From: Raedlr Date: Wed, 4 Sep 2024 14:39:54 +0200 Subject: [PATCH 07/28] A small upgrade for the scan function, adding new parameters that can be used to find the structure file related to the CT (but not yet) --- .../matRad_scanDicomImportFolder.m | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m b/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m index 3240ac2b5..e0d09108a 100644 --- a/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m @@ -92,22 +92,39 @@ switch obj.allfiles{i,2} case 'CT' - obj.allfiles = parseDicomTag(obj.allfiles,info,'SeriesInstanceUID',i,4); - - case {'RTPLAN','RTDOSE','RTSTRUCT'} + obj.allfiles = parseDicomTag(obj.allfiles,info,'SeriesInstanceUID',i,4); + obj.allfiles = parseDicomTag(obj.allfiles,info,'SOPInstanceUID',i,15); + + case 'RTPLAN' - obj.allfiles = parseDicomTag(obj.allfiles,info,'SOPInstanceUID',i,4); + obj.allfiles = parseDicomTag(obj.allfiles,info,'SOPInstanceUID',i,4); + if isfield(info.ReferencedStructureSetSequence.Item_1,'ReferencedSOPInstanceUID') + obj.allfiles = parseDicomTag(obj.allfiles,info.ReferencedStructureSetSequence.Item_1,'ReferencedSOPInstanceUID',i,15); + end + case 'RTDOSE' + + obj.allfiles = parseDicomTag(obj.allfiles,info,'SOPInstanceUID',i,4); + if isfield(info.ReferencedRTPlanSequence.Item_1,'ReferencedSOPInstanceUID') + obj.allfiles = parseDicomTag(obj.allfiles,info.ReferencedRTPlanSequence.Item_1,'ReferencedSOPInstanceUID',i,15); + end + case 'RTSTRUCT' + + obj.allfiles = parseDicomTag(obj.allfiles,info,'SOPInstanceUID',i,4); + if isfield(info.ReferencedFrameOfReferenceSequence.Item_1.RTReferencedStudySequence.Item_1,'ReferencedSOPInstanceUID') + obj.allfiles = parseDicomTag(obj.allfiles,info.ReferencedFrameOfReferenceSequence.Item_1.RTReferencedStudySequence.Item_1,'ReferencedSOPInstanceUID',i,15); + end otherwise obj.allfiles = parseDicomTag(obj.allfiles,info,'SeriesInstanceUID',i,4); end - + obj.allfiles = parseDicomTag(obj.allfiles,info,'SeriesNumber',i,5,@seriesnum2str); %We want to make sure the series number is stored as string obj.allfiles = parseDicomTag(obj.allfiles,info,'FamilyName',i,6); obj.allfiles = parseDicomTag(obj.allfiles,info,'GivenName',i,7); obj.allfiles = parseDicomTag(obj.allfiles,info,'PatientBirthDate',i,8); + obj.allfiles = parseDicomTag(obj.allfiles,info,'StudyInstanceUID',i,14); try if strcmp(info.Modality,'CT') @@ -203,9 +220,9 @@ close(h) if ~isempty(obj.allfiles) - obj.patients = unique(obj.allfiles(:,3)); + obj.patient = unique(obj.allfiles(:,3)); - if isempty(obj.patients) + if isempty(obj.patient) msgbox('No patient found with DICOM CT _and_ RT structure set in patient directory!', 'Error','error'); end else From c5b44adb21f8a5a279db925d26e087b3b0749f0f Mon Sep 17 00:00:00 2001 From: Raedlr Date: Wed, 4 Sep 2024 15:56:36 +0200 Subject: [PATCH 08/28] some changes in the description of matRad_DicomImporter funktions --- .../matRad_DicomImporter.m | 26 +++++++------- .../@matRad_DicomImporter/matRad_calcHU.m | 14 ++++---- .../@matRad_DicomImporter/matRad_createCst.m | 13 ++++--- .../@matRad_DicomImporter/matRad_dummyCst.m | 14 ++++---- .../matRad_importDicom.m | 32 ++++++++--------- .../matRad_importDicomCt.m | 36 ++++++++++--------- .../matRad_importDicomRTDose.m | 19 +++++----- .../matRad_importDicomRTPlan.m | 21 +++++------ .../matRad_importDicomRtss.m | 22 ++++++------ .../matRad_importDicomSteeringPhotons.m | 13 ++++--- .../matRad_interpDicomDoseCube.m | 16 +++++---- .../matRad_scanDicomImportFolder.m | 15 ++++---- 12 files changed, 122 insertions(+), 119 deletions(-) diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_DicomImporter.m b/matRad/dicom/@matRad_DicomImporter/matRad_DicomImporter.m index bade5523e..9397b637a 100644 --- a/matRad/dicom/@matRad_DicomImporter/matRad_DicomImporter.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_DicomImporter.m @@ -26,7 +26,7 @@ % lists of all files allfiles; - patients; + patient; importFiles; % all the names (directories) of files, that will be imported % properties with data for import functions @@ -41,7 +41,7 @@ pln = []; resultGUI = []; - doseGrid; + ImportGrid; % bools dicomMetaBool; @@ -62,8 +62,7 @@ obj.patDir = pathToFolder; matRad_cfg = MatRad_Config.instance(); - env = matRad_getEnvironment(); - if strcmp(env,'OCTAVE') + if matRad_cfg.isOctave %Octave needs the DICOM package try pkg load dicom; @@ -78,7 +77,9 @@ obj = matRad_scanDicomImportFolder(obj); - %MatRad will also be able to separate multiple patients, but this example will only work if there's only a single patient in the folder. + % matRad_DicomImporter imports only one structure, to select + % patients and structures within a single patient the + % matRad_importDicomWidget is used ctFiles = strcmp(obj.allfiles(:,2),'CT'); rtssFiles = strcmpi(obj.allfiles(:,2),'rtstruct'); %note we can have multiple RT structure sets, matRad will always import the firstit finds @@ -86,24 +87,23 @@ rtDoseFiles = strcmpi(obj.allfiles(:,2),'rtdose'); obj.importFiles.ct = obj.allfiles(ctFiles,:);%All CT slice filepaths stored in a cell array like {'CTSlice1.dcm','CTSlice2.dcm'}; - obj.importFiles.rtss = obj.allfiles(rtssFiles,1); %will also be a cell array like {'RTStruct.dcm'}; - obj.importFiles.rtplan = obj.allfiles(rtPlanFiles,1); - obj.importFiles.rtdose = obj.allfiles(rtDoseFiles,1); + obj.importFiles.rtss = obj.allfiles(rtssFiles,:); + obj.importFiles.rtplan = obj.allfiles(rtPlanFiles,:); + obj.importFiles.rtdose = obj.allfiles(rtDoseFiles,:); for i = numel(obj.allfiles(:,1)):-1:1 if strcmp(obj.allfiles(i,2),'CT') obj.importFiles.resx = obj.allfiles{i,9}; - obj.importFiles.resy = obj.allfiles{i,10}; - obj.importFiles.resz = obj.allfiles{i,11}; %some CT dicoms do not follow the standard and use SpacingBetweenSlices + obj.importFiles.resy = obj.allfiles{i,10}; + obj.importFiles.resz = obj.allfiles{i,11}; %some CT dicoms do not follow the standard and use SpacingBetweenSlices break end end %We need to set one more variable I forgot to mention above - obj.importFiles.useDoseGrid = false; + obj.importFiles.useImportGrid = false; - end matRad_importDicom(obj) @@ -128,7 +128,7 @@ obj = matRad_dummyCst(obj) - matRad_saveImport(obj); + % matRad_saveImport(obj); end diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_calcHU.m b/matRad/dicom/@matRad_DicomImporter/matRad_calcHU.m index 8f74cf5d4..ea651ee5d 100644 --- a/matRad/dicom/@matRad_DicomImporter/matRad_calcHU.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_calcHU.m @@ -2,16 +2,16 @@ % matRad function to calculate Hounsfield units from a dicom ct % that originally uses intensity values % -% call -% obj = matRad_calcHU(obj) +% In your object, there must be a property that contains unprocessed +% dicom ct data which are stored as intensity values (IV) +% +% Output - ct structure with cube with HU % -% input -% ct: unprocessed dicom ct data which are stored as intensity values (IV) +% HU = IV * slope + intercept % -% HU = IV * slope + intercept +% call +% obj = matRad_calcHU(obj) % -% output -% ct: ct struct with cube with HU % % References % - diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_createCst.m b/matRad/dicom/@matRad_DicomImporter/matRad_createCst.m index 94bfa3571..bb5b202af 100644 --- a/matRad/dicom/@matRad_DicomImporter/matRad_createCst.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_createCst.m @@ -1,16 +1,15 @@ function obj = matRad_createCst(obj) % matRad function to create a cst struct upon dicom import % +% In your object, there must be a property that contains matlab structure +% containing information about rt structure set (generated with +% matRad_importDicomRtss and matRad_convRtssContours2Indices) +% +% Output - matRad cst structure +% % call % obj = matRad_createCst(obj) % -% input -% structures: matlab struct containing information about rt structure -% set (generated with matRad_importDicomRtss and -% matRad_convRtssContours2Indices) -% -% output -% cst: matRad cst struct % % References % - diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_dummyCst.m b/matRad/dicom/@matRad_DicomImporter/matRad_dummyCst.m index 92a4bc0f6..7534ddc4b 100644 --- a/matRad/dicom/@matRad_DicomImporter/matRad_dummyCst.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_dummyCst.m @@ -1,14 +1,14 @@ function obj = matRad_dummyCst(obj) % matRad function to create a dummy cst struct for a ct -% -% call -% obj = matRad_dummyCst(obj) % -% input -% ct: matRad ct struct +% In your object, there should be properties that contain: +% - ct structure; +% - cst structure. % -% output -% cst: matRad cst struct +% Output - matRad cst structure +% +% call +% obj = matRad_dummyCst(obj) % % References % - diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_importDicom.m b/matRad/dicom/@matRad_DicomImporter/matRad_importDicom.m index bdf363a72..e71d7a21b 100644 --- a/matRad/dicom/@matRad_DicomImporter/matRad_importDicom.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_importDicom.m @@ -2,21 +2,19 @@ function matRad_importDicom(obj) % matRad wrapper function to import a predefined set of dicom files files % into matRad's native data formats % -% call -% matRad_importDicom(obj) +% In your object, there must be properties that contain: +% - list of files to be imported. +% Optional: +% - а boolean; if you don't want to import complete DICOM information, +% set it to false. % -% input -% importFiles: list of files to be imported (will contain cts and rt -% structure set) -% dicomMetaBool: (boolean, optional) import complete dicomInfo and -% patientName +% Next matRad structures are created in the object and saved in the +% workspace: +% - ct, cst, stf, pln, resultGUI. +% *to save them as .mat file you can use matRad_importDicomWidget % -% output -% ct: matRad ct struct -% cst: matRad cst struct -% pln: matRad plan struct -% stf: matRad stf struct -% resultGUI: matRad result struct holding data for visualization in GUI +% call +% matRad_importDicom(obj) % % References % - @@ -51,18 +49,18 @@ function matRad_importDicom(obj) obj.importCT.resolution.x = str2double(obj.importFiles.resx); obj.importCT.resolution.y = str2double(obj.importFiles.resy); obj.importCT.resolution.z = str2double(obj.importFiles.resz); % [mm] / lps coordinate system -if obj.importFiles.useDoseGrid && isfield(obj.importFiles,'rtdose') +if obj.importFiles.useImportGrid && isfield(obj.importFiles,'rtdose') % get grid from dose cube if matRad_cfg.isOctave || verLessThan('matlab','9') doseInfo = dicominfo(obj.importFiles.rtdose{1,1}); else doseInfo = dicominfo(obj.importFiles.rtdose{1,1},'UseDictionaryVR',true); end - obj.doseGrid{1} = doseInfo.ImagePositionPatient(1) + doseInfo.ImageOrientationPatient(1) * ... + obj.ImportGrid{1} = doseInfo.ImagePositionPatient(1) + doseInfo.ImageOrientationPatient(1) * ... doseInfo.PixelSpacing(1) * double(0:doseInfo.Columns - 1); - obj.doseGrid{2} = doseInfo.ImagePositionPatient(2) + doseInfo.ImageOrientationPatient(5) * ... + obj.ImportGrid{2} = doseInfo.ImagePositionPatient(2) + doseInfo.ImageOrientationPatient(5) * ... doseInfo.PixelSpacing(2) * double(0:doseInfo.Rows - 1); - obj.doseGrid{3} = doseInfo.ImagePositionPatient(3) + doseInfo.GridFrameOffsetVector(:)'; + obj.ImportGrid{3} = doseInfo.ImagePositionPatient(3) + doseInfo.GridFrameOffsetVector(:)'; % get ct on grid obj = matRad_importDicomCt(obj); diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_importDicomCt.m b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomCt.m index a8027c3e0..a6cc220b9 100644 --- a/matRad/dicom/@matRad_DicomImporter/matRad_importDicomCt.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomCt.m @@ -1,22 +1,24 @@ function obj = matRad_importDicomCt(obj) % matRad function to import dicom ct data % +% In your object, there must be properties that contain: +% - list of dicom ct files; +% - resolution of the imported ct cube, i.e. this function will +% interpolate to a different resolution if desired; +% - a boolean, if you don't want to import complete dicom information set +% it false. +% Optional: +% - a priori grid specified for interpolation; +% - a boolean to turn off/on visualization. +% +% Output - matRad ct structure. +% Note that this 3D matlab array contains water euqivalent +% electron denisities. Hounsfield units are converted using a standard +% lookup table in matRad_calcWaterEqD +% % call % matRad_importDicomCt(obj) % -% input -% ct: list of dicom ct files -% resolution: resolution of the imported ct cube, i.e. this function -% will interpolate to a different resolution if desired -% dicomMetaBool: store complete dicom information if true -% doseGrid: optional: a priori grid specified for interpolation -% visBool: optional: turn on/off visualization -% -% output -% ct: matRad ct struct. Note that this 3D matlab array -% contains water euqivalent electron denisities. -% Hounsfield units are converted using a standard lookup -% table in matRad_calcWaterEqD % % References % - @@ -54,9 +56,9 @@ for i = 1:numOfSlices if matRad_cfg.isOctave || verLessThan('matlab','9') - tmpDicomInfo = dicominfo(obj.importFiles.ct{i,1}); + tmpDicomInfo = dicominfo(obj.importFiles.ct{i, 1}); else - tmpDicomInfo = dicominfo(obj.importFiles.ct{i,1},'UseDictionaryVR',true); + tmpDicomInfo = dicominfo(obj.importFiles.ct{i, 1},'UseDictionaryVR',true); end % remember relevant dicom info - do not record everything as some tags @@ -217,8 +219,8 @@ %% interpolate cube matRad_cfg.dispInfo('\nInterpolating CT cube...'); -if ~isempty(obj.doseGrid) - obj.ct = matRad_interpDicomCtCube(origCt, obj.importCT.ctInfo, obj.importCT.resolution, obj.doseGrid); +if ~isempty(obj.ImportGrid) + obj.ct = matRad_interpDicomCtCube(origCt, obj.importCT.ctInfo, obj.importCT.resolution, obj.ImportGrid); else obj.ct = matRad_interpDicomCtCube(origCt, obj.importCT.ctInfo, obj.importCT.resolution); end diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_importDicomRTDose.m b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomRTDose.m index ff5f531e3..52672fab6 100644 --- a/matRad/dicom/@matRad_DicomImporter/matRad_importDicomRTDose.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomRTDose.m @@ -1,18 +1,19 @@ function obj = matRad_importDicomRTDose(obj) % matRad function to import dicom RTDOSE data % +% In your object, there must be properties that contain: +% - ct imported by the matRad_importDicomCt function; +% - cell array of RTDose DICOM files. +% Optional: +% - matRad pln structure. +% +% Output - matRad resultGUI structure with different beams. +% Note that the summation (called plan) of the beams is named without +% subscripts, e.g. physical_Dose. +% % call % obj = matRad_importDicomRTDose(obj) % -% input -% obj.ct: ct imported by the matRad_importDicomCt function -% obj.importFiles.rtdose: cell array of RTDOSE Dicom files -% obj.pln: (optional) matRad pln struct -% -% output -% resultGUI: matRad resultGUI struct with different beams. Note that -% the summation (called plan) of the beams is named -% without subscripts, e.g. physical_Dose. % % References % - diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_importDicomRTPlan.m b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomRTPlan.m index 76594ca1e..4445ca52e 100644 --- a/matRad/dicom/@matRad_DicomImporter/matRad_importDicomRTPlan.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomRTPlan.m @@ -1,17 +1,18 @@ function obj = matRad_importDicomRTPlan(obj) % matRad function to import dicom RTPLAN data % +% In your object, there must be properties that contain: +% - ct imported by the matRad_importDicomCt function; +% - list of RTPlan Dicom files; +% - a boolean, if you don't want to import whole dicom information set it +% false. +% +% Output - matRad pln structure with meta information. +% Note that bixelWidth is determined via the importSteering function. +% % call % obj = matRad_importDicomRTPlan(obj) % -% input -% ct: ct imported by the matRad_importDicomCt function -% importFiles.rtplan: list of RTPlan Dicom files -% dicomMetaBool: import whole dicom information -% -% output -% pln: matRad pln struct with meta information. Note that -% bixelWidth is determined via the importSteering function. % % References % - @@ -87,7 +88,7 @@ % parameters not changing are stored in the first ControlPointSequence gantryAngles{i} = currBeamSeq.(ControlParam).Item_1.GantryAngle; PatientSupportAngle{i} = currBeamSeq.(ControlParam).Item_1.PatientSupportAngle; - isoCenter(i,:) = currBeamSeq.(ControlParam).Item_1.IsocenterPosition'; + isoCenter(i,:) = currBeamSeq.(ControlParam).Item_1.IsocenterPosition'; if ~ismember(isoCenter(i,1), obj.ct.x) || ~ismember(isoCenter(i,2), obj.ct.y) || ~ismember(isoCenter(i,3), obj.ct.z) isoCenter(i,:) = matRad_getIsoCenter(obj.cst, obj.ct); end @@ -156,7 +157,7 @@ obj.pln.VoxelDimentions = obj.ct.cubeDim; %if there is not special doseGrid for rtdose -if ~obj.importFiles.useDoseGrid && isfield(obj.importFiles,'rtdose') +if ~obj.importFiles.useImportGrid && isfield(obj.importFiles,'rtdose') obj.pln.propDoseCalc.doseGrid.resolution.x = obj.ct.resolution.x; obj.pln.propDoseCalc.doseGrid.resolution.y = obj.ct.resolution.y; obj.pln.propDoseCalc.doseGrid.resolution.z = obj.ct.resolution.z; diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_importDicomRtss.m b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomRtss.m index d7cb25b8f..b70bcea79 100644 --- a/matRad/dicom/@matRad_DicomImporter/matRad_importDicomRtss.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomRtss.m @@ -1,19 +1,19 @@ function obj = matRad_importDicomRtss(obj) -% matRad function to read the data of the selected dicomRT structure set file -% into a matlab struct +% matRad function to read the data of the selected dicomRT structure set +% file into a matRad structure % +% In your object, there must be properties that contain: +% - name of the rtss file; +% - meta information from the dicom ct files for sanity checks. +% Optional: +% - boolean to turn on/off visualization. +% +% Output - structure containing names, numbers, colors and coordinates +% of the polygon segmentations. +% % call % obj = matRad_importDicomRtss(obj) % -% input -% filename: name of the rtss file -% dicomInfo: meta information from the dicom ct files for sanity -% checks -% visBool: (optional) turn on/off visualization -% -% output -% structures: struct containing names, numbers, colors, and -% coordinates of the polygon segmentations % % References % - diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringPhotons.m b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringPhotons.m index 57757f8b7..c591699f2 100644 --- a/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringPhotons.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringPhotons.m @@ -1,18 +1,17 @@ function obj = matRad_importDicomSteeringPhotons(obj) % matRad function to import a matRad stf struct from dicom RTPLAN data % +% In your object, there must be a property that contains matRad pln +% structure with meta information (collimation data included) +% +% Output - matRad stf and pln structures. +% % call % obj = matRad_importDicomSteeringPhotons(obj) % -% input -% pln: matRad pln struct with meta information (collimation -% data included) -% -% output -% stf matRad stf struct -% pln matRad pln struct % % References +% - % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_interpDicomDoseCube.m b/matRad/dicom/@matRad_DicomImporter/matRad_interpDicomDoseCube.m index c53ce9255..ce9561995 100644 --- a/matRad/dicom/@matRad_DicomImporter/matRad_interpDicomDoseCube.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_interpDicomDoseCube.m @@ -1,16 +1,18 @@ function obj = matRad_interpDicomDoseCube(obj) % matRad function to interpolate a given Dicom Dose Cube dicom RTDOSE data % +% In your object, there must be properties that contain: +% - ct imported by the matRad_importDicomCt function; +% - one (of several) dose cubes which should be interpolated. +% Optional: +% - pln structure. +% +% Output - structure with different actual current dose cube and several +% meta data. +% % call % obj = matRad_interpDicomDoseCube(obj) % -% input -% ct: ct imported by the matRad_importDicomCt function -% currDose: one (of several) dose cubes which should be interpolated -% -% output -% dose: struct with different actual current dose cube and several -% meta data % % References % - diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m b/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m index e0d09108a..a8b5a45b0 100644 --- a/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m @@ -1,16 +1,17 @@ function obj = matRad_scanDicomImportFolder(obj) % matRad function to scan a folder for dicom data % +% In your object, there must be a property that contains path to the +% folder to be scanned +% +% Output: +% - matlab struct with a list of dicom files including meta +% infomation (type, series number etc.) +% - list of patients with dicom data in the folder +% % call % obj = matRad_scanDicomImportFolder(obj) % -% input -% patDir: folder to be scanned -% -% output -% allfiles: matlab struct with a list of dicom files including meta -% infomation (type, series number etc.) -% patientList: list of patients with dicom data in the folder % % References % - From 5dcd7378230d0a3fe9d0e7d6bbb2fbfd5e386e43 Mon Sep 17 00:00:00 2001 From: Raedlr Date: Wed, 4 Sep 2024 16:05:34 +0200 Subject: [PATCH 09/28] scan renamed to scanFolder --- matRad/gui/widgets/matRad_importDicomWidget.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/matRad/gui/widgets/matRad_importDicomWidget.m b/matRad/gui/widgets/matRad_importDicomWidget.m index 359012b2a..509b8deee 100644 --- a/matRad/gui/widgets/matRad_importDicomWidget.m +++ b/matRad/gui/widgets/matRad_importDicomWidget.m @@ -84,7 +84,7 @@ % Update handles structure % guidata(hObject, handles); this.handles = handles; - this.scan(hObject, eventdata) + this.scanFolder(hObject, eventdata) end end @@ -387,7 +387,7 @@ % guidata(hObject, handles); this.handles = handles; end - scan(hObject, eventdata); + scanForder(hObject, eventdata); end % H37 CHECK PATIENTNAME CALLBACK @@ -974,8 +974,8 @@ end methods (Access = private) - % SCAN FUNKTION - function this = scan(this, hObject, eventdata) + % SCANFOLDER FUNKTION + function this = scanFolder(this, hObject, eventdata) handles = this.handles; this.importer = matRad_DicomImporter(get(handles.dir_path_field,'String')); From 182194b0ddfcf7b09113e518b6bdad50566e422c Mon Sep 17 00:00:00 2001 From: Raedlr Date: Wed, 4 Sep 2024 16:48:02 +0200 Subject: [PATCH 10/28] renamed field in stf 'radiationMode' to 'matchRadiationMode' --- .../@matRad_DicomImporter/matRad_importDicomSteeringPhotons.m | 2 +- matRad/util/matRad_comparePlnStf.m | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringPhotons.m b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringPhotons.m index c591699f2..0276acd1c 100644 --- a/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringPhotons.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringPhotons.m @@ -51,7 +51,7 @@ % bixelWidth = 'field' as keyword for whole field dose calc obj.stf(i).bixelWidth = 'field'; - obj.stf(i).radiationMode = 'photons'; + obj.stf(i).matchRadiationMode = 'photons'; % only one bixel per ray and one ray for photon dose calc based on % fields diff --git a/matRad/util/matRad_comparePlnStf.m b/matRad/util/matRad_comparePlnStf.m index 203f5d2ad..48664c9c1 100644 --- a/matRad/util/matRad_comparePlnStf.m +++ b/matRad/util/matRad_comparePlnStf.m @@ -65,7 +65,7 @@ %% compare Bixel width in stf and pln if isfield(pln.propStf,'bixelWidth') && ischar(stf(1).bixelWidth) - LogVal = strcmp(stf(1).radiationMode, pln.radiationMode); + LogVal = strcmp(stf(1).matchRadiationMode, pln.radiationMode); elseif isfield(pln.propStf,'bixelWidth') && ~ischar(stf(1).bixelWidth) LogVal = logical(stf(1).bixelWidth == pln.propStf.bixelWidth); end @@ -77,7 +77,7 @@ end %% compare radiation mode in stf and pln -if ~isfield(pln,'radiationMode') || ~strcmp(stf(1).radiationMode, pln.radiationMode) +if ~isfield(pln,'radiationMode') || ~strcmp(stf(1).matchRadiationMode, pln.radiationMode) allMatch=false; msg= 'Radiation mode does not match'; return From 2d5bdfc936f651ac0ebeed8b0a5732aed154b8d7 Mon Sep 17 00:00:00 2001 From: Raedlr Date: Fri, 6 Sep 2024 17:50:12 +0200 Subject: [PATCH 11/28] matRad_importDicomParticles was adapted, but I would check it with more files --- .../matRad_DicomImporter.m | 1 - .../matRad_importDicomSteeringParticles.m | 168 +++++++++--------- .../matRad_interpDicomDoseCube.m | 1 - 3 files changed, 84 insertions(+), 86 deletions(-) diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_DicomImporter.m b/matRad/dicom/@matRad_DicomImporter/matRad_DicomImporter.m index 9397b637a..65020022e 100644 --- a/matRad/dicom/@matRad_DicomImporter/matRad_DicomImporter.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_DicomImporter.m @@ -100,7 +100,6 @@ end end - %We need to set one more variable I forgot to mention above obj.importFiles.useImportGrid = false; diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringParticles.m b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringParticles.m index 3b1e21166..7ea650e4b 100644 --- a/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringParticles.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringParticles.m @@ -1,22 +1,21 @@ function obj = matRad_importDicomSteeringParticles(obj) % matRad function to import a matRad stf struct from dicom RTPLAN data % +% In your object, there must be properties that contain: +% - ct imported by the matRad_importDicomCt function; +% - matRad pln structure with meta information; +% - name of RTPLAN DICOM file. +% +% Output - matRad stf and pln structures. +% Note: pln is input and output since pln.bixelWidth is determined here. +% % call % obj = matRad_importDicomSteeringParticles(obj) % -% input -% ct: ct imported by the matRad_importDicomCt function -% pln: matRad pln struct with meta information -% importFiles.rtplan: name of RTPLAN DICOM file -% -% output -% stf matRad stf struct -% pln: matRad pln struct. -% Note: pln is input and output since pln.bixelWidth is -% determined here. % % References % - +% % Note % not implemented - compensator. Fixed SAD. % @@ -104,33 +103,33 @@ % surfaceEntry = BeamSeq.Item_1.IonControlPointSequence.Item_1.SurfaceEntryPoint; % Preallocate stf -stf(length(BeamSeqNames)).gantryAngle = []; -stf(length(BeamSeqNames)).couchAngle = []; -stf(length(BeamSeqNames)).bixelWidth = []; -stf(length(BeamSeqNames)).radiationMode = []; -stf(length(BeamSeqNames)).SAD = []; -stf(length(BeamSeqNames)).isoCenter = []; -stf(length(BeamSeqNames)).sourcePoint_bev = []; -stf(length(BeamSeqNames)).numOfRays = []; -stf(length(BeamSeqNames)).numOfBixelsPerRay = []; -stf(length(BeamSeqNames)).totalNumOfBixels = []; -stf(length(BeamSeqNames)).ray = []; +obj.stf(length(BeamSeqNames)).gantryAngle = []; +obj.stf(length(BeamSeqNames)).couchAngle = []; +obj.stf(length(BeamSeqNames)).bixelWidth = []; +obj.stf(length(BeamSeqNames)).matchRadiationMode = []; +obj.stf(length(BeamSeqNames)).SAD = []; +obj.stf(length(BeamSeqNames)).isoCenter = []; +obj.stf(length(BeamSeqNames)).sourcePoint_bev = []; +obj.stf(length(BeamSeqNames)).numOfRays = []; +obj.stf(length(BeamSeqNames)).numOfBixelsPerRay = []; +obj.stf(length(BeamSeqNames)).totalNumOfBixels = []; +obj.stf(length(BeamSeqNames)).ray = []; for i = 1:length(BeamSeqNames) currBeamSeq = BeamSeq.(BeamSeqNames{i}); ControlPointSeq = currBeamSeq.IonControlPointSequence; - stf(i).gantryAngle = obj.pln.propStf.gantryAngles(i); - stf(i).couchAngle = obj.pln.propStf.couchAngles(i); - stf(i).bixelWidth = obj.pln.propStf.bixelWidth; - stf(i).radiationMode = obj.pln.radiationMode; + obj.stf(i).gantryAngle = obj.pln.propStf.gantryAngles(i); + obj.stf(i).couchAngle = obj.pln.propStf.couchAngles(i); + obj.stf(i).bixelWidth = obj.pln.propStf.bixelWidth; + obj.stf(i).matchRadiationMode = obj.pln.radiationMode; % there might be several SAD's, e.g. compensator? - stf(i).SAD_x = currBeamSeq.VirtualSourceAxisDistances(1); - stf(i).SAD_y = currBeamSeq.VirtualSourceAxisDistances(1); + obj.stf(i).SAD_x = currBeamSeq.VirtualSourceAxisDistances(1); + obj.stf(i).SAD_y = currBeamSeq.VirtualSourceAxisDistances(2); %stf(i).SAD = machine.meta.SAD; %we write the SAD later when we check machine match %stf(i).sourcePoint_bev = [0 -stf(i).SAD 0]; - stf(i).isoCenter = obj.pln.propStf.isoCenter(i,:); + obj.stf(i).isoCenter = obj.pln.propStf.isoCenter(i,:); % now loop over ControlPointSequences ControlPointSeqNames = fieldnames(ControlPointSeq); @@ -173,12 +172,12 @@ [RayPosTmp, ~, ic] = unique(temporarySteering(:,1:2), 'rows'); clear ray; for j = 1:size(RayPosTmp,1) - stf(i).ray(j).rayPos_bev = double([RayPosTmp(j,1) 0 RayPosTmp(j,2)]); - stf(i).ray(j).energy = []; - stf(i).ray(j).focusFWHM = []; - stf(i).ray(j).focusIx = []; - stf(i).ray(j).weight = []; - stf(i).ray(j).rangeShifter = struct(); + obj.stf(i).ray(j).rayPos_bev = double([RayPosTmp(j,1) 0 RayPosTmp(j,2)]); + obj.stf(i).ray(j).energy = []; + obj.stf(i).ray(j).focusFWHM = []; + obj.stf(i).ray(j).focusIx = []; + obj.stf(i).ray(j).weight = []; + obj.stf(i).ray(j).rangeShifter = struct(); ray(j).ID = []; ray(j).eqThickness = []; ray(j).sourceRashiDistance = []; @@ -187,9 +186,9 @@ % saves all energies and weights to their corresponding ray for j = 1:size(temporarySteering,1) k = ic(j); - stf(i).ray(k).energy = [stf(i).ray(k).energy double(temporarySteering(j,3))]; - stf(i).ray(k).focusFWHM = [stf(i).ray(k).focusFWHM double(temporarySteering(j,5))]; - stf(i).ray(k).weight = [stf(i).ray(k).weight double(temporarySteering(j,4)) / 1e6]; + obj.stf(i).ray(k).energy = [obj.stf(i).ray(k).energy double(temporarySteering(j,3))]; + obj.stf(i).ray(k).focusFWHM = [obj.stf(i).ray(k).focusFWHM double(temporarySteering(j,5))]; + obj.stf(i).ray(k).weight = [obj.stf(i).ray(k).weight double(temporarySteering(j,4)) / 1e6]; % helpers to construct something like a(:).b = c.b(:) after this % loop ray(k).ID = [ray(k).ID double(temporarySteering(j,6))]; @@ -200,36 +199,36 @@ % reassign to preserve data structure for j = 1:numel(ray) for k = 1:numel(ray(j).ID) - stf(i).ray(j).rangeShifter(k).ID = ray(j).ID(k); - stf(i).ray(j).rangeShifter(k).eqThickness = ray(j).eqThickness(k); - stf(i).ray(j).rangeShifter(k).sourceRashiDistance = stf(i).SAD - ray(j).sourceRashiDistance(k); + obj.stf(i).ray(j).rangeShifter(k).ID = ray(j).ID(k); + obj.stf(i).ray(j).rangeShifter(k).eqThickness = ray(j).eqThickness(k); + obj.stf(i).ray(j).rangeShifter(k).sourceRashiDistance = obj.stf(i).SAD - ray(j).sourceRashiDistance(k); end end % getting some information of the rays % clean up energies, so they appear only one time per energy - numOfRays = size(stf(i).ray,2); + numOfRays = size(obj.stf(i).ray,2); for l = 1:numOfRays - stf(i).ray(l).energy = unique(stf(i).ray(l).energy); + obj.stf(i).ray(l).energy = unique(obj.stf(i).ray(l).energy); end - stf(i).numOfRays = numel(stf(i).ray); + obj.stf(i).numOfRays = numel(obj.stf(i).ray); % save total number of bixels numOfBixels = 0; - for j = 1:numel(stf(i).ray) - numOfBixels = numOfBixels + numel(stf(i).ray(j).energy); - stf(i).numOfBixelsPerRay(j) = numel(stf(i).ray(j).energy); + for j = 1:numel(obj.stf(i).ray) + numOfBixels = numOfBixels + numel(obj.stf(i).ray(j).energy); + obj.stf(i).numOfBixelsPerRay(j) = numel(obj.stf(i).ray(j).energy); % w = [w stf(currBeam).ray(j).weight]; end - stf(i).totalNumOfBixels = numOfBixels; + obj.stf(i).totalNumOfBixels = numOfBixels; % get bixelwidth - bixelWidth_help = zeros(size(stf(i).ray,2),2); - for j = 1:stf(i).numOfRays - bixelWidth_help(j,1) = stf(i).ray(j).rayPos_bev(1); - bixelWidth_help(j,2) = stf(i).ray(j).rayPos_bev(3); + bixelWidth_help = zeros(size(obj.stf(i).ray,2),2); + for j = 1:obj.stf(i).numOfRays + bixelWidth_help(j,1) = obj.stf(i).ray(j).rayPos_bev(1); + bixelWidth_help(j,2) = obj.stf(i).ray(j).rayPos_bev(3); end bixelWidth_help1 = unique(round(1e3*bixelWidth_help(:,1))/1e3,'sorted'); bixelWidth_help2 = unique(round(1e3*bixelWidth_help(:,2))/1e3,'sorted'); @@ -237,9 +236,9 @@ bixelWidth = unique([unique(diff(bixelWidth_help1))' unique(diff(bixelWidth_help2))']); if numel(bixelWidth) == 1 - stf(i).bixelWidth = bixelWidth; + obj.stf(i).bixelWidth = bixelWidth; else - stf(i).bixelWidth = NaN; + obj.stf(i).bixelWidth = NaN; end end @@ -248,15 +247,16 @@ % if a machine is given, check if we can exactly match the plan with % the machine details machineNotMatching = false; -if ~isempty(machine) + +if ~isempty(obj.pln.machine) matRad_cfg.dispInfo('Machine provided! Checking for exact steering match within RTPlan...'); - for i = 1:numel(stf) - for j = 1:stf(i).numOfRays + for i = 1:numel(obj.stf) + for j = 1:obj.stf(i).numOfRays % loop over all energies - numOfEnergy = length(stf(i).ray(j).energy); + numOfEnergy = length(obj.stf(i).ray(j).energy); for k = 1:numOfEnergy - energyTemp = stf(i).ray(j).energy(k); - focusFWHM = stf(i).ray(j).focusFWHM(k); + energyTemp = obj.stf(i).ray(j).energy(k); + focusFWHM = obj.stf(i).ray(j).focusFWHM(k); energyIndex = find(abs([machine.data(:).energy]-energyTemp)<10^-2); if isempty(energyIndex) machineNotMatching = true; @@ -284,30 +284,30 @@ matRad_cfg.dispInfo('not matching!\n'); matRad_cfg.dispWarning('The given machine does not match the steering info found in RTPlan. matRad will generate an stf, but it will be incompatible with the given machine and most likely not directly be usable in dose calculation!'); - for i = 1:numel(stf) - stf(i).SAD = mean([stf(i).SAD_x stf(i).SAD_y]); + for i = 1:numel(obj.stf) + obj.stf(i).SAD = mean([obj.stf(i).SAD_x obj.stf(i).SAD_y]); end else matRad_cfg.dispInfo('matching!\n'); matRad_cfg.dispInfo('Formatting stf for use with given machine...'); - for i = 1:numel(stf) - stf(i).SAD = machine.meta.SAD; - for j = 1:stf(i).numOfRays + for i = 1:numel(obj.stf) + obj.stf(i).SAD = machine.meta.SAD; + for j = 1:obj.stf(i).numOfRays % loop over all energies - numOfEnergy = length(stf(i).ray(j).energy); + numOfEnergy = length(obj.stf(i).ray(j).energy); for k = 1:numOfEnergy %If a corresponding machine was found, check assignment here if ~isempty(machine) - energyTemp = stf(i).ray(j).energy(k); - focusFWHM = stf(i).ray(j).focusFWHM(k); - energyIndex = find(abs([machine.data(:).energy]-energyTempenergyTemp)<10^-2); + energyTemp = obj.stf(i).ray(j).energy(k); + focusFWHM = obj.stf(i).ray(j).focusFWHM(k); + energyIndex = find(abs([machine.data(:).energy]-energyTemp)<10^-2); focusIndex = find(abs([machine.data(energyIndex).initFocus.SisFWHMAtIso] - focusFWHM )< 10^-3); - stf(i).ray(j).energy(k) = machine.data(energyIndex).energy; - stf(i).ray(j).focusIx(k) = focusIndex; - stf(i).ray(j).focusFWHM(k) = machine.data(energyIndex).initFocus.SisFWHMAtIso(stf(i).ray(j).focusIx(k)); + obj.stf(i).ray(j).energy(k) = machine.data(energyIndex).energy; + obj.stf(i).ray(j).focusIx(k) = focusIndex; + obj.stf(i).ray(j).focusFWHM(k) = machine.data(energyIndex).initFocus.SisFWHMAtIso(obj.stf(i).ray(j).focusIx(k)); end end end @@ -318,36 +318,36 @@ end %% Finalize geometry -for i = 1:numel(stf) +for i = 1:numel(obj.stf) % coordinate transformation with rotation matrix. % use transpose matrix because we are working with row vectors - rotMat_vectors_T = transpose(matRad_getRotationMatrix(stf(i).gantryAngle,stf(i).couchAngle)); + rotMat_vectors_T = transpose(matRad_getRotationMatrix(obj.stf(i).gantryAngle,obj.stf(i).couchAngle)); % set source point using (average/machine) SAD - stf(i).sourcePoint_bev = [0 -stf(i).SAD 0]; + obj.stf(i).sourcePoint_bev = [0 -obj.stf(i).SAD 0]; % Rotated Source point (1st gantry, 2nd couch) - stf(i).sourcePoint = stf(i).sourcePoint_bev*rotMat_vectors_T; + obj.stf(i).sourcePoint = obj.stf(i).sourcePoint_bev*rotMat_vectors_T; % Save ray and target position in lps system. - for j = 1:stf(i).numOfRays - stf(i).ray(j).targetPoint_bev = [2*stf(i).ray(j).rayPos_bev(1) stf(i).SAD 2*stf(i).ray(j).rayPos_bev(3)]; - stf(i).ray(j).rayPos = stf(i).ray(j).rayPos_bev*rotMat_vectors_T; - stf(i).ray(j).targetPoint = stf(i).ray(j).targetPoint_bev*rotMat_vectors_T; + for j = 1:obj.stf(i).numOfRays + obj.stf(i).ray(j).targetPoint_bev = [2*obj.stf(i).ray(j).rayPos_bev(1) obj.stf(i).SAD 2*obj.stf(i).ray(j).rayPos_bev(3)]; + obj.stf(i).ray(j).rayPos = obj.stf(i).ray(j).rayPos_bev*rotMat_vectors_T; + obj.stf(i).ray(j).targetPoint = obj.stf(i).ray(j).targetPoint_bev*rotMat_vectors_T; end % book keeping & calculate focus index - for j = 1:stf(i).numOfRays - stf(i).numOfBixelsPerRay(j) = numel([stf(i).ray(j).energy]); + for j = 1:obj.stf(i).numOfRays + obj.stf(i).numOfBixelsPerRay(j) = numel([obj.stf(i).ray(j).energy]); end - stf(i).timeStamp = datetime('now'); + obj.stf(i).timeStamp = datetime('now'); end -if any(isnan([stf(:).bixelWidth])) || numel(unique([stf(:).bixelWidth])) > 1 +if any(isnan([obj.stf(:).bixelWidth])) || numel(unique([obj.stf(:).bixelWidth])) > 1 obj.pln.propStf.bixelWidth = NaN; else - obj.pln.propStf.bixelWidth = stf(1).bixelWidth; + obj.pln.propStf.bixelWidth = obj.stf(1).bixelWidth; end end diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_interpDicomDoseCube.m b/matRad/dicom/@matRad_DicomImporter/matRad_interpDicomDoseCube.m index ce9561995..5370f7b45 100644 --- a/matRad/dicom/@matRad_DicomImporter/matRad_interpDicomDoseCube.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_interpDicomDoseCube.m @@ -46,7 +46,6 @@ % read the dosefile itself dosedata = dicomread(dosefile); obj.importRTDose.dose.cube = double(dosedata); -% importRTDose.dose.cube = single(dosedata); % give it an internal name % obj.dose.internalName = obj.currDose{12};%????? From 5bbd607513b49f0c66c080d8b825d5ae64229d21 Mon Sep 17 00:00:00 2001 From: Raedlr Date: Thu, 12 Sep 2024 12:50:58 +0200 Subject: [PATCH 12/28] Scan funktion was adapted for the case whenthe file contains slices with different thicknesses --- .../@matRad_DicomImporter/matRad_DicomImporter.m | 6 +++--- .../matRad_scanDicomImportFolder.m | 14 ++++++++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_DicomImporter.m b/matRad/dicom/@matRad_DicomImporter/matRad_DicomImporter.m index 65020022e..43625e31e 100644 --- a/matRad/dicom/@matRad_DicomImporter/matRad_DicomImporter.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_DicomImporter.m @@ -93,9 +93,9 @@ for i = numel(obj.allfiles(:,1)):-1:1 if strcmp(obj.allfiles(i,2),'CT') - obj.importFiles.resx = obj.allfiles{i,9}; - obj.importFiles.resy = obj.allfiles{i,10}; - obj.importFiles.resz = obj.allfiles{i,11}; %some CT dicoms do not follow the standard and use SpacingBetweenSlices + obj.importFiles.resx = cellstr(obj.allfiles{i,9}); + obj.importFiles.resy = cellstr(obj.allfiles{i,10}); + obj.importFiles.resz = cellstr(obj.allfiles{i,11}); %some CT dicoms do not follow the standard and use SpacingBetweenSlices break end end diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m b/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m index a8b5a45b0..f18d9dc13 100644 --- a/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m @@ -54,9 +54,9 @@ % Arrays of slice locations to find z resolution % if it is not given initially in the files - LocationsArray1 = ones(1, numel(obj.allfiles(:,1))); - LocationsArray = LocationsArray1*1000; + LocationsArray = NaN(1, numel(obj.allfiles(:,1))); ThBool = []; + numOfFiles = numel(obj.allfiles(:,1)); h = waitbar(0,'Please wait...','Color',matRad_cfg.gui.backgroundColor,'DefaultTextColor',matRad_cfg.gui.textColor); matRad_applyThemeToWaitbar(h); @@ -207,13 +207,19 @@ % Filtration, getting and assigning z resolution to all CT files FiltredLocArray = unique(LocationsArray); - FiltredLocArray(end) = []; + Thickness = rmmissing(unique(diff(FiltredLocArray))); numOfFiles = numel(obj.allfiles(:,1)); + + if numel(Thickness) > 1 + msgbox('Slices are not equidistant! Slice thickness of the first slice was taken as the value for all slices.', 'Warning'); + FirstSliceThikness = rmmissing(unique(diff(FiltredLocArray))); + Thickness = FirstSliceThikness; + end if isempty(ThBool) for i = numOfFiles:-1:1 if strcmp(obj.allfiles{i,2},'CT') - obj.allfiles{i,11} = num2str(unique(diff(FiltredLocArray))); + obj.allfiles{i,11} = num2str(Thickness); end end end From 99bff6989f01e5ee3a972a92cc7691f258ee8805 Mon Sep 17 00:00:00 2001 From: Raedlr Date: Wed, 2 Oct 2024 09:54:05 +0200 Subject: [PATCH 13/28] fixed bug --- .../matRad_DicomImporter.m | 6 +++--- .../matRad_scanDicomImportFolder.m | 21 ++++++++++++++----- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_DicomImporter.m b/matRad/dicom/@matRad_DicomImporter/matRad_DicomImporter.m index 43625e31e..65020022e 100644 --- a/matRad/dicom/@matRad_DicomImporter/matRad_DicomImporter.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_DicomImporter.m @@ -93,9 +93,9 @@ for i = numel(obj.allfiles(:,1)):-1:1 if strcmp(obj.allfiles(i,2),'CT') - obj.importFiles.resx = cellstr(obj.allfiles{i,9}); - obj.importFiles.resy = cellstr(obj.allfiles{i,10}); - obj.importFiles.resz = cellstr(obj.allfiles{i,11}); %some CT dicoms do not follow the standard and use SpacingBetweenSlices + obj.importFiles.resx = obj.allfiles{i,9}; + obj.importFiles.resy = obj.allfiles{i,10}; + obj.importFiles.resz = obj.allfiles{i,11}; %some CT dicoms do not follow the standard and use SpacingBetweenSlices break end end diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m b/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m index f18d9dc13..2ed76a972 100644 --- a/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m @@ -209,11 +209,23 @@ FiltredLocArray = unique(LocationsArray); Thickness = rmmissing(unique(diff(FiltredLocArray))); numOfFiles = numel(obj.allfiles(:,1)); - + if numel(Thickness) > 1 - msgbox('Slices are not equidistant! Slice thickness of the first slice was taken as the value for all slices.', 'Warning'); - FirstSliceThikness = rmmissing(unique(diff(FiltredLocArray))); - Thickness = FirstSliceThikness; + msgbox('Slices are not equidistant! CT will be interpolate with the lowest value of resolution is given by CT slices.', 'Warning'); + Thickness = Thickness(1); % min thickness + ThBool = []; % in this case we will also create ct cube with the same resolution for all slices + PriorThicknesses = flip(rmmissing(diff(FiltredLocArray))); % prior values of spacing, which are needed for interpolation + Counter = 0; + + for i = 1:numOfFiles + if strcmp(obj.allfiles{i,2},'CT') && (i - Counter <= numel(PriorThicknesses)) + obj.allfiles{i,16} = num2str(PriorThicknesses(i - Counter)); + else + Counter = Counter + 1; + obj.allfiles{i,16} = NaN; + end + end + end if isempty(ThBool) @@ -223,7 +235,6 @@ end end end - close(h) if ~isempty(obj.allfiles) From 0256b0112b08932050214034ddad2ab890c3db54 Mon Sep 17 00:00:00 2001 From: Raedlr Date: Wed, 2 Oct 2024 11:14:34 +0200 Subject: [PATCH 14/28] some changes in comparePlnStf --- .../matRad_importDicomSteeringPhotons.m | 2 +- matRad/util/matRad_comparePlnStf.m | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringPhotons.m b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringPhotons.m index 0276acd1c..c591699f2 100644 --- a/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringPhotons.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringPhotons.m @@ -51,7 +51,7 @@ % bixelWidth = 'field' as keyword for whole field dose calc obj.stf(i).bixelWidth = 'field'; - obj.stf(i).matchRadiationMode = 'photons'; + obj.stf(i).radiationMode = 'photons'; % only one bixel per ray and one ray for photon dose calc based on % fields diff --git a/matRad/util/matRad_comparePlnStf.m b/matRad/util/matRad_comparePlnStf.m index 48664c9c1..927813252 100644 --- a/matRad/util/matRad_comparePlnStf.m +++ b/matRad/util/matRad_comparePlnStf.m @@ -64,20 +64,14 @@ end %% compare Bixel width in stf and pln -if isfield(pln.propStf,'bixelWidth') && ischar(stf(1).bixelWidth) - LogVal = strcmp(stf(1).matchRadiationMode, pln.radiationMode); -elseif isfield(pln.propStf,'bixelWidth') && ~ischar(stf(1).bixelWidth) - LogVal = logical(stf(1).bixelWidth == pln.propStf.bixelWidth); -end - -if ~isfield(pln.propStf,'bixelWidth') || ~LogVal +if (ischar(stf(1).bixelWidth) && ~isequal(stf(1).bixelWidth,pln.propStf.bixelWidth) || (~ischar(stf(1).bixelWidth) && ~strcmp(stf(1).bixelWidth,'field'))) allMatch=false; msg= 'Bixel width does not match'; return end %% compare radiation mode in stf and pln -if ~isfield(pln,'radiationMode') || ~strcmp(stf(1).matchRadiationMode, pln.radiationMode) +if ~isfield(pln,'radiationMode') || ~strcmp(stf(1).radiationMode, pln.radiationMode) allMatch=false; msg= 'Radiation mode does not match'; return From 142b82abf9515172e1cba1b7c56187f7f8d1b36d Mon Sep 17 00:00:00 2001 From: Raedlr Date: Wed, 2 Oct 2024 14:18:48 +0200 Subject: [PATCH 15/28] correction --- matRad/util/matRad_comparePlnStf.m | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/matRad/util/matRad_comparePlnStf.m b/matRad/util/matRad_comparePlnStf.m index 927813252..3b5bd8eee 100644 --- a/matRad/util/matRad_comparePlnStf.m +++ b/matRad/util/matRad_comparePlnStf.m @@ -64,7 +64,14 @@ end %% compare Bixel width in stf and pln -if (ischar(stf(1).bixelWidth) && ~isequal(stf(1).bixelWidth,pln.propStf.bixelWidth) || (~ischar(stf(1).bixelWidth) && ~strcmp(stf(1).bixelWidth,'field'))) +bixelMatch = false; +if isnumeric(stf(1).bixelWidth) && isequal(stf(1).bixelWidth,pln.propStf.bixelWidth) + bixelMatch = true; +elseif ischar(stf(1).bixelWidth) && strcmp(stf(1).bixelWidth,'field') + bixelMatch = true; +end + +if ~bixelMatch allMatch=false; msg= 'Bixel width does not match'; return From c948a73eec10a3e524725140bab58c714e4bddaf Mon Sep 17 00:00:00 2001 From: Raedlr Date: Wed, 2 Oct 2024 15:00:09 +0200 Subject: [PATCH 16/28] radiationMode renamed back --- .../matRad_importDicomSteeringParticles.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringParticles.m b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringParticles.m index 7ea650e4b..fa3ed34b8 100644 --- a/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringParticles.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringParticles.m @@ -106,7 +106,7 @@ obj.stf(length(BeamSeqNames)).gantryAngle = []; obj.stf(length(BeamSeqNames)).couchAngle = []; obj.stf(length(BeamSeqNames)).bixelWidth = []; -obj.stf(length(BeamSeqNames)).matchRadiationMode = []; +obj.stf(length(BeamSeqNames)).radiationMode = []; obj.stf(length(BeamSeqNames)).SAD = []; obj.stf(length(BeamSeqNames)).isoCenter = []; obj.stf(length(BeamSeqNames)).sourcePoint_bev = []; @@ -123,7 +123,7 @@ obj.stf(i).gantryAngle = obj.pln.propStf.gantryAngles(i); obj.stf(i).couchAngle = obj.pln.propStf.couchAngles(i); obj.stf(i).bixelWidth = obj.pln.propStf.bixelWidth; - obj.stf(i).matchRadiationMode = obj.pln.radiationMode; + obj.stf(i).radiationMode = obj.pln.radiationMode; % there might be several SAD's, e.g. compensator? obj.stf(i).SAD_x = currBeamSeq.VirtualSourceAxisDistances(1); obj.stf(i).SAD_y = currBeamSeq.VirtualSourceAxisDistances(2); From 4ae1815fdd40ea0a3b802eafe2ca82c3139d2dc7 Mon Sep 17 00:00:00 2001 From: Raedlr Date: Wed, 2 Oct 2024 16:31:30 +0200 Subject: [PATCH 17/28] added test funktion for matRad_DicomImporter constructor --- test/importTest/test_DicomImporter.m | 65 ++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 test/importTest/test_DicomImporter.m diff --git a/test/importTest/test_DicomImporter.m b/test/importTest/test_DicomImporter.m new file mode 100644 index 000000000..72bf29236 --- /dev/null +++ b/test/importTest/test_DicomImporter.m @@ -0,0 +1,65 @@ +function test_suite = test_DicomImporter +%The output should always be test_suite, and the function name the same as +%your file name + +%% Header +% The header is required to be in this format for automatic test collection +% by MOxUnit + +%To collect all tests defined below, this is needed in newer Matlab +%versions. test_functions will collect function handles to below test +%functions +test_functions=localfunctions(); + +% This will initialize the test suite, i.e., take the functions from +% test_functions, check if they contain "test", convert them into a MOxUnit +% Test Case, and add them to the test-runner +initTestSuite; + +%% Custom Tests +% Tests use assert*-like Functions to check outputs etc: +% assertTrue(a) - a is true +% assertFalse(a) - a is false +% assertEqual(a,b) - a and be are equal (isequal) +% assertElementsAlmostEqual(a,b) - numerical test for all vector / matrix elements. Has Additional arguments for absolute / relative tolerance +% assertVectorsAlmostEqual(a,b) - numerical test using vector norm +% assertExceptionThrown(f,id) - test if exception of id is thrown. Take care of Octave issues with exception id (or don't provide id) +% Check MOxUnit for more information or look at other tests + +function test_DicomImporter_classattachment + path = 'C:\Users\r114m\Documents\GitHub\matRad\userdata\dicomExport\HEAD_AND_NECK'; + h = matRad_DicomImporter(path); + assertTrue(isa(h,'matRad_DicomImporter')); + assertTrue(isa(h,'handle')); + +function test_DicomImporter_loadFiles + path = 'C:\Users\r114m\Documents\GitHub\matRad\userdata\dicomExport\HEAD_AND_NECK'; + h = matRad_DicomImporter(path); + assertTrue(isequal(h.patDir, path)); + assertTrue(~isempty(h.allfiles)); + + NumberOfCtFiles = numel(nonzeros(strcmp(h.allfiles(:,2),'CT'))); + NumberOfRtssFiles = numel(nonzeros(strcmpi(h.allfiles(:,2),'rtstruct'))); + NumberOfRtPlanFiles = numel(nonzeros(strcmpi(h.allfiles(:,2),'rtplan'))); + NumberOfRtDoseFiles = numel(nonzeros(strcmpi(h.allfiles(:,2),'rtdose'))); + + assertTrue(isequal(NumberOfCtFiles, size(h.importFiles.ct, 1))); + assertTrue(isequal(NumberOfRtssFiles, size(h.importFiles.rtss, 1))); + assertTrue(isequal(NumberOfRtPlanFiles, size(h.importFiles.rtplan,1))); + assertTrue(isequal(NumberOfRtDoseFiles, size(h.importFiles.rtdose,1))); + + resBool = true; + if isempty(h.importFiles.resx) || isempty(h.importFiles.resy) || isempty(h.importFiles.resz) + resBool = false; + end + assertTrue(resBool); + + + + + + + + + + From 806a94a5fb3db9f9c2da68cd2bd6a5ebbb0486cb Mon Sep 17 00:00:00 2001 From: Raedlr Date: Wed, 2 Oct 2024 17:31:00 +0200 Subject: [PATCH 18/28] removed unnecessary lines and added check for field presence 'bixelWidth' --- matRad/gui/widgets/matRad_importDicomWidget.m | 20 +++++-------------- matRad/util/matRad_comparePlnStf.m | 10 ++++++---- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/matRad/gui/widgets/matRad_importDicomWidget.m b/matRad/gui/widgets/matRad_importDicomWidget.m index 509b8deee..dd55e339c 100644 --- a/matRad/gui/widgets/matRad_importDicomWidget.m +++ b/matRad/gui/widgets/matRad_importDicomWidget.m @@ -149,11 +149,7 @@ end set(handles.resx_edit,'String',this.importer.importFiles.resx); set(handles.resy_edit,'String',this.importer.importFiles.resy); - if numel(this.importer.importFiles.resz) > 1 - set(handles.resz_edit,'String','not equi'); - else - set(handles.resz_edit,'String',this.importer.importFiles.resz); - end + set(handles.resz_edit,'String',this.importer.importFiles.resz); % Update handles structure % guidata(hObject, handles); this.handles = handles; @@ -319,11 +315,8 @@ % retrieve and display resolution for DICOM ct cube set(handles.resx_edit,'String',this.importer.importFiles.resx); set(handles.resy_edit,'String',this.importer.importFiles.resy); - if numel(res_z) > 1 - set(handles.resz_edit,'String','not equi'); - else - set(handles.resz_edit,'String',this.importer.importFiles.resz); - end + set(handles.resz_edit,'String',this.importer.importFiles.resz); + end @@ -476,11 +469,8 @@ end set(handles.resx_edit,'String',res_x); set(handles.resy_edit,'String',res_y); - if numel(res_z) > 1 - set(handles.resz_edit,'String','not equi'); - else - set(handles.resz_edit,'String',res_z); - end + set(handles.resz_edit,'String',res_z); + end this.handles = handles; diff --git a/matRad/util/matRad_comparePlnStf.m b/matRad/util/matRad_comparePlnStf.m index 3b5bd8eee..6447019d8 100644 --- a/matRad/util/matRad_comparePlnStf.m +++ b/matRad/util/matRad_comparePlnStf.m @@ -65,10 +65,12 @@ %% compare Bixel width in stf and pln bixelMatch = false; -if isnumeric(stf(1).bixelWidth) && isequal(stf(1).bixelWidth,pln.propStf.bixelWidth) - bixelMatch = true; -elseif ischar(stf(1).bixelWidth) && strcmp(stf(1).bixelWidth,'field') - bixelMatch = true; +if isfield(pln.propStf,'bixelWidth') && isfield(stf(1),'bixelWidth') + if isnumeric(stf(1).bixelWidth) && isequal(stf(1).bixelWidth,pln.propStf.bixelWidth) + bixelMatch = true; + elseif ischar(stf(1).bixelWidth) && strcmp(stf(1).bixelWidth,'field') + bixelMatch = true; + end end if ~bixelMatch From 66825be716018213a58c14e610b9ea8dabe4ed85 Mon Sep 17 00:00:00 2001 From: Niklas Wahl Date: Thu, 3 Oct 2024 00:16:38 +0200 Subject: [PATCH 19/28] * added helper function to use temporary directory * temorary directory used for dicom export and reimport * removed GUI elements from folder scanning --- .../matRad_DicomImporter.m | 10 +++--- .../matRad_scanDicomImportFolder.m | 9 +++--- .../test_dicomIO.m} | 31 +++++++++++++++---- test/helper_temporaryFolder.m | 31 +++++++++++++++++++ 4 files changed, 64 insertions(+), 17 deletions(-) rename test/{importTest/test_DicomImporter.m => dicom/test_dicomIO.m} (68%) create mode 100644 test/helper_temporaryFolder.m diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_DicomImporter.m b/matRad/dicom/@matRad_DicomImporter/matRad_DicomImporter.m index 65020022e..c870cb100 100644 --- a/matRad/dicom/@matRad_DicomImporter/matRad_DicomImporter.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_DicomImporter.m @@ -70,12 +70,10 @@ matRad_cfg.dispError('The DICOM export requires the octave-forge package "dicom"!\n'); end end - disp(pathToFolder) - if isempty(pathToFolder) - obj.patDir = uigetdir(pwd); - end - - obj = matRad_scanDicomImportFolder(obj); + + obj.patDir = pathToFolder; + + obj.matRad_scanDicomImportFolder(); % matRad_DicomImporter imports only one structure, to select % patients and structures within a single patient the diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m b/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m index 2ed76a972..84dcbc9ec 100644 --- a/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m @@ -211,7 +211,7 @@ numOfFiles = numel(obj.allfiles(:,1)); if numel(Thickness) > 1 - msgbox('Slices are not equidistant! CT will be interpolate with the lowest value of resolution is given by CT slices.', 'Warning'); + matRad_cfg.dispWarning('Slices are not equidistant! CT will be interpolate with the lowest value of resolution is given by CT slices.'); Thickness = Thickness(1); % min thickness ThBool = []; % in this case we will also create ct cube with the same resolution for all slices PriorThicknesses = flip(rmmissing(diff(FiltredLocArray))); % prior values of spacing, which are needed for interpolation @@ -241,16 +241,15 @@ obj.patient = unique(obj.allfiles(:,3)); if isempty(obj.patient) - msgbox('No patient found with DICOM CT _and_ RT structure set in patient directory!', 'Error','error'); + matRad_cfg.dispError('No patient found with DICOM CT _and_ RT structure set in patient directory!'); end else - msgbox('No DICOM files found in patient directory!', 'Error','error'); + matRad_cfg.dispError('No DICOM files found in patient directory!'); %h.WindowStyle = 'Modal'; %error('No DICOM files found in patient directory'); end else - msgbox('Search folder empty!', 'Error','error'); - + matRad_cfg.dispError('Folder is empty!'); end clear warnDlgDICOMtagShown; diff --git a/test/importTest/test_DicomImporter.m b/test/dicom/test_dicomIO.m similarity index 68% rename from test/importTest/test_DicomImporter.m rename to test/dicom/test_dicomIO.m index 72bf29236..b763183bd 100644 --- a/test/importTest/test_DicomImporter.m +++ b/test/dicom/test_dicomIO.m @@ -1,4 +1,4 @@ -function test_suite = test_DicomImporter +function test_suite = test_dicomIO %The output should always be test_suite, and the function name the same as %your file name @@ -26,14 +26,33 @@ % assertExceptionThrown(f,id) - test if exception of id is thrown. Take care of Octave issues with exception id (or don't provide id) % Check MOxUnit for more information or look at other tests -function test_DicomImporter_classattachment - path = 'C:\Users\r114m\Documents\GitHub\matRad\userdata\dicomExport\HEAD_AND_NECK'; - h = matRad_DicomImporter(path); - assertTrue(isa(h,'matRad_DicomImporter')); +function test_DicomImporter_emptyfolder + path = helper_temporaryFolder('dicomIOtest'); + assertExceptionThrown(@() matRad_DicomImporter(path)); + +function test_DicomExporter + testpatient = load('photons_testData.mat'); + path = helper_temporaryFolder('dicomIOtest'); + + dummyResultGUI = struct('physicalDose',rand(testpatient.ct.cubeDim),'w',ones(sum([testpatient.stf.totalNumOfBixels]),1)); + for i = 1:numel(testpatient.stf) + dummyResultGUI.(['physicalDose_beam' num2str(i)]) = rand(testpatient.ct.cubeDim); + end + dummyResultGUI.wUnsequenced = dummyResultGUI.w; + + h = matRad_DicomExporter(testpatient.ct,testpatient.cst,testpatient.pln,testpatient.stf,dummyResultGUI); + assertTrue(isa(h,'matRad_DicomExporter')); assertTrue(isa(h,'handle')); + h.dicomDir = path; + h.matRad_exportDicom(); + + dircontents = dir([path filesep '*.dcm']); + assertTrue(numel(dircontents) > 0) + + function test_DicomImporter_loadFiles - path = 'C:\Users\r114m\Documents\GitHub\matRad\userdata\dicomExport\HEAD_AND_NECK'; + path = helper_temporaryFolder('dicomIOtest',false); h = matRad_DicomImporter(path); assertTrue(isequal(h.patDir, path)); assertTrue(~isempty(h.allfiles)); diff --git a/test/helper_temporaryFolder.m b/test/helper_temporaryFolder.m new file mode 100644 index 000000000..297ffc229 --- /dev/null +++ b/test/helper_temporaryFolder.m @@ -0,0 +1,31 @@ +function [tmpPath,status] = helper_temporaryFolder(folderName,clearIfExists) +%helper_temporaryFolder Creates a temporary folder for test data in the +% users temporary systemdirectory. + +if nargin < 2 + clearIfExists = true; +end + +tmpPath = fullfile(tempdir(),folderName); + +if isfolder(tmpPath) && clearIfExists + status = rmdir(tmpPath,'s'); + if status + status = mkdir(tmpPath); + else + status = double(isfolder(tmpPath)); + end +elseif ~isfolder(tmpPath) + status = mkdir(tmpPath); +else + status = 1; +end + +if ~status + tmpPath = pwd(); + warning('temporary directory invalid - using wokring directory!'); +end + + +end + From f616715ec113017bf234b60064f462c2a75449f0 Mon Sep 17 00:00:00 2001 From: Niklas Wahl Date: Thu, 3 Oct 2024 00:22:17 +0200 Subject: [PATCH 20/28] add actual export test --- matRad/dicom/matRad_interpDicomCtCube.m | 4 +--- test/dicom/test_dicomIO.m | 6 +++++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/matRad/dicom/matRad_interpDicomCtCube.m b/matRad/dicom/matRad_interpDicomCtCube.m index 171e1bb96..50cb061c9 100644 --- a/matRad/dicom/matRad_interpDicomCtCube.m +++ b/matRad/dicom/matRad_interpDicomCtCube.m @@ -99,9 +99,7 @@ % set the return ctCube to the original Ct information because % there is no interpolation needed - interpCt.cubeIV{1} = origCt; - cfg = MatRad_Config.instance(); - + interpCt.cubeIV{1} = origCt; else % calculate new grid for the interpolation, % grid equals a range of the first pixel, to the original diff --git a/test/dicom/test_dicomIO.m b/test/dicom/test_dicomIO.m index b763183bd..724f99ed0 100644 --- a/test/dicom/test_dicomIO.m +++ b/test/dicom/test_dicomIO.m @@ -51,7 +51,7 @@ assertTrue(numel(dircontents) > 0) -function test_DicomImporter_loadFiles +function test_DicomImporter_construct_with_path_and_file_load path = helper_temporaryFolder('dicomIOtest',false); h = matRad_DicomImporter(path); assertTrue(isequal(h.patDir, path)); @@ -73,7 +73,11 @@ end assertTrue(resBool); +function test_DicomImporter_Import + path = helper_temporaryFolder('dicomIOtest',false); + h = matRad_DicomImporter(path); + h.matRad_importDicom(); From 2a3c89557a7393e90e96c4a3dcd9ea4cb14d5674 Mon Sep 17 00:00:00 2001 From: Niklas Wahl Date: Thu, 3 Oct 2024 00:24:39 +0200 Subject: [PATCH 21/28] use temporary folder helper for example tests --- test/autoExampleTest/test_examples.m | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/test/autoExampleTest/test_examples.m b/test/autoExampleTest/test_examples.m index 9b2d783ba..378ef7b56 100644 --- a/test/autoExampleTest/test_examples.m +++ b/test/autoExampleTest/test_examples.m @@ -41,11 +41,7 @@ [folders,names,exts] = cellfun(@fileparts,exampleScripts,'UniformOutput',false); %Create temporary example test folder -tmpExampleTestFolder = tempdir(); -tmpExampleTestFolder = fullfile(tmpExampleTestFolder,'exampleTest'); -if ~exist(tmpExampleTestFolder,'dir') - mkdir(tmpExampleTestFolder); -end +tmpExampleTestFolder = helper_temporaryFolder('exampleTest',true); addpath(tmpExampleTestFolder); newFolders = cell(size(folders)); [newFolders{:}] = deal(tmpExampleTestFolder); @@ -80,12 +76,6 @@ %test_functions{testIx,1} = testfun; end -try - rmdir(exampleTestFolder,'s'); -catch - warning('Could not delete temporary example test folder'); -end - %initTestSuite; %We need to manually set up the test_suite From 1dfb220e9a50ee54552a95822fa4ec6ec185c9b9 Mon Sep 17 00:00:00 2001 From: Amit Bennan Date: Tue, 8 Oct 2024 17:00:19 +0200 Subject: [PATCH 22/28] enforcing dicom standard of storing slice thickness as string --- matRad/dicom/@matRad_DicomExporter/matRad_exportDicomCt.m | 2 +- .../dicom/@matRad_DicomExporter/matRad_exportDicomRTDoses.m | 2 +- .../@matRad_DicomExporter/matRad_exportDicomRTStruct.m | 6 +++--- matRad/dicom/@matRad_DicomImporter/matRad_importDicomCt.m | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/matRad/dicom/@matRad_DicomExporter/matRad_exportDicomCt.m b/matRad/dicom/@matRad_DicomExporter/matRad_exportDicomCt.m index 956bac464..9a2968b62 100644 --- a/matRad/dicom/@matRad_DicomExporter/matRad_exportDicomCt.m +++ b/matRad/dicom/@matRad_DicomExporter/matRad_exportDicomCt.m @@ -81,7 +81,7 @@ obj.ct = ct; %Since we are exporting HU directly --> no rescaling in any case -meta.SliceThickness = ct.resolution.z; +meta.SliceThickness = num2str(ct.resolution.z); meta.PixelSpacing = [ct.resolution.y; ct.resolution.x]; meta.ImageOrientationPatient = [1;0;0;0;1;0]; %lps meta.RescaleType = 'HU'; diff --git a/matRad/dicom/@matRad_DicomExporter/matRad_exportDicomRTDoses.m b/matRad/dicom/@matRad_DicomExporter/matRad_exportDicomRTDoses.m index d470e8a57..ed420dc0e 100644 --- a/matRad/dicom/@matRad_DicomExporter/matRad_exportDicomRTDoses.m +++ b/matRad/dicom/@matRad_DicomExporter/matRad_exportDicomRTDoses.m @@ -82,7 +82,7 @@ %Now image meta resolution = ct.resolution; meta.PixelSpacing = [resolution.y; resolution.x]; -meta.SliceThickness = resolution.z; +meta.SliceThickness = num2str(resolution.z); meta.ImagePositionPatient = [ct.x(1); ct.y(1); ct.z(1)]; meta.ImageOrientationPatient = [1;0;0;0;1;0]; diff --git a/matRad/dicom/@matRad_DicomExporter/matRad_exportDicomRTStruct.m b/matRad/dicom/@matRad_DicomExporter/matRad_exportDicomRTStruct.m index 9a0bb9e45..0de043444 100644 --- a/matRad/dicom/@matRad_DicomExporter/matRad_exportDicomRTStruct.m +++ b/matRad/dicom/@matRad_DicomExporter/matRad_exportDicomRTStruct.m @@ -96,9 +96,9 @@ %Since we are exporting HU directly --> no rescaling in any case -%meta.SliceThickness = ct.resolution.z; -%meta.PixelSpacing = [ct.resolution.y; ct.resolution.x]; -%meta.ImageOrientationPatient = [1;0;0;0;1;0]; %lps +meta.SliceThickness = num2str(ct.resolution.z); +meta.PixelSpacing = [ct.resolution.y; ct.resolution.x]; +meta.ImageOrientationPatient = [1;0;0;0;1;0]; %lps %meta.RescaleSlope = 1; %meta.RescaleIntercept = 0; diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_importDicomCt.m b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomCt.m index a6cc220b9..82e152ab0 100644 --- a/matRad/dicom/@matRad_DicomImporter/matRad_importDicomCt.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_importDicomCt.m @@ -65,7 +65,7 @@ % might not been defined for individual files obj.importCT.ctInfo(i).PixelSpacing = tmpDicomInfo.PixelSpacing; obj.importCT.ctInfo(i).ImagePositionPatient = tmpDicomInfo.ImagePositionPatient; - obj.importCT.ctInfo(i).SliceThickness = obj.importFiles.resz; + obj.importCT.ctInfo(i).SliceThickness = str2double(obj.importFiles.resz); obj.importCT.ctInfo(i).ImageOrientationPatient = tmpDicomInfo.ImageOrientationPatient; obj.importCT.ctInfo(i).PatientPosition = tmpDicomInfo.PatientPosition; obj.importCT.ctInfo(i).Rows = tmpDicomInfo.Rows; From c42515523490b74918443b83ef6f62d5cada426d Mon Sep 17 00:00:00 2001 From: Amit Bennan Date: Tue, 8 Oct 2024 18:05:12 +0200 Subject: [PATCH 23/28] Update matRad_scanDicomImportFolder.m questdlg error for octave --- .../matRad_scanDicomImportFolder.m | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m b/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m index 84dcbc9ec..375fc56e4 100644 --- a/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m @@ -280,22 +280,15 @@ allfiles{row,column} = NaN; end -if ~warnDlgDICOMtagShown && strcmp(allfiles{row,column},defaultPlaceHolder) && (column == 3 || column == 4) - - dlgTitle = 'Dicom Tag import'; - dlgQuestion = ['matRad_scanDicomImportFolder: Could not parse dicom tag: ' tag '. Using placeholder ' defaultPlaceHolder ' instead. Please check imported data carefully! Do you want to continue?']; - answer = questdlg(dlgQuestion,dlgTitle,'Yes','No', 'Yes'); +if strcmp(allfiles{row,column},defaultPlaceHolder) && (column == 3 || column == 4) - warnDlgDICOMtagShown = true; + wrnTxt = ['matRad_scanDicomImportFolder: Could not parse dicom tag: ' tag '. Using placeholder ' defaultPlaceHolder ' instead. Please check imported data carefully!']; + matRad_cfg.dispWarning(wrnTxt) - switch answer - case 'No' - matRad_cfg.dispError('Inconsistency in DICOM tags') - end end - end + function value = seriesnum2str(value) if isnumeric(value) value = num2str(value); From 1705db5c241eddd326cfa15c6f234cc6827a5f3c Mon Sep 17 00:00:00 2001 From: Amit Bennan Date: Wed, 9 Oct 2024 08:30:50 +0200 Subject: [PATCH 24/28] Update matRad_scanDicomImportFolder.m --- .../dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m b/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m index 375fc56e4..375bbacf4 100644 --- a/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m @@ -281,7 +281,7 @@ end if strcmp(allfiles{row,column},defaultPlaceHolder) && (column == 3 || column == 4) - + matRad_cfg = MatRad_Config.instance(); wrnTxt = ['matRad_scanDicomImportFolder: Could not parse dicom tag: ' tag '. Using placeholder ' defaultPlaceHolder ' instead. Please check imported data carefully!']; matRad_cfg.dispWarning(wrnTxt) From d56ac92ef3af1c71be352bef6027c8f48fada6d8 Mon Sep 17 00:00:00 2001 From: Amit Bennan Date: Wed, 9 Oct 2024 10:26:49 +0200 Subject: [PATCH 25/28] Update matRad_scanDicomImportFolder.m replacing rmmissing (not supported on octave) --- .../dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m b/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m index 375bbacf4..41a5ef4c4 100644 --- a/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m @@ -207,7 +207,8 @@ % Filtration, getting and assigning z resolution to all CT files FiltredLocArray = unique(LocationsArray); - Thickness = rmmissing(unique(diff(FiltredLocArray))); + locZ = ~isnan(FiltredLocArray); + Thickness = unique(diff(FiltredLocArray(locZ))); numOfFiles = numel(obj.allfiles(:,1)); if numel(Thickness) > 1 From 4ecde641b0094cf822f2cb18cf741d45621be5c9 Mon Sep 17 00:00:00 2001 From: Niklas Wahl Date: Wed, 9 Oct 2024 11:19:14 +0200 Subject: [PATCH 26/28] respect waitbar settings and loglevels in dicom import --- .../matRad_importDicom.m | 44 +++++++++++++------ .../matRad_scanDicomImportFolder.m | 15 ++++--- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_importDicom.m b/matRad/dicom/@matRad_DicomImporter/matRad_importDicom.m index e71d7a21b..2d209052b 100644 --- a/matRad/dicom/@matRad_DicomImporter/matRad_importDicom.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_importDicom.m @@ -39,13 +39,19 @@ function matRad_importDicom(obj) end %% -h = waitbar(0,'Please wait...','Color',matRad_cfg.gui.backgroundColor,'DefaultTextColor',matRad_cfg.gui.textColor); -matRad_applyThemeToWaitbar(h); +if ~matRad_cfg.disableGUI + h = waitbar(0,'Please wait...','Color',matRad_cfg.gui.backgroundColor,'DefaultTextColor',matRad_cfg.gui.textColor); + matRad_applyThemeToWaitbar(h); +else + h = []; +end %h.WindowStyle = 'Modal'; steps = 2; %% import ct-cube -waitbar(1 / steps) +if any(ishandle(h)) + waitbar(1/steps, h) +end obj.importCT.resolution.x = str2double(obj.importFiles.resx); obj.importCT.resolution.y = str2double(obj.importFiles.resy); obj.importCT.resolution.z = str2double(obj.importFiles.resz); % [mm] / lps coordinate system @@ -72,13 +78,21 @@ function matRad_importDicom(obj) if ~isempty(obj.importFiles.rtss) %% import structure data - waitbar(2 / steps) + if any(ishandle(h)) + waitbar(2/steps, h) + end obj = matRad_importDicomRtss(obj); - close(h) + if any(ishandle(h)) + close(h) + end %% creating structure cube - h = waitbar(0,'Please wait...','Color',matRad_cfg.gui.backgroundColor,'DefaultTextColor',matRad_cfg.gui.textColor); - matRad_applyThemeToWaitbar(h); + if ~matRad_cfg.disableGUI + h = waitbar(0,'Please wait...','Color',matRad_cfg.gui.backgroundColor,'DefaultTextColor',matRad_cfg.gui.textColor); + matRad_applyThemeToWaitbar(h); + else + h = []; + end %h.WindowStyle = 'Modal'; steps = numel(obj.importRtss.structures); @@ -99,17 +113,19 @@ function matRad_importDicom(obj) end for i = 1:numel(obj.importRtss.structures) % computations take place here - waitbar(i / steps) - fprintf('creating cube for %s volume... ', obj.importRtss.structures(i).structName); + if any(ishandle(h)) + waitbar(1/steps, h) + end + matRad_cfg.dispInfo('creating cube for %s volume... ', obj.importRtss.structures(i).structName); try obj.importRtss.structures(i).indices = matRad_convRtssContours2Indices(obj.importRtss.structures(i),obj.ct); - fprintf('\n'); + matRad_cfg.dispInfo('\n'); catch ME warning('matRad:dicomImport','could not be imported: %s',ME.message); obj.importRtss.structures(i).indices = []; end end - fprintf('finished!\n'); + matRad_cfg.dispInfo('finished!\n'); close(h) %% creating cst @@ -153,17 +169,17 @@ function matRad_importDicom(obj) % check if obj.importFiles.rtdose contains a path and is labeld as RTDose % only the first two elements are relevant for loading the rt dose if ~(cellfun(@isempty,obj.importFiles.rtdose(1,1))) - fprintf('loading dose files...\n'); + matRad_cfg.dispInfo('loading dose files...\n'); % parse plan in order to scale dose cubes to a fraction based dose obj = matRad_importDicomRTDose(obj); if size(obj.resultGUI) == 0 obj.resultGUI = struct([]); end end - fprintf('finished!\n'); + matRad_cfg.dispInfo('finished!\n'); else obj.resultGUI = struct([]); - fprintf('There are no dose files!\n'); + matRad_cfg.dispInfo('There are no dose files!\n'); end %% put weight also into resultGUI diff --git a/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m b/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m index 41a5ef4c4..9ffb9578b 100644 --- a/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m +++ b/matRad/dicom/@matRad_DicomImporter/matRad_scanDicomImportFolder.m @@ -58,14 +58,21 @@ ThBool = []; numOfFiles = numel(obj.allfiles(:,1)); - h = waitbar(0,'Please wait...','Color',matRad_cfg.gui.backgroundColor,'DefaultTextColor',matRad_cfg.gui.textColor); - matRad_applyThemeToWaitbar(h); + if ~matRad_cfg.disableGUI + h = waitbar(0,'Please wait...','Color',matRad_cfg.gui.backgroundColor,'DefaultTextColor',matRad_cfg.gui.textColor); + matRad_applyThemeToWaitbar(h); + else + h = []; + end % precision value for double to string conversion str2numPrc = 10; %h.WindowStyle = 'Modal'; steps = numOfFiles; for i = numOfFiles:-1:1 - waitbar((numOfFiles+1-i) / steps) + if any(ishandle(h)) + waitbar((numOfFiles+1-i) / steps, h) + end + try % try to get DicomInfo if matRad_cfg.isOctave || verLessThan('matlab','9') info = dicominfo(obj.allfiles{i}); @@ -246,8 +253,6 @@ end else matRad_cfg.dispError('No DICOM files found in patient directory!'); - %h.WindowStyle = 'Modal'; - %error('No DICOM files found in patient directory'); end else matRad_cfg.dispError('Folder is empty!'); From f89d141e9887e3d2ee50122656ff999df291e3bb Mon Sep 17 00:00:00 2001 From: Niklas Wahl Date: Wed, 9 Oct 2024 11:23:15 +0200 Subject: [PATCH 27/28] fix in DICOM exporter to pass tests --- matRad/dicom/@matRad_DicomExporter/matRad_exportDicomCt.m | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/matRad/dicom/@matRad_DicomExporter/matRad_exportDicomCt.m b/matRad/dicom/@matRad_DicomExporter/matRad_exportDicomCt.m index 9a2968b62..2dc7841de 100644 --- a/matRad/dicom/@matRad_DicomExporter/matRad_exportDicomCt.m +++ b/matRad/dicom/@matRad_DicomExporter/matRad_exportDicomCt.m @@ -69,19 +69,13 @@ nSlices = ct.cubeDim(3); %Create X Y Z vectors if not present if ~any(isfield(ct,{'x','y','z'})) - %positionOffset = transpose(ct.cubeDim ./ 2); - %positionOffset = ct.cubeDim .* [ct.resolution.y, ct.resolution.x, ct.resolution.z] ./ 2; -% positionOffset = [ct.resolution.y, ct.resolution.x, ct.resolution.z] ./ 2; -% ct.x = ct.resolution.x*[0:ct.cubeDim(2)-1] - positionOffset(2); -% ct.y = ct.resolution.y*[0:ct.cubeDim(1)-1] - positionOffset(1); -% ct.z = ct.resolution.z*[0:ct.cubeDim(3)-1] - positionOffset(3); ct = matRad_getWorldAxes(ct); end obj.ct = ct; %Since we are exporting HU directly --> no rescaling in any case -meta.SliceThickness = num2str(ct.resolution.z); +meta.SliceThickness = ct.resolution.z; meta.PixelSpacing = [ct.resolution.y; ct.resolution.x]; meta.ImageOrientationPatient = [1;0;0;0;1;0]; %lps meta.RescaleType = 'HU'; From 0d9f17720126ee9708e3b6e2246e1d0ad19aa93e Mon Sep 17 00:00:00 2001 From: Niklas Wahl Date: Wed, 9 Oct 2024 11:25:59 +0200 Subject: [PATCH 28/28] disable octave asking for deletion of temporary test directory --- test/helper_temporaryFolder.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/helper_temporaryFolder.m b/test/helper_temporaryFolder.m index 297ffc229..addfcb263 100644 --- a/test/helper_temporaryFolder.m +++ b/test/helper_temporaryFolder.m @@ -2,6 +2,11 @@ %helper_temporaryFolder Creates a temporary folder for test data in the % users temporary systemdirectory. +matRad_cfg = MatRad_Config.instance(); +if matRad_cfg.isOctave + confirm_recursive_rmdir(false,"local"); +end + if nargin < 2 clearIfExists = true; end