-
Notifications
You must be signed in to change notification settings - Fork 0
/
Vessel.jl
172 lines (153 loc) · 8.9 KB
/
Vessel.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# This file is originally based on Matlab code written by Christine Droigk
"""
vesselPath(N::NTuple{3,Int}; start, angle_xy, angle_xz, diameter, split_prob, change_prob,
max_change, splitnr, max_number_splits, stepsize, change_diameter_splitting, split_prob_factor,
change_prob_increase, rng)
### Input parameters:
* N: Image size, given as a 3 tuple
* start: starting point given as a 3x1 vector
* angle_xy: angle in radians describing the starting angle with which the
* vessel runs.
* angle_xz: same, but angle from xy-plane to vessel's z
* diameter: starting diameter of vessel
* split_prob: probability for a splitting of the vessel into two vessel segments. Values between 0 and 1.
* change_prob: probability for directional change of the vessel route. Values between 0 and 1.
* max_change: max_change * pi specifies the maximum direction-change angle.
* splitnr: used for recursive call of the function. For the first call set it to 1.
* max_number_splits: maximum number of splits of the vessel.
* stepsize: stepsize of the vessel.
* change_diameter_splitting: Indicates by how much the diameter decreases when the vessel splits
* split_prob_factor: Factor by which the split probability `split_prob` is multiplied when the vessel splits
* change_prob_increase: Increase of the change probability `change_prob` when the vessel splits
* rng: Random number generator
Output:
* route: A length N vector containing the points 3D of the route of the vessel. The
* length N depends on the random route.
* diameter_route: A length N vector containing the diameter of the vessel at the positions of the route.
"""
function vesselPath(N::NTuple{3,Int};
start,
angle_xy,
angle_xz,
diameter,
split_prob,
change_prob,
max_change,
splitnr,
max_number_splits = Inf,
stepsize = max(N...)/200,
change_diameter_splitting = 0.6,
split_prob_factor = 0.5,
change_prob_increase = 0.01,
rng::AbstractRNG = GLOBAL_RNG)
route = NTuple{3,Float64}[]
push!(route, start)
while all( 1 .<= route[end] .< N) # while the route of the vessel is inside the image area
if (rand(rng, Float64) < change_prob) && (length(route) > 2)# if directional change of the vessel
change_angle_xy = pi*(max_change-2*max_change*rand(rng,Float64)) # throw dice for the angles of directional change. For more or less variations replace (0.1-0.2*rand)
change_angle_xz = pi*(max_change-2*max_change*rand(rng,Float64)) # same for other angle
step_angle_xy = range(0, change_angle_xy, length=20) # to obtain a piecewise change of angle
step_angle_xz = range(0, change_angle_xz, length=20)
for i = 1:max(length(step_angle_xy),length(step_angle_xz)) # until the largest change of the two angles is reached
if all( 1 .<= route[end] .< N) # check whether image boundaries are reached
if i<=length(step_angle_xy) && i<=length(step_angle_xz) # both angles are still changing
push!(route, route[end] .+
stepsize .* (cos(angle_xy + step_angle_xy[i])*cos(angle_xz + step_angle_xz[i]),
sin(angle_xy + step_angle_xy[i])*cos(angle_xz + step_angle_xz[i]),
sin(angle_xz + step_angle_xz[i])) )
elseif i>length(step_angle_xy) && i<=length(step_angle_xz) # only xz-angle changes
push!(route, route[end] .+
stepsize .* (cos(angle_xy + change_angle_xy)*cos(angle_xz + step_angle_xz[i]),
sin(angle_xy + change_angle_xy)*cos(angle_xz + step_angle_xz[i]),
sin(angle_xz + step_angle_xz[i])) )
elseif i<=length(step_angle_xy) && i>length(step_angle_xz) # only xy-angle changes
push!(route, route[end] .+
stepsize .* (cos(angle_xy+step_angle_xy[i])*cos(angle_xz + change_angle_xz),
sin(angle_xy+step_angle_xy[i])*cos(angle_xz + change_angle_xz),
sin(angle_xz + change_angle_xz)) )
end
end
end
# set current angles to new angles
angle_xy = angle_xy + change_angle_xy # set current angles to new angles
angle_xz = angle_xz + change_angle_xz
else
# if no directional change
num_step = 15
for _=1:num_step
push!(route, route[end] .+ stepsize .* (cos(angle_xy)*cos(angle_xz),
sin(angle_xy)*cos(angle_xz),
sin(angle_xz))) # use old angle
end
end
if rand(rng, Float64) < split_prob && (length(route) > 3) && (splitnr ≤ max_number_splits) # if vessel is splitting
min_angle_diff = 0.15*pi
angle_diff = rand(rng, Float64)*(pi/2 - min_angle_diff) + min_angle_diff # throw dice for angle between the resulting two vessel parts
angle_diff_z = rand(rng, Float64)*(pi/2 - min_angle_diff) + min_angle_diff # same for z-angle
part_a = rand(rng, Float64) # Part of the angle related to the first division
part_a_z = rand(rng, Float64) # Same for z-angle
# Call recursively this function for each of the two vessel
# segments with adjusted angles and diameter. Here, an adjustment
# of the splitting probabilities and directional change
# probabilities is made.
routeA, diameterA = vesselPath(N; start=route[end], angle_xy=angle_xy-part_a*angle_diff,
angle_xz=angle_xz-part_a_z*angle_diff_z, diameter=diameter*change_diameter_splitting,
split_prob=split_prob*split_prob_factor, change_prob=change_prob+change_prob_increase, max_change, splitnr=splitnr+1,
max_number_splits, stepsize, change_diameter_splitting, split_prob_factor, change_prob_increase, rng)
routeB, diameterB = vesselPath(N; start=route[end], angle_xy=angle_xy+(1-part_a)*angle_diff,
angle_xz=angle_xy+(1-part_a_z)*angle_diff_z, diameter=diameter*change_diameter_splitting,
split_prob=split_prob*split_prob_factor, change_prob=change_prob+change_prob_increase, max_change, splitnr=splitnr+1,
max_number_splits, stepsize, change_diameter_splitting, split_prob_factor, change_prob_increase, rng)
if splitnr>1
# set diameter for the splitting parts
change_diameter_splitting_ = length(route) > 1 ? change_diameter_splitting : 1
diameter_route = collect(range((1/change_diameter_splitting_)*diameter, diameter, length=length(route)))
else
diameter_route = diameter*ones(length(route))
end
append!(route, routeA, routeB)
append!(diameter_route, diameterA, diameterB)
return route, diameter_route
end
end
if splitnr>1
change_diameter_splitting_ = length(route) > 1 ? change_diameter_splitting : 1
diameter_route = collect(range((1/change_diameter_splitting_)*diameter, diameter, length=length(route)))
else
diameter_route = diameter*ones(length(route))
end
return route, diameter_route
end
"""
vesselPhantom(N::NTuple{3,Int}; oversampling=2, kargs...)
### Input parameters:
* N: Image size, given as a 3 tuple
* oversampling: Oversampling factor for the phantom. Default is 2.
* rng: Random number generator
* kernelWidth: Width (standard deviation) of the Gaussian kernel (in pixel) used for smoothing the phantom. If nothing is given, a random value is chosen.
* kargs...: remaining keyword arguments for `vesselPath`
### Example usage:
using GLMakie, TrainingPhantoms, StableRNGs
im = vesselPhantom((51,51,51);
start=(1, 25, 25), angle_xy=0.0, angle_xz=0.0,
diameter=2.5, split_prob=0.4, change_prob=0.3,
max_change=0.3, splitnr=1, max_number_splits=1, rng StableRNG(123));
f = Figure(size=(300,300))
ax = Axis3(f[1,1], aspect=:data)
volume!(ax, im, algorithm=:iso, isorange=0.13, isovalue=0.3, colormap=:viridis, colorrange=[0.0,0.2])
"""
function vesselPhantom(N::NTuple{3,Int}; oversampling=2, rng = GLOBAL_RNG, kernelWidth=nothing, kargs...)
route, diameter_route = vesselPath(N; rng, kargs...)
# add small sphere for every entry in the route
obs = [ sphere( Float32.(route[i]), Float32(diameter_route[i]), 1.0f0) for i=eachindex(route) ]
ranges = ntuple(d-> 1:N[d], 3)
img = phantom(ranges..., obs, oversampling)
if isnothing(kernelWidth)
# filter for smoothing, offset to ensure minimal filter width
filterWidth = (1.0-0.3)*rand(rng) + 0.3
kernelWidth = ntuple(_ -> filterWidth*N[1] / 20, 3)
end
img = imfilter(img, Kernel.gaussian(kernelWidth))
img[img .> 1] .= 1
return img
end