-
Notifications
You must be signed in to change notification settings - Fork 199
Range shifter positioning for MC and minor bug fixing #880
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Range shifter positioning for MC and minor bug fixing #880
Conversation
…oxel belongs to target
… first/last voxel
|
@JenHardt could you please do the review first and then I will take a look |
There was a problem hiding this 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_calcSigmaRashifunction 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_getIsoCenterto 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.
| isoToRaShi = stf(i).SAD - rangeShifter.sourceRashiDistance - rsWidth; | ||
| fprintf(fileHandle,'####IsocenterToRangeShifterDistance\n%f\n',isoToRaShi/10); %in cm |
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
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.
| 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 |
| end | ||
|
|
||
| methods (Access = protected) | ||
| methods %(Access = protected) |
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
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.
| methods %(Access = protected) | |
| methods (Access = protected) |
| %assertTrue(isscalar(stf2(i).ray.energy)); | ||
| end | ||
|
|
||
| function test_generateRangeShfterStf() |
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
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').
| function test_generateRangeShfterStf() | |
| function test_generateRangeShifterStf() |
|
|
||
| properties | ||
| useRangeShifter = false; | ||
| useRangeShifter = false |
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
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.
| useRangeShifter = false | |
| useRangeShifter = false; |
| stfFieldMCsquareRaShi.rangeShifterID = raShiField; | ||
| stfFieldMCsquareRaShi.rangeShifterType = 'binary'; | ||
|
|
||
| % Selecte the energies that have a RaShi for |
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
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).
| % Selecte the energies that have a RaShi for | |
| % Select the energies that have a RaShi for |
| % 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; |
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
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.
| 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); |
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
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.
| 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); |
| 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) |
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
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').
| %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) |
| % we can eliminate the layer from the | ||
| % non-RaShi field | ||
| if isscalar(raShiIDSinLayer) && raShiIDSinLayer == stfFieldMCsquareRaShi.rangeShifterID | ||
| stfFieldMCsquare(stfFieldMCsquare.energies == layerEnergy) = []; |
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
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.
| stfFieldMCsquare(stfFieldMCsquare.energies == layerEnergy) = []; | |
| stfFieldMCsquare.energies(stfFieldMCsquare.energies == layerEnergy) = []; |
|
|
||
| % run MCsquare | ||
| mcSquareCall = [this.mcSquareBinary ' ' MCsquareConfigFile]; | ||
| mcSquareCall = [this.mcSquareBinary ' ' sprintf('"%s"', MCsquareConfigFile)]; |
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
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();
Codecov Report❌ Patch coverage is 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. |
JenHardt
left a comment
There was a problem hiding this 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
|
maybe we could also use this opportunity to write a range shifter test for MCsquare ? |
Minor bug fixes: