diff --git a/matRad/doseCalc/+DoseEngines/@matRad_DoseEngineBase/matRad_DoseEngineBase.m b/matRad/doseCalc/+DoseEngines/@matRad_DoseEngineBase/matRad_DoseEngineBase.m index 0a1e97f92..5005caeda 100644 --- a/matRad/doseCalc/+DoseEngines/@matRad_DoseEngineBase/matRad_DoseEngineBase.m +++ b/matRad/doseCalc/+DoseEngines/@matRad_DoseEngineBase/matRad_DoseEngineBase.m @@ -226,9 +226,9 @@ function assignPropertiesFromPln(this,pln,warnWhenPropertyChanged) resultGUI = []; + activeScenIx = find(this.multScen.scenMask); for i = 1:this.multScen.totNumScen - scenSubIx = this.multScen.linearMask(i,:); - resultGUItmp = matRad_calcCubes(ones(dij.numOfBeams,1),dij,this.multScen.sub2scenIx(scenSubIx(1),scenSubIx(2),scenSubIx(3))); + resultGUItmp = matRad_calcCubes(ones(dij.numOfBeams,1),dij,activeScenIx(i)); if i == 1 resultGUI = resultGUItmp; end diff --git a/matRad/doseCalc/+DoseEngines/matRad_PencilBeamEngineAbstract.m b/matRad/doseCalc/+DoseEngines/matRad_PencilBeamEngineAbstract.m index 8833847c9..949dcf13f 100644 --- a/matRad/doseCalc/+DoseEngines/matRad_PencilBeamEngineAbstract.m +++ b/matRad/doseCalc/+DoseEngines/matRad_PencilBeamEngineAbstract.m @@ -123,7 +123,7 @@ function setDefaults(this) for ctScen = 1:this.multScen.numOfCtScen for rangeShiftScen = 1:this.multScen.totNumRangeScen - fullScenIdx = this.multScen.sub2scenIx(ctScen,shiftScen,rangeShiftScen); + fullScenIdx = this.multScen.sub2scenIx(ctScen,shiftScen,rangeShiftScen,'position'); if this.multScen.scenMask(fullScenIdx) %TODO: This shows we probably need @@ -673,4 +673,3 @@ function setDefaults(this) end end - diff --git a/matRad/scenarios/matRad_NominalScenario.m b/matRad/scenarios/matRad_NominalScenario.m index 6b58dcdb4..cde5f426b 100644 --- a/matRad/scenarios/matRad_NominalScenario.m +++ b/matRad/scenarios/matRad_NominalScenario.m @@ -96,7 +96,14 @@ this.totNumScen = totNumScen; end end + + function scenIx = sub2scenIx(this,ctScen,shiftScen,rangeShiftScen,ctScenReference) + if nargin < 5 + scenIx = sub2scenIx@matRad_ScenarioModel(this,ctScen,shiftScen,rangeShiftScen); + else + scenIx = sub2scenIx@matRad_ScenarioModel(this,ctScen,shiftScen,rangeShiftScen,ctScenReference); + end + end end end - diff --git a/matRad/scenarios/matRad_ScenarioModel.m b/matRad/scenarios/matRad_ScenarioModel.m index 2244027ec..33f059d5f 100644 --- a/matRad/scenarios/matRad_ScenarioModel.m +++ b/matRad/scenarios/matRad_ScenarioModel.m @@ -159,10 +159,15 @@ function listAllScenarios(this) newInstance = matRad_NominalScenario(); ctScenNum = this.linearMask(scenNum,1); + ctScenProbIx = find(this.ctScenProb(:,1) == ctScenNum,1,'first'); + if isempty(ctScenProbIx) + matRad_cfg = MatRad_Config.instance(); + matRad_cfg.dispError('Could not find CT scenario %d in ctScenProb.',ctScenNum); + end %First set properties that force an update newInstance.numOfCtScen = 1; - newInstance.ctScenProb = this.ctScenProb(ctScenNum,:); + newInstance.ctScenProb = this.ctScenProb(ctScenProbIx,:); %Now overwrite existing variables for correct probabilties and %error realizations @@ -181,14 +186,16 @@ function listAllScenarios(this) %newInstance.updateScenarios(); end - function scenIx = sub2scenIx(this,ctScen,shiftScen,rangeShiftScen) + function scenIx = sub2scenIx(this,ctScen,shiftScen,rangeShiftScen,ctScenReference) %Returns linear index in the scenario cell array from scenario - %subscript indices - if ~isvector(this.scenMask) - scenIx = sub2ind(size(this.scenMask),this.ctScenIx(ctScen),shiftScen,rangeShiftScen); - else - scenIx = this.ctScenIx(ctScen); + %subscript indices. The optional ctScenReference disambiguates + %whether ctScen is a local position ('position', default) or + %an absolute CT scenario id ('id'). + if nargin < 5 || isempty(ctScenReference) + ctScenReference = 'position'; end + ctScenId = resolveCtScenarioId(this,ctScen,ctScenReference); + scenIx = scenarioSub2Ind(this,ctScenId,shiftScen,rangeShiftScen); end function scenNum = scenNum(this,fullScenIx) @@ -246,3 +253,80 @@ function listAllScenarios(this) end end +function ctScenId = resolveCtScenarioId(scenarioModel,ctScen,ctScenReference) + +validatePositiveIntegerScalar(ctScen,'ctScen'); +ctScenReference = normalizeCtScenReference(ctScenReference); + +switch ctScenReference + case 'position' + if ctScen > size(scenarioModel.ctScenProb,1) + matRad_cfg = MatRad_Config.instance(); + matRad_cfg.dispError('CT scenario position %d exceeds the scenario model size.',ctScen); + end + ctScenId = scenarioModel.ctScenProb(ctScen,1); + case 'id' + if ~any(scenarioModel.ctScenProb(:,1) == ctScen) + matRad_cfg = MatRad_Config.instance(); + matRad_cfg.dispError('Could not find CT scenario %d in the scenario model.',ctScen); + end + ctScenId = ctScen; +end + +end + +function ctScenReference = normalizeCtScenReference(ctScenReference) + +if isstring(ctScenReference) && isscalar(ctScenReference) + ctScenReference = char(ctScenReference); +end + +if ~ischar(ctScenReference) + matRad_cfg = MatRad_Config.instance(); + matRad_cfg.dispError('ctScenReference must be ''position'' or ''id''.'); +end + +switch lower(ctScenReference) + case {'position','ctscenposition'} + ctScenReference = 'position'; + case {'id','ctscenid'} + ctScenReference = 'id'; + otherwise + matRad_cfg = MatRad_Config.instance(); + matRad_cfg.dispError('ctScenReference must be ''position'' or ''id''.'); +end + +end + +function scenIx = scenarioSub2Ind(scenarioModel,ctScenId,shiftScen,rangeShiftScen) + +validatePositiveIntegerScalar(shiftScen,'shiftScen'); +validatePositiveIntegerScalar(rangeShiftScen,'rangeShiftScen'); + +if ~isvector(scenarioModel.scenMask) + scenMaskSize = size(scenarioModel.scenMask); + if ctScenId > scenMaskSize(1) || shiftScen > scenMaskSize(2) || ... + rangeShiftScen > scenMaskSize(3) + matRad_cfg = MatRad_Config.instance(); + matRad_cfg.dispError('Scenario subscript exceeds the scenario mask dimensions.'); + end + scenIx = sub2ind(scenMaskSize,ctScenId,shiftScen,rangeShiftScen); +else + if ctScenId > numel(scenarioModel.scenMask) + matRad_cfg = MatRad_Config.instance(); + matRad_cfg.dispError('CT scenario id %d exceeds the scenario mask dimensions.',ctScenId); + end + scenIx = ctScenId; +end + +end + +function validatePositiveIntegerScalar(value,valueName) + +if ~(isnumeric(value) && isscalar(value) && isfinite(value) && ... + round(value) == value && value >= 1) + matRad_cfg = MatRad_Config.instance(); + matRad_cfg.dispError('%s must be a positive integer scalar.',valueName); +end + +end diff --git a/test/scenarios/test_scenarioModel.m b/test/scenarios/test_scenarioModel.m index 4c9642156..d368da93f 100644 --- a/test/scenarios/test_scenarioModel.m +++ b/test/scenarios/test_scenarioModel.m @@ -44,6 +44,19 @@ function test_scenarioAbstractAvailableTypes() assertEqual(model.name,availableTypes{i}); end +function test_extractSingleScenario_accepts_sparse_ct_scenario_probabilities + ct.numOfCtScen = 3; + model = matRad_RandomScenarios(ct); + model.ctScenProb = [2 1]; + + scenario = model.extractSingleScenario(1); + + assertEqual(scenario.ctScenProb,[2 1]); + assertEqual(scenario.ctScenIx,2); + assertEqual(scenario.sub2scenIx(1,1,1),2); + assertEqual(scenario.sub2scenIx(1,1,1,'position'),2); + assertEqual(scenario.sub2scenIx(2,1,1,'id'),2); + assertExceptionThrown(@() scenario.sub2scenIx(2,1,1,'position'),'matRad:Error'); function instanceTest_listAllScenarios(model) model.listAllScenarios(); @@ -103,4 +116,3 @@ function instanceTest_TYPE(model) function instanceTest_wcFactor(model) %assertWarning(@() model.wcFactor,'matRad:Deprecated'); assertEqual(model.TYPE,model.name); -