Skip to content

Commit eae1760

Browse files
axel-grcSimonRit
authored andcommitted
ENH: Add rtksubselect python application
1 parent 006be3b commit eae1760

File tree

7 files changed

+172
-0
lines changed

7 files changed

+172
-0
lines changed

applications/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ The following are examples using RTK applications:
2121
./rtkshowgeometry/README.md
2222
./rtkosem/README.md
2323
./rtksart/README.md
24+
./rtksubselect/README.md
2425
```
2526

2627
In [applications/rtktutorialapplication/](https://github.com/RTKConsortium/RTK/blob/main/applications/rtktutorialapplication), you will find a very basic RTK application that can be used as a starting point for building your own new application.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Projection selection
2+
3+
This example illustrates the `rtksubselect` application, which extracts a subset of projections from an input stack and geometry and writes a reduced stack and matching geometry.
4+
5+
![sin](../../documentation/docs/ExternalData/SheppLogan-Sinogram-3D.png){w=200px alt="SheppLogan sinogram"}
6+
![img](../../documentation/docs/ExternalData/SubSelect-Sinogram.png){w=200px alt="SubSelect sinogram"}
7+
8+
This script uses the SheppLogan phantom.
9+
10+
```{literalinclude} SubSelect.sh
11+
```
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Create a simulated geometry
2+
rtksimulatedgeometry -n 180 -o geometry.xml
3+
4+
# Create projections of the Shepp–Logan phantom
5+
rtkprojectshepploganphantom -g geometry.xml -o projections.mha --spacing 2 --size 200
6+
7+
# Subselect every 2nd projection and write matching geometry
8+
rtksubselect -p . -r projections.mha --geometry geometry.xml \
9+
--out-geometry geometry_subset.xml --out-proj projections_subset.mha \
10+
--first 30 --last 140 --step 2
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
#!/usr/bin/env python
2+
import argparse
3+
import itk
4+
from itk import RTK as rtk
5+
6+
7+
def build_parser():
8+
# argument parsing
9+
parser = rtk.RTKArgumentParser(
10+
description="Subselect projections from a stack and write updated geometry."
11+
)
12+
13+
parser.add_argument(
14+
"--geometry", "-g", help="XML geometry file name", type=str, required=True
15+
)
16+
parser.add_argument(
17+
"--out_geometry", help="Output geometry file name", type=str, required=True
18+
)
19+
parser.add_argument(
20+
"--out_proj", help="Output projections stack file name", type=str, required=True
21+
)
22+
23+
parser.add_argument(
24+
"--first", "-f", help="First projection index", type=int, default=0
25+
)
26+
parser.add_argument("--last", "-l", help="Last projection index", type=int)
27+
parser.add_argument(
28+
"--step", "-s", help="Step between projections", type=int, default=1
29+
)
30+
parser.add_argument(
31+
"--list",
32+
help="List of projection indices to keep (0-based)",
33+
type=int,
34+
nargs="+",
35+
)
36+
# RTK specific groups
37+
rtk.add_rtkinputprojections_group(parser)
38+
39+
# Parse the command line arguments
40+
return parser
41+
42+
43+
def process(args_info: argparse.Namespace):
44+
OutputPixelType = itk.F
45+
Dimension = 3
46+
OutputImageType = itk.Image[OutputPixelType, Dimension]
47+
48+
# Projections reader
49+
reader = rtk.ProjectionsReader[OutputImageType].New()
50+
rtk.SetProjectionsReaderFromArgParse(reader, args_info)
51+
52+
# Geometry
53+
if args_info.verbose:
54+
print(f"Reading geometry information from {args_info.geometry}...")
55+
geometry = rtk.read_geometry(args_info.geometry)
56+
57+
# Compute the indices of the selected projections
58+
indices = []
59+
n = len(geometry.GetGantryAngles())
60+
if args_info.last:
61+
n = min(args_info.last, n)
62+
if args_info.list:
63+
for i in args_info.list:
64+
indices.append(i)
65+
else:
66+
start = args_info.first
67+
step = args_info.step
68+
for noProj in range(start, n, step):
69+
indices.append(noProj)
70+
71+
# Output RTK geometry object
72+
outputGeometry = rtk.ThreeDCircularProjectionGeometry.New()
73+
74+
# Output projections object
75+
source = rtk.ConstantImageSource[OutputImageType].New()
76+
source.SetInformationFromImage(reader.GetOutput())
77+
outputSize = itk.size(reader.GetOutput())
78+
outputSize_list = [s for s in outputSize]
79+
outputSize_list[Dimension - 1] = len(indices)
80+
source.SetSize(outputSize_list)
81+
source.SetConstant(0)
82+
source.Update()
83+
84+
# Fill in the outputGeometry and the output projections
85+
paste = itk.PasteImageFilter[OutputImageType].New()
86+
paste.SetSourceImage(reader.GetOutput())
87+
paste.SetDestinationImage(source.GetOutput())
88+
89+
for i, src_idx in enumerate(indices):
90+
# If it is not the first projection, we need to use the output of the paste filter as input
91+
if i:
92+
pimg = paste.GetOutput()
93+
pimg.DisconnectPipeline()
94+
paste.SetDestinationImage(pimg)
95+
96+
sourceRegion = reader.GetOutput().GetLargestPossibleRegion()
97+
sourceRegion.SetIndex(Dimension - 1, src_idx)
98+
sourceRegion.SetSize(Dimension - 1, 1)
99+
paste.SetSourceRegion(sourceRegion)
100+
101+
destinationIndex = reader.GetOutput().GetLargestPossibleRegion().GetIndex()
102+
destinationIndex.SetElement(Dimension - 1, i)
103+
paste.SetDestinationIndex(destinationIndex)
104+
105+
paste.Update()
106+
107+
# Fill in the output geometry object
108+
outputGeometry.SetRadiusCylindricalDetector(
109+
geometry.GetRadiusCylindricalDetector()
110+
)
111+
outputGeometry.AddProjectionInRadians(
112+
geometry.GetSourceToIsocenterDistances()[src_idx],
113+
geometry.GetSourceToDetectorDistances()[src_idx],
114+
geometry.GetGantryAngles()[src_idx],
115+
geometry.GetProjectionOffsetsX()[src_idx],
116+
geometry.GetProjectionOffsetsY()[src_idx],
117+
geometry.GetOutOfPlaneAngles()[src_idx],
118+
geometry.GetInPlaneAngles()[src_idx],
119+
geometry.GetSourceOffsetsX()[src_idx],
120+
geometry.GetSourceOffsetsY()[src_idx],
121+
)
122+
outputGeometry.SetCollimationOfLastProjection(
123+
geometry.GetCollimationUInf()[src_idx],
124+
geometry.GetCollimationUSup()[src_idx],
125+
geometry.GetCollimationVInf()[src_idx],
126+
geometry.GetCollimationVSup()[src_idx],
127+
)
128+
129+
# Geometry writer
130+
if args_info.verbose:
131+
print(f"Writing geometry information in {args_info.out_geometry}...")
132+
rtk.write_geometry(outputGeometry, args_info.out_geometry)
133+
134+
# Write
135+
if args_info.verbose:
136+
print(f"Writing selected projections to {args_info.out_proj}...")
137+
itk.imwrite(paste.GetOutput(), args_info.out_proj)
138+
139+
140+
def main(argv=None):
141+
parser = build_parser()
142+
args_info = parser.parse_args(argv)
143+
process(args_info)
144+
145+
146+
if __name__ == "__main__":
147+
main()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
7d5c53c19ccbc227fde52d86fd2a58c381ee8825b8fef4e9da87ef7688d6304d11af2287081ed11f6f7508ef096ac50519ac8a7065d1b265ec65fa1ad0c26f45

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ rtkprojectshepploganphantom= "itk.rtkprojectshepploganphantom:main"
7070
rtkshowgeometry = "itk.rtkshowgeometry:main"
7171
rtksart = "itk.rtksart:main"
7272
rtksimulatedgeometry = "itk.rtksimulatedgeometry:main"
73+
rtksubselect = "itk.rtksubselect:main"
7374
rtktotalvariationdenoising = "itk.rtktotalvariationdenoising:main"
7475
rtkvarianobigeometry = "itk.rtkvarianobigeometry:main"
7576
rtkwaveletsdenoising = "itk.rtkwaveletsdenoising:main"

wrapping/__init_rtk__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"rtkshowgeometry",
5353
"rtksart",
5454
"rtksimulatedgeometry",
55+
"rtksubselect",
5556
"rtktotalvariationdenoising",
5657
"rtkvarianobigeometry",
5758
"rtkwaveletsdenoising",

0 commit comments

Comments
 (0)