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(); obj.redraw_overlay(); end case {'Geometry' 'ctWindow' 'ROIdisp' 'dosedisp'} % redraw all obj.redraw_image(); % obj.redraw_contour(); obj.redraw_overlay(); 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}(:))]); % Grozomah set(obj.hAxis, 'CLim', [0 200]); 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' % Grozomah % doseImage = imagescX(doseSlice, jet(256), [0 max(obj.hSVPS.optResults.dose{end}(:))]); doseImage = imagescX(doseSlice, jet(256), [0 max(obj.hSVPS.optResults.dose{end}(:))/1.1]); 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) % This function is not called anymore. See redraw_overlay for % the actual drawing of the contours slice = obj.hSVPS.POI(obj.zOrien); disp('Contour!!') 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 %------------------------------------------------------------------------------- function redraw_overlay(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; first if statement checks if % 'visible' field has been created yet % fprintf('ROI #%.0f', i) if isfield(obj.hSVPS.Geometry.ROIS{i},'visible')==false continue; end if obj.hSVPS.Geometry.ROIS{i}.visible == false % fprintf(' skipped!\n', i) continue; end % fprintf(' analyzed!\n', i) testIdxList=obj.hSVPS.Geometry.ROIS{i}.ind; matSize=size(obj.hSVPS.Geometry.data); [x,y,z] = ind2sub(matSize, testIdxList); a=zeros(matSize); a(testIdxList)=1; % orthoslice(a, [0,1], 3) % orthoslice(obj.hSVPS.Geometry.data, [0,500], 3) % fprintf('ROI #%.0f \n', disp(obj.zOrien)) switch obj.zOrien case 1 curve_indices = find(x == slice); if ~isempty(curve_indices) for curve_idx = curve_indices canvas=zeros(matSize(2),matSize(3)); idx=sub2ind([matSize(2),matSize(3)], y(curve_indices), z(curve_indices)); canvas(idx)=1; % orthoslice(obj.hSVPS.Geometry.data, [0,1]) % sum(canvas(:)) B = bwboundaries(canvas); numContours=length(B); for contourI=1:numContours B_I = B{contourI}; clear C % up-down (+ high) yshift= obj.hSVPS.Geometry.start(2) - 1.0*obj.hSVPS.Geometry.voxel_size(2); zshift= obj.hSVPS.Geometry.start(3) - 1.0*obj.hSVPS.Geometry.voxel_size(3); C(:,1)= yshift + (B_I(:,1))*obj.hSVPS.Geometry.voxel_size(1); C(:,2)= zshift + (B_I(:,2))*obj.hSVPS.Geometry.voxel_size(3); obj.hROIs(end+1) = plot(obj.hAxis, ... C(:,2), C(:,1), ... '-', 'LineWidth', 2, 'Color', obj.hSVPS.Geometry.ROIS{i}.color); end end end % end of IF case 2 curve_indices = find(y == slice); if ~isempty(curve_indices) for curve_idx = curve_indices canvas=zeros(matSize(1),matSize(3)); idx=sub2ind([matSize(1),matSize(3)], x(curve_indices), z(curve_indices)); canvas(idx)=1; % sum(canvas(:)) B = bwboundaries(canvas); numContours=length(B); for contourI=1:numContours B_I = B{contourI}; clear C % up-down (+ high) xshift= obj.hSVPS.Geometry.start(1) - 1.0*obj.hSVPS.Geometry.voxel_size(1); zshift= obj.hSVPS.Geometry.start(3) - 1.0*obj.hSVPS.Geometry.voxel_size(3); C(:,1)= xshift + (B_I(:,1))*obj.hSVPS.Geometry.voxel_size(1); C(:,2)= zshift + (B_I(:,2))*obj.hSVPS.Geometry.voxel_size(3); obj.hROIs(end+1) = plot(obj.hAxis, ... C(:,2), C(:,1), ... '-', 'LineWidth', 2, 'Color', obj.hSVPS.Geometry.ROIS{i}.color); end end end % end of IF case 3 curve_indices = find(z == slice); if ~isempty(curve_indices) for curve_idx = curve_indices canvas=zeros(matSize(1),matSize(2)); idx=sub2ind([matSize(1),matSize(2)], x(curve_indices), y(curve_indices)); canvas(idx)=1; % sum(canvas(:)) B = bwboundaries(canvas); numContours=length(B); for contourI=1:numContours B_I = B{contourI}; clear C % up-down (+ high) yshift= obj.hSVPS.Geometry.start(2) - 1.0*obj.hSVPS.Geometry.voxel_size(2); xshift= obj.hSVPS.Geometry.start(1) - 1.0*obj.hSVPS.Geometry.voxel_size(1); C(:,1)= yshift + (B_I(:,1))*obj.hSVPS.Geometry.voxel_size(1); C(:,2)= xshift + (B_I(:,2))*obj.hSVPS.Geometry.voxel_size(2); obj.hROIs(end+1) = plot(obj.hAxis, ... C(:,2), C(:,1), ... '-', 'LineWidth', 2, 'Color', obj.hSVPS.Geometry.ROIS{i}.color); end end end % end of IF end end hold(obj.hAxis,'off'); end %------------------------------------------------------------------------------- end % methods %=============================================================================== end