Register
Login
Resources
Docs Blog Datasets Glossary Case Studies Tutorials & Webinars
Product
Data Engine LLMs Platform Enterprise
Pricing Explore
Connect to our Discord channel

2a58407d-9521-42c1-aff4-581a3ad4e3a4 8.1 KB
Raw

You have to be logged in to leave a comment. Sign In
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
  1. function target_voxel_str = fun_graph_get_nearest_skl_ind(vessel_image, vessel_mask, vessel_skl, ep_sub, ep_all_sub, return_local_arrayQ)
  2. % fun_graph_get_nearest_skl_ind use threshold relaxation to find the
  3. % closest skeleton voxels that locates in a connected component other than
  4. % the connected component that ep_1_sub belong to.
  5. % Input:
  6. % vessel_image: 3D numerical array
  7. % vessel_mask: 3D logical array, mask of the vessel
  8. % vessel_skl: 3D logical array, the skeleton of the vessel
  9. % ep_1_sub: 1-by-3 vector, subscript of the endpoint
  10. % Output:
  11. % target_voxel_str: structure with fields
  12. if nargin < 5
  13. ep_all_sub = [];
  14. return_local_arrayQ = false;
  15. elseif nargin < 6
  16. return_local_arrayQ = false;
  17. end
  18. image_size = double(size(vessel_image));
  19. if isscalar(ep_sub)
  20. warning('Input endpoint subscript is a scalar. Assumed to be index and convert it to subscript automatically');
  21. ep_sub = fun_ind2sub(image_size, ep_sub);
  22. end
  23. local_bbox_r = 20;
  24. local_bbox_r_max = 40;
  25. min_cc_size = 250; % If too small, might find connected components with no skeleton voxels inside.
  26. max_use_nearby_endpoint_dist = 5;
  27. local_cc_to_connect_num = 0;
  28. target_voxel_str = fun_initialized_structure_array_with_fieldname_list({'foundQ', ...
  29. 'ep_global_sub', 'ep_global_ind', 'search_bbox_r', 'search_bbox_mmxx', 'ep_local_sub', ...
  30. 'threshold_list', 'bg_mean', 'bg_std', 'connected_th', 'search_full_image_Q', ...
  31. 'dist_ep2target', 'local_sub', 'target_global_sub', 'target_global_ind', 'local_int', ...
  32. 'local_mask', 'connected_mask' });
  33. target_voxel_str.foundQ = false;
  34. target_voxel_str.ep_global_sub = ep_sub;
  35. target_voxel_str.ep_global_ind = sub2ind(image_size, ep_sub(1), ep_sub(2), ep_sub(3));
  36. %% Search for the local connected components other than the one contains the
  37. % endpoint.
  38. while ~local_cc_to_connect_num
  39. target_voxel_str.search_bbox_r = local_bbox_r;
  40. % Crop local image, mask and skeleton
  41. [local_int, local_bbox_mmxx] = crop_center_box(vessel_image, ep_sub, local_bbox_r);
  42. target_voxel_str.search_bbox_mmxx = local_bbox_mmxx;
  43. local_int = single(local_int);
  44. local_mask = crop_center_box(vessel_mask, ep_sub, local_bbox_r);
  45. local_skl = crop_center_box(vessel_skl, ep_sub, local_bbox_r);
  46. % Update local mask
  47. local_mask = local_mask | local_skl;
  48. % Compute local background average intensty and its standard deviation.
  49. local_bg_mean = mean(local_int(~local_mask));
  50. local_bg_std = std(local_int(~local_mask));
  51. local_mask_size = size(local_mask);
  52. % Position of the endpoint in the local coordinate
  53. ep_local_sub = ep_sub - local_bbox_mmxx(1:3) + 1;
  54. target_voxel_str.ep_local_sub = ep_local_sub;
  55. ep_local_ind = sub2ind(local_mask_size, ep_local_sub(1), ep_local_sub(2), ...
  56. ep_local_sub(3));
  57. % Connected components in the local mask
  58. local_cc = bwconncomp(local_mask, 26);
  59. local_mask_label = labelmatrix(local_cc);
  60. ep_cc_label = local_mask_label(ep_local_ind);
  61. assert(ep_cc_label>0, 'The skeleton voxel is outside the vessel mask');
  62. local_cc_size = cellfun(@numel, local_cc.PixelIdxList);
  63. % Remoce small connecterd component
  64. local_cc_selected = local_cc_size > min_cc_size;
  65. % Get connected component other than the one that contaisn the endpoint
  66. local_cc_selected(ep_cc_label) = false;
  67. local_cc_to_connect = local_cc.PixelIdxList(local_cc_selected);
  68. local_cc_to_connect_num = numel(local_cc_to_connect);
  69. if local_cc_to_connect_num ~= 0
  70. % Check if the cc contains the skeleton
  71. cc_contain_skl_Q = any(local_skl( cat(1, local_cc_to_connect{:})));
  72. else
  73. cc_contain_skl_Q = false;
  74. end
  75. if local_cc_to_connect_num == 0 || ~cc_contain_skl_Q
  76. if local_bbox_r <= local_bbox_r_max
  77. local_bbox_r = local_bbox_r + 5;
  78. disp('Increase the size of the local mask.');
  79. else
  80. disp('No nearby connected components.');
  81. return;
  82. end
  83. end
  84. end
  85. local_search_mask = (local_mask_label > 0) & (local_mask_label ~= ep_cc_label);
  86. %% Threshold relaxation
  87. % Estimate the compute threshold list
  88. cc_ep_int_mean = mean(local_int(local_mask_label == ep_cc_label));
  89. num_step = round((cc_ep_int_mean - local_bg_mean)/local_bg_std);
  90. th_list = local_bg_mean + (num_step : -1 : -3) * local_bg_std;
  91. target_voxel_str.threshold_list = th_list;
  92. target_voxel_str.bg_mean = local_bg_mean;
  93. target_voxel_str.bg_std= local_bg_std;
  94. % Iteratively reduce the threshold until the the input endpoint is in the
  95. % same connected component with a skeleton voxel that is originally in the
  96. % other connected component
  97. connected_cc_label = [];
  98. for iter_th = 1 : length(th_list)
  99. test_th = th_list(iter_th);
  100. test_mask = local_int > test_th;
  101. if ~test_mask(ep_local_ind)
  102. % Endpoint not in the mask
  103. continue;
  104. end
  105. test_mask_labeled = bwlabeln(test_mask, 26);
  106. test_ep_label = test_mask_labeled(ep_local_ind);
  107. % Test if any of the other connected component in the original mask is
  108. % connected
  109. for iter_cc = 1 : local_cc_to_connect_num
  110. if any(test_mask_labeled(local_cc_to_connect{iter_cc}) == test_ep_label)
  111. % Connected.
  112. connected_cc_label = iter_cc;
  113. connected_mask = (test_mask_labeled == test_ep_label);
  114. break;
  115. end
  116. end
  117. if ~isempty(connected_cc_label)
  118. local_skl_masked = connected_mask & local_search_mask & local_skl;
  119. local_skl_sub = fun_ind2sub(local_mask_size, find(local_skl_masked));
  120. if ~isempty(local_skl_sub)
  121. target_voxel_str.foundQ = true;
  122. break;
  123. end
  124. end
  125. end
  126. % Note that this threshold can be further refined by binary search between
  127. % the found threshold and the last unconnected threhsold
  128. if isempty(connected_cc_label) || isempty(local_skl_sub)
  129. disp('No label found');
  130. return;
  131. end
  132. target_voxel_str.connected_th = test_th;
  133. if test_th == th_list(end)
  134. target_voxel_str.search_full_image_Q = true;
  135. else
  136. target_voxel_str.search_full_image_Q = false;
  137. end
  138. %% Find the nearest skeleton voxel
  139. ep_skel_dist = pdist2(ep_local_sub, local_skl_sub);
  140. [target_voxel_str.dist_ep2target, min_dist_skl_idx] = min(ep_skel_dist);
  141. min_dist_skl_local_sub = local_skl_sub(min_dist_skl_idx, :);
  142. target_voxel_sub = min_dist_skl_local_sub;
  143. % Check if nearby endpoint is in the ep_all_sub list
  144. % Local position of the endpoints
  145. if ~isempty(ep_all_sub)
  146. ep_all_local_sub_Q = all(bsxfun(@ge, ep_all_sub, local_bbox_mmxx(1:3)), 2) &...
  147. all(bsxfun(@le, ep_all_sub, local_bbox_mmxx(4:6)),2);
  148. ep_all_local_sub = bsxfun(@minus, ep_all_sub, local_bbox_mmxx(1:3) - 1);
  149. ep_all_local_sub = ep_all_local_sub(ep_all_local_sub_Q,:);
  150. if ~isempty(ep_all_local_sub)
  151. % Distance between the endpoints in the local bounding box and the skeleton
  152. % voxels that closest to the endpoint
  153. dist_skl_to_eps = pdist2(min_dist_skl_local_sub, ep_all_local_sub);
  154. [min_dist_skl_to_eps, tmp_idx] = min(dist_skl_to_eps);
  155. closest_ep_label = test_mask_labeled(ep_all_local_sub(tmp_idx,1), ep_all_local_sub(tmp_idx,2), ...
  156. ep_all_local_sub(tmp_idx,3));
  157. if (min_dist_skl_to_eps <= max_use_nearby_endpoint_dist) && (connected_cc_label == closest_ep_label)
  158. if min_dist_skl_to_eps > eps
  159. fprintf('Change the closest point to the endpoint\n');
  160. end
  161. % Then endpoint is very close to the found skeleton voxle and
  162. % they belong to the same connected components
  163. target_voxel_sub = ep_all_local_sub(tmp_idx, :);
  164. end
  165. end
  166. end
  167. target_voxel_str.local_sub = target_voxel_sub;
  168. target_voxel_str.target_global_sub = target_voxel_sub + local_bbox_mmxx(1:3) - 1;
  169. target_voxel_str.target_global_ind = sub2ind(image_size, target_voxel_str.target_global_sub(1), target_voxel_str.target_global_sub(2), ...
  170. target_voxel_str.target_global_sub(3));
  171. if return_local_arrayQ
  172. target_voxel_str.local_int = local_int;
  173. target_voxel_str.local_mask = local_mask;
  174. target_voxel_str.connected_mask = connected_mask;
  175. end
  176. end
Tip!

Press p or to see the previous file or, n or to see the next file

Comments

Loading...