Skip to content

Conversation

@remocristoforetti
Copy link
Contributor

Minor bug fixes:

  • IMPT stf generator
  • Hong PB algorithm, call to matRad_calcSigmaRashi
  • MCsquare: handle presence of range shifter, need and additional "field" in the MC parameter files for this

@wahln wahln requested a review from Copilot December 1, 2025 08:53
@wahln
Copy link
Contributor

wahln commented Dec 1, 2025

@JenHardt could you please do the review first and then I will take a look

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request implements range shifter positioning for Monte Carlo dose calculation engines and addresses several minor bugs in the IMPT steering file generator and Hong pencilbeam algorithm. The changes enable proper handling of range shifters in MCsquare simulations by splitting fields based on range shifter presence and correcting positioning calculations.

Key Changes:

  • Fixed range shifter positioning calculations in MCsquare and TOPAS MC engines
  • Corrected matRad_calcSigmaRashi function calls to pass the full base data entry instead of just energy
  • Added handling for edge cases in IMPT generator where targets start/end at voxel boundaries
  • Fixed matRad_getIsoCenter to properly calculate mean along rows

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
test/steering/test_stfGEneratorParticleIMPT.m Added test case for range shifter generation in IMPT
test/doseCalc/test_sigmaRashi.m New test for sigma calculation with range shifter
test/doseCalc/test_HongPB.m Added test for Hong PB dose calculation with range shifter
matRad/steering/matRad_StfGeneratorParticleSingleBeamlet.m Fixed undefined visBool variable by adding as class property
matRad/steering/matRad_StfGeneratorParticleRayBixelAbstract.m Added rangeShifterEqD property and logic to handle user-defined thickness
matRad/steering/matRad_StfGeneratorParticleIMPT.m Fixed target entry/exit detection for edge cases and range shifter references
matRad/geometry/matRad_getIsoCenter.m Fixed mean calculation to compute along dimension 1
matRad/doseCalc/+DoseEngines/matRad_TopasMCEngine.m Corrected range shifter position by adding half-width offset
matRad/doseCalc/+DoseEngines/matRad_ParticlePencilBeamEngineAbstract.m Updated matRad_calcSigmaRashi calls to pass full baseData entry
matRad/doseCalc/+DoseEngines/matRad_ParticleMCsquareEngine.m Implemented field splitting for range shifter handling and corrected positioning formula
matRad/basedata/matRad_MCemittanceBaseData.m Changed access modifier to make getRangeShiftersFromStf public

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +814 to +815
isoToRaShi = stf(i).SAD - rangeShifter.sourceRashiDistance - rsWidth;
fprintf(fileHandle,'####IsocenterToRangeShifterDistance\n%f\n',isoToRaShi/10); %in cm
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The sign change in this calculation should be verified. Previously it was + rsWidth, now it's - rsWidth. Combined with the sign change in line 815 (removing the negative), this represents a significant change in the range shifter positioning calculation. Please ensure this has been validated with test data, as incorrect positioning could lead to dose calculation errors.

Suggested change
isoToRaShi = stf(i).SAD - rangeShifter.sourceRashiDistance - rsWidth;
fprintf(fileHandle,'####IsocenterToRangeShifterDistance\n%f\n',isoToRaShi/10); %in cm
isoToRaShi = stf(i).SAD - rangeShifter.sourceRashiDistance + rsWidth;
fprintf(fileHandle,'####IsocenterToRangeShifterDistance\n%f\n',-isoToRaShi/10); %in cm

Copilot uses AI. Check for mistakes.
end

methods (Access = protected)
methods %(Access = protected)
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The commented-out access modifier changes the visibility of the getRangeShiftersFromStf method from protected to public. This should either have the comment removed (if making it public is intentional) or be reverted to Access = protected. Leaving it commented out is unclear and appears incomplete.

Suggested change
methods %(Access = protected)
methods (Access = protected)

Copilot uses AI. Check for mistakes.
%assertTrue(isscalar(stf2(i).ray.energy));
end

function test_generateRangeShfterStf()
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in function name: "test_generateRangeShfterStf" should be "test_generateRangeShifterStf" (missing 'i' in 'Shifter').

Suggested change
function test_generateRangeShfterStf()
function test_generateRangeShifterStf()

Copilot uses AI. Check for mistakes.

properties
useRangeShifter = false;
useRangeShifter = false
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing semicolon after property declaration. All other property declarations in this file and MATLAB conventions typically use semicolons to suppress output.

Suggested change
useRangeShifter = false
useRangeShifter = false;

Copilot uses AI. Check for mistakes.
stfFieldMCsquareRaShi.rangeShifterID = raShiField;
stfFieldMCsquareRaShi.rangeShifterType = 'binary';

% Selecte the energies that have a RaShi for
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in comment: "Selecte" should be "Select" (extra 'e' at the end).

Suggested change
% Selecte the energies that have a RaShi for
% Select the energies that have a RaShi for

Copilot uses AI. Check for mistakes.
% If more than one energy layer is
% found, one of them is for the
% RaShiField
energyIx = energyIx([stf(i).ray(j).rangeShifter(energyIx).ID] == 0); % Select the one with no RaShi;
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential bug: When filtering energyIx on line 391, if all energies for this layer have a range shifter (i.e., none have ID == 0), energyIx will become empty. This would cause issues when used on lines 394, 401, 404, and 406. Consider adding a check to skip processing if energyIx is empty after filtering, or ensure this case is handled properly.

Copilot uses AI. Check for mistakes.
Comment on lines +327 to +332
raShiLayers = [];
for j = 1:stf(i).numOfRays
currentRay = stf(i).ray(j);
raShiLayers = [raShiLayers, currentRay.energy([currentRay.rangeShifter.ID] == stfFieldMCsquareRaShi.rangeShifterID)];
end
stfFieldMCsquareRaShi.energies = unique(raShiLayers);
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Inconsistent naming: The variable is named raShiLayers (plural) but represents energy values, not layers. Consider renaming to raShiEnergies to be consistent with line 189 where a similar concept uses the name raShiEnergies.

