-
Notifications
You must be signed in to change notification settings - Fork 1
/
WriteDVH.m
292 lines (251 loc) · 10.7 KB
/
WriteDVH.m
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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
function varargout = WriteDVH(varargin)
% WriteDVH computes the DVH for each structure included in the image input
% variable given the dose input variable and writes the resulting DVHs to
% a comma-separated value file. The DVHs can also be optionally returned as
% an array. Note, if a DVH filename is not provided in the third input
% argument, WriteDVH will simply compute the DVH from the image,
% structures, and dose and optionally return the array.
%
% The first row contains the file name, the second row contains column
% headers for each structure set (including the volume in cc in
% parentheses), with each subsequent row containing the percent volume of
% each structure at or above the dose specified in the first column (in
% Gy). The resolution is determined by dividing the maximum dose by 1001,
% or nbins + 1;
%
% The following variables are required for proper execution:
% varargin{1}: structure containing the CT image data and structure set
% data. See LoadDICOMImages and LoadDICOMStructures for more
% information on the format of this object.
% varargin{2}: structure containing the calculated dose. Must contain
% start, width, and data fields. See CalcDose for more information on
% the format of this object. Start and widths are in cm.
% varargin{3} (optional): string containing the path and name to write
% the DVH .csv file to. MATLAB must have write access to this
% location to execute. If not provided, a DVH file will not be saved.
%
% The following variables are returned upon succesful completion:
% varargout{1} (optional): a 1001 by n+1 array of cumulative DVH values
% for n structures where n+1 is the x-axis value (separated into 1001
% bins).
%
% Below are examples of how this function is used:
%
% % Load DICOM images
% path = '/path/to/files/';
% names = {
% '2.16.840.1.114362.1.5.1.0.101218.5981035325.299641582.274.1.dcm'
% '2.16.840.1.114362.1.5.1.0.101218.5981035325.299641582.274.2.dcm'
% '2.16.840.1.114362.1.5.1.0.101218.5981035325.299641582.274.3.dcm'
% };
% image = LoadDICOMImages(path, names);
%
% % Load DICOM structure set into image structure
% name = '2.16.840.1.114362.1.5.1.0.101218.5981035325.299641579.747.dcm';
% image.structures = LoadDICOMStructures(path, name, image);
%
% % Create dose array with same dimensions and coordinates as image
% dose.data = rand(size(image.data));
% dose.width = image.width;
% dose.start = image.start;
%
% % Declare file name and path to write DVH to
% dest = '/path_to_file/dvh.csv';
%
% % Execute WriteDVH
% WriteDVH(image, dose, dest);
%
% % Execute WriteDVH again, this time returning the DVH as an array
% dvh = WriteDVH(image, dose, dest);
%
% % Compute but do not save the DVH to a file
% dvh = WriteDVH(image, dose);
%
% Author: Mark Geurts, mark.w.geurts@gmail.com
% Copyright (C) 2016 University of Wisconsin Board of Regents
%
% This program is free software: you can redistribute it and/or modify it
% under the terms of the GNU General Public License as published by the
% Free Software Foundation, either version 3 of the License, or (at your
% option) any later version.
%
% This program is distributed in the hope that it will be useful, but
% WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
% Public License for more details.
%
% You should have received a copy of the GNU General Public License along
% with this program. If not, see http://www.gnu.org/licenses/.
% Specify number of bins
nbins = 1000;
% Execute in try/catch statement
try
% Log start of DVH computation and start timer
if exist('Event', 'file') == 2
Event('Computing dose volume histograms');
tic;
end
% If the dose variable contains a valid data array
if isfield(varargin{2}, 'data') && size(varargin{2}.data, 1) > 0
% If the image size, pixel size, or start differs between datasets
if size(varargin{2}.data,1) ~= size(varargin{1}.data,1) ...
|| size(varargin{2}.data,2) ~= size(varargin{1}.data,2) ...
|| size(varargin{2}.data,3) ~= size(varargin{1}.data,3) ...
|| isequal(varargin{2}.width, varargin{1}.width) == 0 ...
|| isequal(varargin{2}.start, varargin{1}.start) == 0
% Create 3D mesh for reference image
[refX, refY, refZ] = meshgrid(single(varargin{1}.start(2) + ...
varargin{1}.width(2) * (size(varargin{1}.data,2) - 1): ...
-varargin{1}.width(2):varargin{1}.start(2)), single(varargin{1}.start(1): ...
varargin{1}.width(1):varargin{1}.start(1) + varargin{1}.width(1)...
* (size(varargin{1}.data,1) - 1)), single(varargin{1}.start(3):...
varargin{1}.width(3):varargin{1}.start(3) + varargin{1}.width(3)...
* (size(varargin{1}.data,3) - 1)));
% Create GPU 3D mesh for secondary dataset
[secX, secY, secZ] = meshgrid(single(varargin{2}.start(2) + ...
varargin{2}.width(2) * (size(varargin{2}.data,2) - 1):...
-varargin{2}.width(2):varargin{2}.start(2)), single(varargin{2}.start(1): ...
varargin{2}.width(1):varargin{2}.start(1) + varargin{2}.width(1) ...
* (size(varargin{2}.data,1) - 1)), single(varargin{2}.start(3):...
varargin{2}.width(3):varargin{2}.start(3) + varargin{2}.width(3) ...
* (size(varargin{2}.data,3) - 1)));
% Attempt to use GPU to interpolate dose to image/structure
% coordinate system. If a GPU compatible device is not
% available, any errors will be caught and CPU interpolation
% will be used instead.
try
% Initialize and clear GPU memory
gpuDevice(1);
% Interpolate the dose to the reference coordinates using
% GPU linear interpolation, and store back to
% varargin{2}.data
varargin{2}.data = gather(interp3(gpuArray(secX), ...
gpuArray(secY), gpuArray(secZ), ...
gpuArray(varargin{2}.data), gpuArray(refX), ...
gpuArray(refY), gpuArray(refZ), 'linear', 0));
% Clear GPU memory
gpuDevice(1);
% Catch any errors that occured and attempt CPU interpolation
% instead
catch
% Interpolate the dose to the reference coordinates using
% linear interpolation, and store back to varargin{2}.data
varargin{2}.data = interp3(secX, secY, secZ, ...
varargin{2}.data, refX, refY, refZ, '*linear', 0);
end
% Clear temporary variables
clear refX refY refZ secX secY secZ;
end
% Store the maximum value in the reference dose
maxdose = max(max(max(varargin{2}.data)));
% If max dose is zero, throw an error
if maxdose == 0
if exist('Event', 'file') == 2
Event('The dose array is empty', 'ERROR');
else
error('The dose array is empty');
end
end
% Initialize array for reference DVH values with 1001 bins
dvh = zeros(nbins + 1, length(varargin{1}.structures) + 1);
% Defined the last column to be the x-axis, ranging from 0 to the
% maximum dose
dvh(:, length(varargin{1}.structures) + 1) = 0:maxdose/nbins:maxdose;
% Loop through each reference structure
for i = 1:length(varargin{1}.structures)
% If valid reference dose data was passed
if isfield(varargin{2}, 'data') && ...
size(varargin{2}.data,1) > 0
% Multiply the dose by the structure mask and reshape into
% a vector (adding 1e-6 is necessary to retain zero dose
% values inside the structure mask)
data = reshape((varargin{2}.data + 1e-6) .* ...
varargin{1}.structures{i}.mask, 1, []);
% Remove all zero values (basically, voxels outside of the
% structure mask
data(data==0) = [];
% Compute cumulative histogram
dvh(1:nbins,i) = 1 - histcounts(data, dvh(:, ...
length(varargin{1}.structures) + 1), ...
'Normalization', 'cdf')';
% Normalize histogram to relative volume
dvh(:,i) = dvh(:,i) / max(dvh(:,i)) * 100;
% Clear temporary variable
clear data;
end
end
% Set varargout, if needed
if nargout == 1
varargout{1} = dvh;
elseif nargout > 1
if exist('Event', 'file') == 2
Event('Too many output variables requested', 'ERROR');
else
error('Too many output variables requested');
end
end
% Clear temporary variable
clear i maxdose;
% Otherwise, the dose array is invalid
else
% Throw an error
if exist('Event', 'file') == 2
Event('The dose array is invalid', 'ERROR');
else
error('The dose array is invalid');
end
end
% If a filename was provided
if nargin == 3
% Extract file name
[~, file, ext] = fileparts(varargin{3});
% Log event
if exist('Event', 'file') == 2
Event(sprintf('Writing dose volume histogram to %s', ...
strcat(file, ext)));
end
% Open a write file handle to the file
fid = fopen(varargin{3}, 'w');
% If a valid file handle was returned
if fid > 0
% Write the file name in the first row, starting with a hash
fprintf(fid, '#,%s\n', strcat(file, ext));
% Write the structure names and volumes in the second row
for i = 1:length(varargin{1}.structures)
fprintf(fid, ',%s (%i)(volume: %0.2f)', ...
varargin{1}.structures{i}.name, i, ...
varargin{1}.structures{i}.volume);
end
fprintf(fid, '\n');
% Circshift dvh to place dose in first column
dvh = circshift(dvh, [0 1])';
% Write dvh contents to file
fprintf(fid, [repmat('%g,', 1, size(dvh,1)), '\n'], dvh);
% Close file handle
fclose(fid);
% Otherwise MATLAB couldn't open a write handle
else
% Throw an error
if exist('Event', 'file') == 2
Event(sprintf('A file handle could not be opened to %s', ...
varargin{3}), 'ERROR');
else
error('A file handle could not be opened to %s', varargin{3});
end
end
end
% Log completion of function
if exist('Event', 'file') == 2
Event(sprintf(['Dose volume histograms completed successfully in ', ...
'%0.3f seconds'], toc));
end
% Clear temporary variables
clear i dvh fid file ext;
% Catch errors, log, and rethrow
catch err
if exist('Event', 'file') == 2
Event(getReport(err, 'extended', 'hyperlinks', 'off'), 'ERROR');
else
rethrow(err);
end
end