123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391 |
- classdef SliceViewerPanel < handle
- %SliceViewerPanel uipanel viewer to SliceViewerPanelSource
- %
- % SliceViewerPanel plots the content from SliceViewerPanelSource (SVPS).
- %
- % Coordinate/Orientation in detail:
- % zOrient = 1: Coronal, abscissa = dim3 = zmesh, ordinate = dim2 = ymesh
- % zOrient = 2: Sagittal, abscissa = dim3 = zmesh, ordinate = dim1 = xmesh
- % zOrient = 3: Transverse, abscissa = dim1 = xmesh, ordinate = dim2 = ymesh
- % or think as this:
- % zOrient = 1: image(zmesh, ymesh, Geometry.rhomw(slice,:,:))
- % zOrient = 2: image(zmesh, xmesh, Geometry.rhomw(:,slice,:))
- % zOrient = 3: image(xmesh, ymesh, Geometry.rhomw(:,:,slice))
- %
- % See also XTPS
- %
- % Author: Xiaohu Mo
- %===============================================================================
- properties
- hSVPS % handle to SliceViewerPanelSource
- zOrien % scroll along which dimension: 1=S; 2=C; 3=T
- % TODO should be private
- % handles to gui objects
- hPanel
- hAxis
- hSlider
- hEdit
- hLineH % horizontal TCS line
- hLineV % vertical TCS line
- hImage % image has CT and dose overlayed manually
- hCbar % colorbar handle
- hROIs % handle vector of overlay ROIs
- hDoseContours % handle vector of overlay isodose lines
- hDoseLegend % handle of legend of isodose lines
- end
- %===============================================================================
- %===============================================================================
- methods
- %-------------------------------------------------------------------------------
- function obj = SliceViewerPanel(hSVPS, zOrien, hPanelParent) % Constructor returns object
- obj.hSVPS = hSVPS;
- obj.zOrien = zOrien;
- obj.hPanel = uipanel('Parent', hPanelParent);
- obj.hAxis = axes('Parent', obj.hPanel);
- obj.hSlider = uicontrol(obj.hPanel, 'Style','slider');
- obj.hEdit = uicontrol(obj.hPanel, 'Style','edit');
- obj.hImage = image('Parent', obj.hAxis);
- obj.hLineH = line('Parent', obj.hAxis, 'XData', [], 'YData', []);
- obj.hLineV = line('Parent', obj.hAxis, 'XData', [], 'YData', []);
- obj.hCbar = [];
- obj.hDoseContours = [];
- obj.hDoseLegend = [];
- % Callback passes 3 args: (obj, src, evt)
- set(obj.hSlider, 'Callback', @obj.Slider_Callback);
- set(obj.hEdit, 'Callback', @obj.Edit_Callback);
- % Properties
- axis(obj.hAxis, 'off');
- set(obj.hImage, 'CData', [], 'CDataMapping', 'scaled');
- set(obj.hLineH, 'HitTest', 'off', 'Color', 'red');
- set(obj.hLineV, 'HitTest', 'off', 'Color', 'red');
- set([obj.hPanel obj.hSlider obj.hEdit], ...
- 'Background', get(get(obj.hPanel, 'Parent'), 'Color'));
- % Add Listener
- addlistener(obj.hSVPS, 'POI', 'PostSet', @obj.handleEvent);
- addlistener(obj.hSVPS, 'Geometry', 'PostSet', @obj.handleEvent);
- addlistener(obj.hSVPS, 'dosedisp', 'PostSet', @obj.handleEvent);
- addlistener(obj.hSVPS, 'ctWindow', 'PostSet', @obj.handleEvent);
- addlistener(obj.hSVPS, 'TCSVisible', 'PostSet', @obj.handleEvent);
- end % function SliceViewerPanel
- %-------------------------------------------------------------------------------
- function set_layout(obj, src, evt)
- % Set widget positions and sizes
- set([obj.hPanel obj.hAxis obj.hSlider obj.hEdit], 'Unit', 'normalized');
-
- padding = [0.01 0.01];
- setPositionXY(obj.hAxis, [0 1], [0.1 1], padding);
- setPositionXY(obj.hSlider, [0 0.8], [0 0.1], padding, 1.5, 'characters');
- setPositionXY(obj.hEdit, [0.8 1], [0 0.1], padding, 1.5, 'characters');
- end
- %-------------------------------------------------------------------------------
- function handleEvent(obj, src, evt)
- switch src.Name
- case 'POI'
- obj.redraw_lines();
- % redraw only changed dimension
- if obj.hSVPS.POI(obj.zOrien) ~= obj.hSVPS.prev_POI(obj.zOrien)
- obj.redraw_image();
- obj.redraw_contour();
- end
- case {'Geometry' 'ctWindow' 'ROIdisp' 'dosedisp'}
- % redraw all
- obj.redraw_image();
- obj.redraw_contour();
- case 'TCSVisible'
- % redraw line
- obj.redraw_lines();
- end
- end
- %-------------------------------------------------------------------------------
- function Slider_Callback(obj, src, evt)
- % check value, update Edit and Axis
- value = round(get(obj.hSlider, 'Value'));
- set(obj.hSlider, 'Value', value);
- set(obj.hEdit, 'String', num2str(value));
- obj.hSVPS.POI(obj.zOrien) = value;
- end % function Slider_Callback
- %-------------------------------------------------------------------------------
- function Edit_Callback(obj, src, evt)
- % check value, update Slider and Axis
- value = round(str2double(get(obj.hEdit, 'String')));
- if value >= get(obj.hSlider, 'Min') && value <= get(obj.hSlider, 'Max')
- set(obj.hSlider, 'Value', value);
- set(obj.hEdit, 'String', num2str(value));
- obj.hSVPS.POI(obj.zOrien) = value;
- else % invalid input, set back to slider value
- value = get(obj.hSlider, 'Value');
- set(obj.hEdit, 'String', num2str(value));
- end
- end % function Edit_Callback
- %-------------------------------------------------------------------------------
- function load_data(obj)
- set(obj.hSlider, 'Min', 1, 'Max', size(obj.hSVPS.Geometry.rhomw, obj.zOrien), 'Value', 1);
- set(obj.hSlider, 'SliderStep', [1 10] / size(obj.hSVPS.Geometry.rhomw, obj.zOrien));
- % set image coordinates, axis limits, DataAspectRatio
- switch obj.zOrien
- case 1
- set(obj.hImage, 'XData', obj.hSVPS.Geometry.z, 'YData', obj.hSVPS.Geometry.y);
- set(obj.hAxis, 'XLim', [min(obj.hSVPS.Geometry.z) max(obj.hSVPS.Geometry.z)]);
- set(obj.hAxis, 'YLim', [min(obj.hSVPS.Geometry.y) max(obj.hSVPS.Geometry.y)]);
- zoom(obj.hAxis, 'reset');
- daspect(obj.hAxis, [obj.hSVPS.Geometry.voxel_size(3) obj.hSVPS.Geometry.voxel_size(2) 1]);
- case 2
- set(obj.hImage, 'XData', obj.hSVPS.Geometry.z, 'YData', obj.hSVPS.Geometry.x);
- set(obj.hAxis, 'XLim', [min(obj.hSVPS.Geometry.z) max(obj.hSVPS.Geometry.z)]);
- set(obj.hAxis, 'YLim', [min(obj.hSVPS.Geometry.x) max(obj.hSVPS.Geometry.x)]);
- zoom(obj.hAxis, 'reset');
- daspect(obj.hAxis, [obj.hSVPS.Geometry.voxel_size(3) obj.hSVPS.Geometry.voxel_size(1) 1]);
- set(obj.hAxis, 'YDir', 'reverse');
- case 3
- set(obj.hImage, 'XData', obj.hSVPS.Geometry.x, 'YData', obj.hSVPS.Geometry.y);
- set(obj.hAxis, 'XLim', [min(obj.hSVPS.Geometry.x) max(obj.hSVPS.Geometry.x)]);
- set(obj.hAxis, 'YLim', [min(obj.hSVPS.Geometry.y) max(obj.hSVPS.Geometry.y)]);
- zoom(obj.hAxis, 'reset');
- daspect(obj.hAxis, [obj.hSVPS.Geometry.voxel_size(1) obj.hSVPS.Geometry.voxel_size(2) 1]);
- set(obj.hAxis, 'YDir', 'reverse');
- end
-
- % default to volume center
- obj.hSVPS.POI = round(size(obj.hSVPS.Geometry.rhomw)/2);
-
- % set slider and edit control when manually set focus point
- slice = obj.hSVPS.POI(obj.zOrien);
- set(obj.hSlider, 'Value', slice);
- set(obj.hEdit, 'String', slice);
- end % function load_data
- %-------------------------------------------------------------------------------
- function updateColorbar(obj)
- % only plot color on transvers axis
- if obj.zOrien ~= 3; return; end
-
- % delete old one
- delete(obj.hCbar); obj.hCbar = [];
- switch lower(obj.hSVPS.dosedisp.mode)
- case {'isodose' 'isodoseline'}
- % get a sorted, valid dose level and colors
- valid_indices = find(obj.hSVPS.dosedisp.level > 0);
- if isempty(valid_indices); return; end
- dose_levels = obj.hSVPS.dosedisp.level(valid_indices);
- dose_colors = obj.hSVPS.dosedisp.color(valid_indices,:);
- [dose_levels iX] = sort(dose_levels);
- dose_labels = arrayfun(@(d)num2str(d), dose_levels, 'UniformOutput', false)';
- cmap = dose_colors(iX, :);
-
- colormap(cmap);
- set(obj.hAxis, 'CLim', [0 1]);
- obj.hCbar = colorbar('Peer', obj.hAxis, 'Location', 'SouthOutside');
- set(obj.hCbar, 'TickLength', [0 0], ...
- 'XTick', (0.5:numel(dose_levels)-0.5)/numel(dose_levels), ...
- 'XTickLabel', dose_labels);
- case 'colorwash'
- colormap(jet(256));
- set(obj.hAxis, 'CLim', [0 max(obj.hSVPS.optResults.dose{end}(:))]);
- obj.hCbar = colorbar('Peer', obj.hAxis, 'Location', 'SouthOutside');
- end
- end % function updateColorbar
- %-------------------------------------------------------------------------------
- function updateAxis(obj)
- % save axis scaling
- % xlim = get(obj.hAxis, 'XLim');
- % ylim = get(obj.hAxis, 'YLim');
-
- % % redraw more if slice changed
- % if obj.hSVPS.POI(obj.zOrien) ~= obj.hSVPS.prev_POI(obj.zOrien)
- % obj.redraw_image();
- % obj.redraw_contour();
- % end
- % obj.redraw_lines();
- % restore axis scaling
- % set(obj.hAxis, 'XLim', xlim);
- % set(obj.hAxis, 'YLim', ylim);
- end
- %-------------------------------------------------------------------------------
- function redraw_lines(obj)
- % P1 & P2 are the extent of the grid
- P1 = [min(obj.hSVPS.Geometry.y) min(obj.hSVPS.Geometry.x) min(obj.hSVPS.Geometry.z)];
- P2 = [max(obj.hSVPS.Geometry.y) max(obj.hSVPS.Geometry.x) max(obj.hSVPS.Geometry.z)];
- % F is the coordinate of the intersection of TCS planes
- F = [obj.hSVPS.Geometry.y(obj.hSVPS.POI(1)) ...
- obj.hSVPS.Geometry.x(obj.hSVPS.POI(2)) ...
- obj.hSVPS.Geometry.z(obj.hSVPS.POI(3))];
-
- switch obj.zOrien
- case 1
- set(obj.hLineH, 'XData', [P1(3) P2(3)], 'YData', [F(2) F(2)]);
- set(obj.hLineV, 'XData', [F(3) F(3)], 'YData', [P1(2) P2(2)]);
- case 2
- set(obj.hLineH, 'XData', [P1(3) P2(3)], 'YData', [F(1) F(1)]);
- set(obj.hLineV, 'XData', [F(3) F(3)], 'YData', [P1(1) P2(1)]);
- case 3
- set(obj.hLineH, 'XData', [P1(2) P2(2)], 'YData', [F(1) F(1)]);
- set(obj.hLineV, 'XData', [F(2) F(2)], 'YData', [P1(1) P2(1)]);
- end
-
- % Set visibility
- if obj.hSVPS.TCSVisible == true
- set(obj.hLineH, 'Visible', 'on');
- set(obj.hLineV, 'Visible', 'on');
- else
- set(obj.hLineH, 'Visible', 'off');
- set(obj.hLineV, 'Visible', 'off');
- end
- end
- %-------------------------------------------------------------------------------
- function redraw_image(obj)
- slice = obj.hSVPS.POI(obj.zOrien);
-
- % get CT image
- switch obj.zOrien
- case 1
- ctImage = imagescX((obj.hSVPS.Geometry.data(slice,:,:)), gray(256), obj.hSVPS.ctClim);
- case 2
- ctImage = imagescX((obj.hSVPS.Geometry.data(:,slice,:)), gray(256), obj.hSVPS.ctClim);
- case 3
- ctImage = imagescX((obj.hSVPS.Geometry.data(:,:,slice)), gray(256), obj.hSVPS.ctClim);
- end
-
- % set image to CT. Will re-set if need to blend with dose later.
- set(obj.hImage, 'CData',ctImage);
- % redraw dose
- delete(obj.hDoseContours);
- obj.hDoseContours = [];
- if ~isempty(obj.hSVPS.optResults)
- switch obj.zOrien
- case 1
- doseSlice = squeeze(obj.hSVPS.optResults.dose{end}(slice,:,:));
- xaxis = obj.hSVPS.Geometry.z;
- yaxis = obj.hSVPS.Geometry.y;
- case 2
- doseSlice = squeeze(obj.hSVPS.optResults.dose{end}(:,slice,:));
- xaxis = obj.hSVPS.Geometry.z;
- yaxis = obj.hSVPS.Geometry.x;
- case 3
- doseSlice = squeeze(obj.hSVPS.optResults.dose{end}(:,:,slice));
- xaxis = obj.hSVPS.Geometry.x;
- yaxis = obj.hSVPS.Geometry.y;
- end
- switch lower(obj.hSVPS.dosedisp.mode)
- case {'isodoseline'}
- if ~isempty(nonzeros(obj.hSVPS.dosedisp.level))
- hold(obj.hAxis, 'on');
- % sort dose levels before contourf
- [tilde, sorted_indices]= sort(obj.hSVPS.dosedisp.level);
- for idx = sorted_indices(:)'
- % scalar of current dose level
- dose_level = obj.hSVPS.dosedisp.level(idx);
- if dose_level ~= 0
- % plot this contour level and append handles to list
- % [level level] is used to avoid single level ambiguity of contour
- [tilde, obj.hDoseContours(end+1)] = contour(obj.hAxis, ...
- xaxis, yaxis, ...
- doseSlice, [dose_level dose_level], ...
- '-', 'LineWidth', 2, ...
- 'LineColor', obj.hSVPS.dosedisp.color(idx, :), ...
- 'DisplayName', sprintf('%.1f', dose_level));
- end
- end
- hold(obj.hAxis, 'off');
- end
- case 'isodose'
- % Using transparency with contourf like this fails due to the overlay of patches:
- % hPatches = findobj(obj.hDoseContours(end), 'Type', 'patch', '-property', 'FaceAlpha');
- % set(hPatches, 'FaceAlpha', obj.hSVPS.dosedisp.alpha(idx));
- if ~isempty(nonzeros(obj.hSVPS.dosedisp.level))
- doseMap = ones(size(doseSlice));
- alphaMap = zeros(size(doseSlice));
- % sort dose levels
- [tilde, sorted_indices]= sort(obj.hSVPS.dosedisp.level);
- for idx = sorted_indices(:)'
- % scalar of current dose level
- dose_level = obj.hSVPS.dosedisp.level(idx);
- % pixels below cmin will have alpha = 0
- if dose_level ~= 0
- doseMap(doseSlice >= dose_level) = idx;
- alphaMap(doseSlice >= dose_level) = obj.hSVPS.dosedisp.alpha(idx);
- end
- end
- % get the color
- doseImage = zeros(size(doseSlice,1), size(doseSlice,2), 3);
- R = obj.hSVPS.dosedisp.color(:, 1);
- G = obj.hSVPS.dosedisp.color(:, 2);
- B = obj.hSVPS.dosedisp.color(:, 3);
- doseImage(:,:,1) = reshape(R(doseMap), size(doseSlice));
- doseImage(:,:,2) = reshape(G(doseMap), size(doseSlice));
- doseImage(:,:,3) = reshape(B(doseMap), size(doseSlice));
- %
- compImage = imblend(doseImage, alphaMap, ctImage, 1);
- set(obj.hImage, 'CData', compImage);
- end
- case 'colorwash'
- doseImage = imagescX(doseSlice, jet(256), [0 max(obj.hSVPS.optResults.dose{end}(:))]);
- alphaMap = interp1([0; 40], [0; 1], doseSlice, 'linear', 1);
- alphaMap = reshape(alphaMap, size(doseSlice));
- compImage = imblend(doseImage, alphaMap, ctImage, 1);
- set(obj.hImage, 'CData', compImage);
- end
- end
- end
- %-------------------------------------------------------------------------------
- function redraw_contour(obj)
- slice = obj.hSVPS.POI(obj.zOrien);
- delete(obj.hROIs);
- obj.hROIs = [];
- hold(obj.hAxis,'on');
- for i = 1:numel(obj.hSVPS.Geometry.ROIS)
- % skip if display == OFF
- if obj.hSVPS.Geometry.ROIS{i}.visible == false
- continue;
- end
- switch obj.zOrien
- case 1
- curve_indices = find(obj.hSVPS.Geometry.ROIS{i}.Xcurves_slices == slice);
- if ~isempty(curve_indices)
- for curve_idx = curve_indices
- obj.hROIs(end+1) = plot(obj.hAxis, ...
- obj.hSVPS.Geometry.ROIS{i}.Xcurves{curve_idx}(:,3), ...
- obj.hSVPS.Geometry.ROIS{i}.Xcurves{curve_idx}(:,2), ...
- '--', 'LineWidth', 2, 'Color', obj.hSVPS.Geometry.ROIS{i}.color);
- end
- end
- case 2
- curve_indices = find(obj.hSVPS.Geometry.ROIS{i}.Ycurves_slices == slice);
- if ~isempty(curve_indices)
- for curve_idx = curve_indices
- obj.hROIs(end+1) = plot(obj.hAxis, ...
- obj.hSVPS.Geometry.ROIS{i}.Ycurves{curve_idx}(:,3), ...
- obj.hSVPS.Geometry.ROIS{i}.Ycurves{curve_idx}(:,1), ...
- '--', 'LineWidth', 2, 'Color', obj.hSVPS.Geometry.ROIS{i}.color);
- end
- end
- case 3
- % curve_indices = find(abs(obj.hSVPS.Geometry.ROIS{i}.curvesZ - obj.hSVPS.Geometry.z(slice)) < 1E-2);
- curve_indices = find(obj.hSVPS.Geometry.ROIS{i}.Zcurves_slices == slice);
- % skip if no curve found on current slice
- if ~isempty(curve_indices)
- for curve_idx = curve_indices
- obj.hROIs(end+1) = plot(obj.hAxis, ...
- obj.hSVPS.Geometry.ROIS{i}.curves{curve_idx}(:,1), ...
- obj.hSVPS.Geometry.ROIS{i}.curves{curve_idx}(:,2), ...
- '--', 'LineWidth', 2, 'Color', obj.hSVPS.Geometry.ROIS{i}.color);
- end
- end
- end
- end
-
- hold(obj.hAxis,'off');
- end
- %-------------------------------------------------------------------------------
- end % methods
- %===============================================================================
- end
|