Suggested change
raShiLayers = [];
for j = 1:stf(i).numOfRays
currentRay = stf(i).ray(j);
raShiLayers = [raShiLayers, currentRay.energy([currentRay.rangeShifter.ID] == stfFieldMCsquareRaShi.rangeShifterID)];
end
stfFieldMCsquareRaShi.energies = unique(raShiLayers);
raShiEnergies = [];
for j = 1:stf(i).numOfRays
currentRay = stf(i).ray(j);
raShiEnergies = [raShiEnergies, currentRay.energy([currentRay.rangeShifter.ID] == stfFieldMCsquareRaShi.rangeShifterID)];
end
stfFieldMCsquareRaShi.energies = unique(raShiEnergies);

Copilot uses AI. Check for mistakes.
stfFieldMCsquareRaShi.energyLayer(k).targetPoints = [stfFieldMCsquareRaShi.energyLayer(k).targetPoints; ...
-stf(i).ray(j).rayPos_bev(1) stf(i).ray(j).rayPos_bev(3)];

%Number of primaries depending on beamlet-wise or field-based compuation (direct dose calculation)
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in comment: "compuation" should be "computation" (missing 't').

Suggested change
%Number of primaries depending on beamlet-wise or field-based compuation (direct dose calculation)
%Number of primaries depending on beamlet-wise or field-based computation (direct dose calculation)

Copilot uses AI. Check for mistakes.
% we can eliminate the layer from the
% non-RaShi field
if isscalar(raShiIDSinLayer) && raShiIDSinLayer == stfFieldMCsquareRaShi.rangeShifterID
stfFieldMCsquare(stfFieldMCsquare.energies == layerEnergy) = [];
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential bug: The variable stfFieldMCsquare is being indexed like an array on line 349 (stfFieldMCsquare(stfFieldMCsquare.energies == layerEnergy) = []), but it was initialized as a struct on line 283, not as an array. This should likely be stfFieldMCsquare.energies(stfFieldMCsquare.energies == layerEnergy) = [] to remove specific energy values from the energies array.

Suggested change
stfFieldMCsquare(stfFieldMCsquare.energies == layerEnergy) = [];
stfFieldMCsquare.energies(stfFieldMCsquare.energies == layerEnergy) = [];

Copilot uses AI. Check for mistakes.

% run MCsquare
mcSquareCall = [this.mcSquareBinary ' ' MCsquareConfigFile];
mcSquareCall = [this.mcSquareBinary ' ' sprintf('"%s"', MCsquareConfigFile)];
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Command injection risk: mcSquareCall is constructed via string concatenation with untrusted data (this.mcSquareBinary) and executed with system, allowing shell metacharacters in the binary path to be interpreted. If an attacker can influence this.mcSquareBinary (e.g., via configuration), they can inject ;, &&, or similar to execute arbitrary commands. Fix by avoiding the shell entirely (e.g., use Java ProcessBuilder or .NET System.Diagnostics.Process to pass executable and args separately), or at minimum strictly whitelist/validate this.mcSquareBinary and quote/escape both the binary and argument:

% Safer (still shell-based): ensure both elements are quoted and validated
cmd = sprintf('"%s" "%s"', this.mcSquareBinary, MCsquareConfigFile);
[status, cmdout] = system(cmd);

% Preferred: Java ProcessBuilder without shell
pb = java.lang.ProcessBuilder(this.mcSquareBinary, MCsquareConfigFile);
proc = pb.start();

Copilot uses AI. Check for mistakes.
@codecov
Copy link

codecov bot commented Dec 1, 2025

Codecov Report

❌ Patch coverage is 50.49505% with 50 lines in your changes missing coverage. Please review.
✅ Project coverage is 54.00%. Comparing base (162bde8) to head (8bc7fd9).

Files with missing lines Patch % Lines
...eCalc/+DoseEngines/matRad_ParticleMCsquareEngine.m 46.66% 40 Missing ⚠️
matRad/steering/matRad_StfGeneratorParticleIMPT.m 64.28% 5 Missing ⚠️
...teering/matRad_StfGeneratorParticleSingleBeamlet.m 0.00% 2 Missing ⚠️
...eEngines/matRad_ParticlePencilBeamEngineAbstract.m 66.66% 1 Missing ⚠️
...atRad/doseCalc/+DoseEngines/matRad_TopasMCEngine.m 0.00% 1 Missing ⚠️
...ring/matRad_StfGeneratorParticleRayBixelAbstract.m 80.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##              dev     #880      +/-   ##
==========================================
+ Coverage   53.88%   54.00%   +0.12%     
==========================================
  Files         308      308              
  Lines       19954    20004      +50     
==========================================
+ Hits        10752    10803      +51     
+ Misses       9202     9201       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@github-actions
Copy link

github-actions bot commented Dec 1, 2025

Test Results

    3 files  ±0      3 suites  ±0   44m 30s ⏱️ -8s
  322 tests +3    322 ✅ +3  0 💤 ±0  0 ❌ ±0 
1 062 runs  +9  1 057 ✅ +9  5 💤 ±0  0 ❌ ±0 

Results for commit 8bc7fd9. ± Comparison against base commit 162bde8.

♻️ This comment has been updated with latest results.

Copy link
Contributor

@JenHardt JenHardt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good to me, please change the spelling and the semicolon copilot suggested

@wahln
Copy link
Contributor

wahln commented Dec 19, 2025

maybe we could also use this opportunity to write a range shifter test for MCsquare ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants