From a0ed2686d5b393ced0574916ee6bf52def7943a7 Mon Sep 17 00:00:00 2001 From: Jim Pollaro Date: Fri, 26 Jul 2024 09:47:45 -0500 Subject: [PATCH 01/14] last function change before class --- +nla/+gfx/drawBrainVis.m | 47 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/+nla/+gfx/drawBrainVis.m b/+nla/+gfx/drawBrainVis.m index b9e845bd..aa6a7311 100755 --- a/+nla/+gfx/drawBrainVis.m +++ b/+nla/+gfx/drawBrainVis.m @@ -157,7 +157,20 @@ function drawEdges(ROI_pos, ax, net_atlas, net1, net2, color_map, color_map_p, c end end - function onePlot(ax, pos, color_mode, color_mat) + function onePlot(varargin) + if nargin >= 3 + ax = varargin{1}; + pos = varargin{2}; + color_mode = varargin{3}; + end + if nargin >= 4 + color_mat = varargin{4}; + end + if nargin > 4 + ulimit = str2double(varargin{5}); + llimit = str2double(varargin{6}); + end + if color_mode == nla.gfx.BrainColorMode.NONE ROI_final_pos = nla.gfx.drawROIsOnCortex(ax, net_atlas, ctx, mesh_alpha, ROI_radius, pos, surface_parcels,... nla.gfx.BrainColorMode.NONE); @@ -213,6 +226,37 @@ function onePlot(ax, pos, color_mode, color_mat) hold(ax, 'off'); nla.gfx.hideAxes(ax); + function openModal(source, ~) + d = figure("WindowStyle", "normal", "Units", "pixels", "Position", [source.Parent.Position(1), source.Parent.Position(2), 250, 150]); + upper_limit_box = uicontrol("Style", "edit", "Units", "pixels", "Position", [d.Position(3) / 2, d.Position(4) / 2 + 5, 50, 25], "String", ulimit); + lower_limit_box = uicontrol("Style", "edit", "Units", "pixels", "Position", [d.Position(3) / 2, d.Position(4) / 2 - upper_limit_box.Position(4) - 5, 50, 25], "String", llimit); + uicontrol("Style", "text", "String", "Upper Limit", "Units", "pixels", "Position", [upper_limit_box.Position(1) - 80, upper_limit_box.Position(2) - 5, 80, upper_limit_box.Position(4)]); + uicontrol("Style", "text", "String", "Lower Limit", "Units", "pixels", "Position", [lower_limit_box.Position(1) - 80, lower_limit_box.Position(2) - 5, 80, lower_limit_box.Position(4)]); + + apply_button_position = [d.Position(3) / 2 - 85, 10, 80, 25]; + close_button_position = [apply_button_position(1) + 85, apply_button_position(2), apply_button_position(3), apply_button_position(4)]; + uicontrol("String", "Apply", "Units", "pixels", "Position", apply_button_position, "Callback", {@applyScale, upper_limit_box, lower_limit_box}); + uicontrol("String", "Close", "Units", "pixels", "Position", close_button_position, "Callback", @(~, ~)close(d)); + end + + function applyScale(~, ~, upper_limit_box, lower_limit_box) + upper_limit = get(upper_limit_box, "String"); + lower_limit = get(lower_limit_box, "String"); + figure(fig) + if surface_parcels && ~islogical(net_atlas.parcels) + onePlot(subplot('Position',[.45,0.505,.53,.45]), nla.gfx.ViewPos.LAT, nla.gfx.BrainColorMode.COLOR_ROIS, color_mat, upper_limit, lower_limit); + onePlot(subplot('Position',[.45,0.055,.53,.45]), nla.gfx.ViewPos.MED, nla.gfx.BrainColorMode.COLOR_ROIS, color_mat, upper_limit, lower_limit); + else + onePlot(subplot('Position',[.45,0.505,.26,.45]), nla.gfx.ViewPos.BACK, nla.gfx.BrainColorMode.NONE, false, upper_limit, lower_limit); + onePlot(subplot('Position',[.73,0.505,.26,.45]), nla.gfx.ViewPos.FRONT, nla.gfx.BrainColorMode.NONE, false, upper_limit, lower_limit); + onePlot(subplot('Position',[.45,0.055,.26,.45]), nla.gfx.ViewPos.LEFT, nla.gfx.BrainColorMode.NONE, false, upper_limit, lower_limit); + onePlot(subplot('Position',[.73,0.055,.26,.45]), nla.gfx.ViewPos.RIGHT, nla.gfx.BrainColorMode.NONE, false, upper_limit, lower_limit); + end + % ROI_final_pos = nla.gfx.drawROIsOnCortex(ax, net_atlas, ctx, mesh_alpha, ROI_radius, pos, surface_parcels,... + % nla.gfx.BrainColorMode.NONE); + % drawEdges(ROI_final_pos, ax, net_atlas, net1, net2, color_map, color_map_p, color_map_n, color_fx, fc_exists, lower_limit, upper_limit); + end + %% Display colormap if color_fc % legend(ax, 'Location', 'best'); @@ -232,6 +276,7 @@ function onePlot(ax, pos, color_mode, color_mat) cb = colorbar(ax); cb.Location = 'southoutside'; cb.Label.String = 'Coefficient Magnitude'; + cb.ButtonDownFcn = @openModal; ticks = [0:num_ticks]; cb.Ticks = double(ticks) ./ num_ticks; From 0b36f7040232d83e75be010db97f1ed4999ba1db Mon Sep 17 00:00:00 2001 From: Jim Pollaro Date: Wed, 31 Jul 2024 10:36:53 -0500 Subject: [PATCH 02/14] first shot at new brain plots --- +nla/+gfx/+brain/BrainPlot.m | 395 +++++++++++++++++++++++++++++++++++ 1 file changed, 395 insertions(+) create mode 100644 +nla/+gfx/+brain/BrainPlot.m diff --git a/+nla/+gfx/+brain/BrainPlot.m b/+nla/+gfx/+brain/BrainPlot.m new file mode 100644 index 00000000..c7ed07e5 --- /dev/null +++ b/+nla/+gfx/+brain/BrainPlot.m @@ -0,0 +1,395 @@ +classdef BrainPlot < handle + + properties + plot_figure + edge_test_options + network_test_options + network_atlas + edge_test_result + network1 + network2 + test_name + upper_limit + lower_limit + color_map = cat(1, winter(1000), flip(autumn(1000))); + ROI_values + function_connectivity_values + ROI_radius + surface_parcels + end + + properties (Constant) + noncorrelation_input_tests = ["chi_squared", "hypergeometric"] % These are tests that do not use correlation coefficients as inputs + end + + properties (Dependent) + is_noncorrelation_input + functional_connectivity_exists + end + + methods + + function obj = BrainPlot(edge_test_result, edge_test_options, network_test_options, network1, network2, network_atlas) + brain_input_parser = inputParser; + addRequired(brain_input_parser, "edge_test_result"); + addRequired(brain_input_parser, "edge_test_options"); + addRequired(brain_input_parser, "network_test_options"); + addRequired(brain_input_parser, "network1"); + addRequired(brain_input_parser, "network2"); + addRequired(brain_input_parser, "network_atlas"); + + validNumberInput = @(x) isnumeric(x) && isscalar(x) + addParameter(brain_input_parser, "ROI_radius", 0, validNumberInput); + addParameter(brain_input_parser, "surface_parcels", false, @isboolean); + + parse(brain_input_parser, edge_test_result, edge_test_options, network_test_options, network1, network2, network_atlas, varargin{:}); + properties = {"edge_test_result", "edge_test_options", "network_test_options", "network1", "network2", "network_atlas", "ROI_radius", "surface_parcels"}; + for property = properties + obj.(property{1}) = brain_input_parser.Results.(property{1}); + end + + %% + % everything below here we'll need, but we need other values set first. and they need to be editable + obj.plot_figure = nla.gfx.createFigure(1550, 750); + figure_title = sprintf("Brain Visualization: Average of edge-level correlations between nets in [%s - %s] Network Pair",... + obj.network_atlas.nets(obj.network1).name, obj.network_atlas.nets(obj.network2).name); + if obj.is_noncorrelation_input + figure_title = [figure_title sprintf(" (Edge-level P < %.2g)", obj.edge_test_options.prob_max)]; + end + obj.plot_figure.Name = figure_title; + + obj.upper_limit = obj.edge_test_result.coeff_range(1); + obj.lower_limit = obj.edge_test_result.coeff_range(2); + + obj.ROI_values = nan(obj.network_atlas.numROIs(), 1); + obj.function_connectivity_values = nan(obj.network_atlas.numROIs(), 1) + %% + end + + function drawBrainPlots(obj) + import nla.gfx.ViewPos nla.gfx.BrainColorMode + + obj.setROIandConnectivity(); + + all_edges = []; + if obj.surface_parcels && ~islogical(obj.network_atlas.parcels) + edges1 = obj.single_plot(subplot("Position", [0.45, 0.505, 0.53, 0.45], ViewPos.LAT, BrainColorMode.COLOR_ROIS, obj.color_map)); + edges2 = obj.single_plot(subplot("Position", [0.45, 0.055, 0.53, 0.45], ViewPos.MED, BrainColorMode.COLOR_ROIS, obj.color_map)); + all_edges = [edges1 edges2]; + else + edges1 = obj.single_plot(subplot("Position", [0.45, 0.505, 0.26, 0.45], ViewPos.BACK, BrainColorMode.NONE)); + edges2 = obj.single_plot(subplot("Position", [0.73, 0.505, 0.26, 0.45], ViewPos.FRONT, BrainColorMode.NONE]); + edges3 = obj.single_plot(subplot("Position", [0.45, 0.055, 0.26, 0.45], ViewPos.LEFT, BrainColorMode.NONE)); + edges4 = obj.single_plot(subplot("Position", [0.73, 0.055, 0.26, 0.45], ViewPos.RIGHT, BrainColorMode.None)); + all_edges = [edges1 edges2 edges3 edges4]; + end + + if obj.functional_connectivity_exists + plot_axis = subplot("Position", [0.075, 0.175, 0.35, 0.75]); + else + plot_axis = subplot("Position", [0.075, 0.025, 0.35, 0.85]); + end + + if obj.surface_parcels && ~islogical(obj.network_atlas.parcels) + obj.single_plot(plot_axis, ViewPos.DORSAL, BrainColorMode.COLOR_ROIS, obj.colo_maps); + else + obj.single_plot(plot_axis, ViewPos.DORSAL, BrainColorMode.NONE); + end + + light("Position", [0, 100, 100], "Style", "local"); + + % Display Legend + hold(plot_axis, "on"); + if obj.network1 == obj.network2 + legend_entry = bar(plot_axis, NaN); + legend_entry.FaceColor = obj.network_atlas.nets(obj.network1).color; + legend_entry.DisplayName = obj.network_atlas.nets(obj.network1).name; + else + for network = [obj.network1, obj.network2] + legend_entry = bar(plot_axs, NaN); + legend_entry.FaceColor = obj.network_atlas.nets(network).color; + legend_entry.DisplayName = obj.network_atlas.nets(network).name; + end + end + hold(plot_axis, "off"); + nla.gfx.hideAxes(plot_axis); + + obj.drawColorMap(plot_axis); + end + + function setROIandConnectivity(obj) + + network1_ROI_indexes = obj.network_atlas.nets(obj.network1).indexes; + network2_ROI_indexes = obj.network_atlas.nets(obj.network2).indexes; + + for ROI_index1 = 1:numel(network1_ROI_indexes) + network1_ROI_index = network1_ROI_indexes(ROI_index1); + [coefficient1, coefficient2, function_connectivity1, function_connectivity2] = obj.getCoefficients(network1_ROI_index, network2_ROI_indexes); + obj.ROI_values(ROI_index1) = (sum(coefficient1) + sum(coefficient2)) / (numel(coffecient1) + numel(coefficient2)); + obj.function_connectivity_values(ROI_index1) = (sum(function_connectivity1) + sum(function_connectivity2)) / (numel(function_connectivity1) + numel(function_connectivity2)); + end + + for ROI_index2 = 1:numel(network2_ROI_indexes) + network2_ROI_index = network2_ROI_indexes(ROI_index2); + [coefficient1, coefficient2, function_connectivity1, function_connectivity2] = obj.getCoefficients(network2_ROI_index, network1_ROI_indexes); + ROI_value = (sum(coefficient1) + sum(coefficient2)) / (numel(coffecient1) + numel(coefficient2)); + function_connectivity_value = (sum(function_connectivity1) + sum(function_connectivity2)) / (numel(function_connectivity1) + numel(function_connectivity2)); + if obj.network1 == obj.network2 + obj.ROI_values(network2_ROI_index) = (obj.ROI_values(network2_ROI_index) + ROI_value) ./ 2; + obj.function_connectivity_values(network2_ROI_index) = (obj.function_connectivity_values(network2_ROI_index) + function_connectivity_values) ./ 2; + else + obj.ROI_values(network2_ROI_index) = ROI_value; + obj.function_connectivity_values(network2_ROI_index) = function_connectivity_value; + end + end + end + + function [coefficient1, coefficient2, function_connectivity1, function_connectivity2] =... + getCoefficients(obj, network1_index, network2_indexes) + + coefficient1 = obj.edge_test_result.coeff.get(network1_index, network2_indexes); + coefficient2 = obj.edge_test_result.coeff.get(network2_indexes, network1_index); + + function_connectivity1 = false; + function_connectivity2 = false; + if obj.functional_connectivity_exists + function_connectivity1 = mean(obj.edge_test_options.func_conn.get(network1_index, network2_indexes), 2); + function_connectivity2 = mean(obj.edge_test_options.func_conn.get(network2_indexes, network1_index), 2); + end + + if obj.is_noncorrelation_input + probability_significance1 = obj.edge_test_result.prob_sig.get(network1_index, network2_indexes); + probability_significance2 = obj.edge_test_result.prob_sig.get(network2_indexes, network1_index); + + coefficient1 = coefficient1(logical()); + coefficient2 = coefficient2(logical()); + function_connectivity1 = function_connectivity1(logical(probability_significance1)); + function_connectivity2 = function_connectivity2(logical(probability_significance2)); + end + end + + function colors = mapColorsToLimits(obj) + import nla.gfx.valToColor + + color_rows = size(colors); + color_map_positive = obj.color_map(1:(color_rows/2), :); + color_map_negative = obj.color_map(((color_rows/2) + 1):end, :); + + if functional_connectivity_exists + colors_positive = valToColor(obj.function_connectivity_vals, -0.5, 0.5, color_map_positive); + colors_negative = valToColor(obj.function_connectivity_vals, -0.5, 0.5, color_map_negative); + colors(obj.ROI_values > 0, :) = colors_positive(obj.ROI_values > 0, :); + colors(obj.ROI_values <= 0, :) = colors_negative(obj.ROI_values <= 0, :); + else + colors = valToColor(obj.ROI_values, obj.lower_limit, obj.upper_limit, obj.color_map); + end + colors(isnan(obj.ROI_values), :) = 0.5; + end + + function edges = drawEdges(obj, ROI_position, plot_axis) + + point1_indexes = obj.network_atlas.nets(obj.network1).indexes; + point2_indexes = obj.network_atlas.nets(obj.network2).indexes; + + edges = []; + for point1_index = 1:numel(point1_indexes) + point1 = point1_indexes(point1_index); + for point2_index = 1:numel(point2_indexes) + point2 = point2_indexes(point2_index); + if point1 < point2 + network_point1 = point2; + network_point2 = point1; + else + network_point1 = point1; + network_point2 = point2; + end + + [coefficient, ~, function_connectivity_vector, ~] = obj.getCoefficients(network_point1, network_point2); + function_connectivity_average = mean(function_connectivity_vector); + + if ~isempty(cofficient) + color_value = obj.mapValuesToLimits(); + color_value = [reshape(color_value, [1, 3]), 0.5]; + edge = plot3(plot_axis, [ROI_position(network_point1, 1), ROI_position(network_point2, 1)],... + [ROI_position(network_point1, 2), ROI_position(network_point2, 2)],... + [ROI_position(network_point1, 3), ROI_position(network_point2, 3)],... + "Color", color_value, "LineWidth", 5); + edge.Annotation.LegendInformation.IconDisplayStyle = "off"; + edges = [edges edge]; + end + end + end + end + + function drawCortex(obj, anatomy, plot_axis, mesh_type, mesh_alpha, view_position, left_color, right_color) + import nla.gfx.ViewPos + + % Set some defaults up + plot_axis.Color = 'w'; + if ~exist("left_color", "var") + left_color = repmat(0.5, [size(anatomy.hemi_l.nodes, 1), 3]); + end + if ~exist("right_color", "var") + right_color = repmat(0.5, [size(anatomy.hemi_r.nodes, 1), 3]); + end + + % Re-position hemisphere meshes to transverse/axial orientation + [left_mesh, right_mesh] = nla.gfx.anatToMesh(anatomy, mesh_type, view_position); + + % Set lighting and view positioning + if view_position == ViewPos.LAT || ViewPos.MED + view(plot_axis, [-90, 0]); + % local light is akin to a lightbulb at that location in space + % infinite light has light that originates at that point and only goes in one direction + light(plot_axis, "Position", [-100, 200, 0], "Style", "local"); + light(plot_axis, "Position", [-50, -500, 100], "Style", "infinite"); + light(plot_axis, "Position", [-50, 0, 0], "Style", "infinite"); + else: + view(plot_axis, [0, 0]); + switch view_position + case ViewPos.DORSAL + view(plot_axis, [0, 90]); + light(plot_axis, "Position", [100, 300, 100], "Style", "infiinite"); + case ViewPos.LEFT + view(plot_axis, [-90, 0]); + light(plot_axis, "Position", [-100, 0, 0], "Style", "infinite"); + case ViewPos.RIGHT + view(plot_axis, [90, 0]); + light(plot_axis, "Position", [100, 0, 0], "Style", "infinite"); + case ViewPos.FRONT + view(plot_axis, [180, 0]); + light(plot_axis, "Position", [100, 300, 100], "Style", "infinite"); + case ViewPos.BACK + light(plot_axis, "Position", [0, -200, 0], "Style", "infinite"); + + light(plot_axis, "Position", [-500, -20, 0], "Style", "local"); + light(plot_axis, "Position", [500, -20, 0], "Style", "local"); + light(plot_axis, "Position", [0, -200, 50], "Style", "local"); + + left_hemisphere = obj.drawCortexHemisphere(plot_axis, anatomy.hemi_l, left_mesh, left_color, mesh_alpha); + right_hemisphere = obj.drawCortexHemisphere(plot_axis, anatomy.hemi_r, right_mesh, right_color, mesh_alpha); + + axis(plot_axis, "image"); + axis(plot_axis, "off"); + end + + function hemisphere = drawCortexHemisphere(obj, plot_axis, hemisphere_anatomy, mesh, color, mesh_alpha) + properties = struct( + "Faces", hemisphere_anatomy.elements(:, 1:3), "Vertices", mesh,... + "EdgeColor", "none", "FaceColor", "interp", "FaceVertexCData", color,... + "FaceLightin", "gourand", "FaceAlpha", mesh_alpha,... + "AmbientStrength", 0.25, "DiffuseStrengh", 0.75, "SpecularStrength", 0.1... + ); + hemisphere = patch(plot_axis, properties); + hemisphere.Annotation.LegendInformation.IconDisplayStyle = "off"; + end + + function edges = singlePlot(obj, plot_axis, view_position, mesh_type, color_mode, color_matrix, upper_limit, lower_limit) + + if exist("upper_limit", "var") + self.upper_limit = upper_limit; + end + if exist("lower_limit", "var") + self.lower_limit = lower_limit; + end + + connectivity_map = ~isnan(obj.ROI_values); + + [ROI_final_positions, ROI_colors = obj.getROIPositions(mesh_type, view_position, color_mode, color_matrix); + if ~isequal(color_mode, nla.BrainColorMode.NONE) && obj.surface_parcels && ~islogical(obj.network_atlas.parcels) && isequal(size(obj.network_atlas.parcels.ctx_l,1), size(obj.network_atlas.anat.hemi_l.nodes, 1)) && isequal(size(obj.network_atlas.parcels.ctx_r, 1), size(obj.network_atlas.anat.hemi_r.nodes, 1)) + ROI_color_map = [0.5 0.5 0.5; ROI_colors]; + obj.drawCortex(obj.network_atlas.anat, plot_axis, mesh_type, mesh_alpha, view_position, ROI_color_map(obj.network_atlas.parcels.ctx_l + 1, :), ROI_color_map(obj.network_atlas.parcels.ctx_r + 1, :)); + else + obj.drawCortex(obj.network_atlas.anat, plot_axis, mesh_type, mesh_alpha, view_position); + if ~isequal(color_mode, nla.BrainColorMode.NONE) + for roi = 1:obj.network_atlas.numROIs(): + nla.gfx.drawSphere(plot_axis, ROI_final_positions(roi, :), ROI_colors(roi, :), obj.ROI_radius); + end + end + edges = obj.drawEdges(ROI_final_positions, plot_axis); + end + + if ~isfield(obj.edge_test_options, "show_ROI_centroids") || (isfield(obj.edge_test_options, "show_ROI_centroids") && isequal(obj.edge_test_options.show_ROI_centroids, true)); + obj.drawROISpheres(ROI_final_positions, plot_axis, connectivity_map); + end + + colorbar(plot_axis, "off"); + hold(plot_axis, on); + end + + function drawROISpheres(obj, ROI_position, plot_axis, connectivity_map) + + for network = [obj.network1, obj.network2] + network_indexes = obj.network_atlas.nets(network).indexes; + for index_iterator = 1:numel(network_indexes) + index = network_indexes(index_iterator); + + if connectivity_map(index) + nla.gfx.drawSphere(plot_axis, ROI_position(index, :), obj.network_atlas.nets(network).color, obj.ROI_radius); + end + end + end + end + + function [ROI_final_positions, ROI_colors] = getROIPositions(obj, mesh_type, view_position, color_mode, color_matrix) + import nla.gfx.BrainColorMode + + [left_mesh, right_mesh] = nla.gfx.anatToMesh(obj.network_atlas.anat, mesh_type, view_position); + ROI_positions = [obj.network_atlas.ROIs.pos]'; + + [left_indexes, left_distances] = knnsearch(obj.network_atlas.anat.hemi_l.nodes, ROI_positions); + [right_indexes, right_distances] = knnsearch(obj.network_atlas.anat.hemi_r.nodes, ROI_positions); + + for network = 1:obj.network_atlas.numNets() + for network_indexes = 1:numel(obj.network_atlas.nets(network).indexes) + ROI_index = obj.network_atlas.nets(network).indexes(network_indexes); + offset = [NaN NaN NaN]; + if left_distances(ROI_index) < right_distances(ROI_index) + offset = left_mesh(left_indexes(ROI_index, :) - obj.network_atlas.anat.hemi_l.nodes(ROI_index), :); + else: + offset = right_mesh(right_indexes(ROI_index, :) - obj.network_atlas.anat.hemi_r.nodes(ROI_index), :); + end + ROI_final_positions(ROI_index, :) = ROI_positions(ROI_index, :) + offset; + + switch color_mode + case BrainColorMode.DEFAULT_NETS + ROI_colors(ROI_index, :) = obj.network_atlas.nets(network).color; + case BrainColorMode.COLOR_NETS + ROI_colors(ROI_index, :) = color_matrix(network, :); + case BrainColorMode.COLOR_ROIS + ROI_colors(ROI_index, :) = color_matrix(ROI_index, :); + end + end + end + end + + function drawColorMap(obj, plot_axis) + if obj.functional_connectivity_exists + colormap(plot_axis, obj.color_map); + color_bar = colorbar(plot_axis); + color_bar.Location = "southoutside"; + else + colormap(plot_axis, obj.color_map); + color_bar = colorbar(plot_axis); + color_bar.Location = "southoutside"; + color_bar.ButtonDownFcn = @openModal; + + number_of_ticks = 10; + ticks = [0:number_of_ticks]; + color_bar.Ticks = double(ticks) ./ number_of_ticks; + tick_labels = {}; + for tick = ticks + tick_labels{tick + 1} = sprintf("%.2g", obj.lower_limit + (tick * ((double(obj.upper_limit - obj.lower_limit) / number_of_ticks)))); + end + color_bar.TickLabels = tick_labels; + caxis(plot_axis, [0, 1]); + end + end + + %% GETTERS for dependent properties + function value = get.is_noncorrelation_input(obj) + % Convenience method to determine if inputs were correlation coefficients, or "significance" values + value = any(strcmp(obj.noncorrelation_input_tests, obj.test_name)); + end + end +end \ No newline at end of file From ce35adc873e144e824b7c3b65cd6eb7d40352019 Mon Sep 17 00:00:00 2001 From: Jim Pollaro Date: Fri, 2 Aug 2024 13:57:00 -0500 Subject: [PATCH 03/14] Finally got brain plots to match original --- +nla/+gfx/+brain/BrainPlot.m | 197 ++++++++++-------- +nla/+gfx/drawCortexHemi.m | 2 +- .../+net/+result/NetworkResultPlotParameter.m | 8 +- 3 files changed, 119 insertions(+), 88 deletions(-) diff --git a/+nla/+gfx/+brain/BrainPlot.m b/+nla/+gfx/+brain/BrainPlot.m index c7ed07e5..65dfbb3b 100644 --- a/+nla/+gfx/+brain/BrainPlot.m +++ b/+nla/+gfx/+brain/BrainPlot.m @@ -12,10 +12,12 @@ upper_limit lower_limit color_map = cat(1, winter(1000), flip(autumn(1000))); - ROI_values - function_connectivity_values + ROI_values = [] + function_connectivity_values = [] ROI_radius surface_parcels + mesh_type + mesh_alpha end properties (Constant) @@ -25,11 +27,12 @@ properties (Dependent) is_noncorrelation_input functional_connectivity_exists + color_functional_connectivity end methods - function obj = BrainPlot(edge_test_result, edge_test_options, network_test_options, network1, network2, network_atlas) + function obj = BrainPlot(edge_test_result, edge_test_options, network_test_options, network1, network2, network_atlas, varargin) brain_input_parser = inputParser; addRequired(brain_input_parser, "edge_test_result"); addRequired(brain_input_parser, "edge_test_options"); @@ -38,12 +41,14 @@ addRequired(brain_input_parser, "network2"); addRequired(brain_input_parser, "network_atlas"); - validNumberInput = @(x) isnumeric(x) && isscalar(x) - addParameter(brain_input_parser, "ROI_radius", 0, validNumberInput); - addParameter(brain_input_parser, "surface_parcels", false, @isboolean); + validNumberInput = @(x) isnumeric(x) && isscalar(x); + addParameter(brain_input_parser, "ROI_radius", 3, validNumberInput); + addParameter(brain_input_parser, "surface_parcels", true, @isboolean); + addParameter(brain_input_parser, "mesh_type", nla.gfx.MeshType.STD, @isenum); + addParameter(brain_input_parser, "mesh_alpha", 0.25, validNumberInput); parse(brain_input_parser, edge_test_result, edge_test_options, network_test_options, network1, network2, network_atlas, varargin{:}); - properties = {"edge_test_result", "edge_test_options", "network_test_options", "network1", "network2", "network_atlas", "ROI_radius", "surface_parcels"}; + properties = {"edge_test_result", "edge_test_options", "network_test_options", "network1", "network2", "network_atlas", "ROI_radius", "surface_parcels", "mesh_type", "mesh_alpha"}; for property = properties obj.(property{1}) = brain_input_parser.Results.(property{1}); end @@ -51,18 +56,12 @@ %% % everything below here we'll need, but we need other values set first. and they need to be editable obj.plot_figure = nla.gfx.createFigure(1550, 750); - figure_title = sprintf("Brain Visualization: Average of edge-level correlations between nets in [%s - %s] Network Pair",... - obj.network_atlas.nets(obj.network1).name, obj.network_atlas.nets(obj.network2).name); - if obj.is_noncorrelation_input - figure_title = [figure_title sprintf(" (Edge-level P < %.2g)", obj.edge_test_options.prob_max)]; - end - obj.plot_figure.Name = figure_title; - obj.upper_limit = obj.edge_test_result.coeff_range(1); - obj.lower_limit = obj.edge_test_result.coeff_range(2); + obj.upper_limit = obj.edge_test_result.coeff_range(2); + obj.lower_limit = obj.edge_test_result.coeff_range(1); obj.ROI_values = nan(obj.network_atlas.numROIs(), 1); - obj.function_connectivity_values = nan(obj.network_atlas.numROIs(), 1) + obj.function_connectivity_values = nan(obj.network_atlas.numROIs(), 1); %% end @@ -73,27 +72,27 @@ function drawBrainPlots(obj) all_edges = []; if obj.surface_parcels && ~islogical(obj.network_atlas.parcels) - edges1 = obj.single_plot(subplot("Position", [0.45, 0.505, 0.53, 0.45], ViewPos.LAT, BrainColorMode.COLOR_ROIS, obj.color_map)); - edges2 = obj.single_plot(subplot("Position", [0.45, 0.055, 0.53, 0.45], ViewPos.MED, BrainColorMode.COLOR_ROIS, obj.color_map)); + edges1 = obj.singlePlot(subplot("Position", [0.45, 0.505, 0.53, 0.45]), ViewPos.LAT, BrainColorMode.COLOR_ROIS, obj.color_map); + edges2 = obj.singlePlot(subplot("Position", [0.45, 0.055, 0.53, 0.45]), ViewPos.MED, BrainColorMode.COLOR_ROIS, obj.color_map); all_edges = [edges1 edges2]; else - edges1 = obj.single_plot(subplot("Position", [0.45, 0.505, 0.26, 0.45], ViewPos.BACK, BrainColorMode.NONE)); - edges2 = obj.single_plot(subplot("Position", [0.73, 0.505, 0.26, 0.45], ViewPos.FRONT, BrainColorMode.NONE]); - edges3 = obj.single_plot(subplot("Position", [0.45, 0.055, 0.26, 0.45], ViewPos.LEFT, BrainColorMode.NONE)); - edges4 = obj.single_plot(subplot("Position", [0.73, 0.055, 0.26, 0.45], ViewPos.RIGHT, BrainColorMode.None)); + edges1 = obj.singlePlot(subplot("Position", [0.45, 0.505, 0.26, 0.45]), ViewPos.BACK, BrainColorMode.NONE, obj.color_map); + edges2 = obj.singlePlot(subplot("Position", [0.73, 0.505, 0.26, 0.45]), ViewPos.FRONT, BrainColorMode.NONE, obj.color_map); + edges3 = obj.singlePlot(subplot("Position", [0.45, 0.055, 0.26, 0.45]), ViewPos.LEFT, BrainColorMode.NONE, obj.color_map); + edges4 = obj.singlePlot(subplot("Position", [0.73, 0.055, 0.26, 0.45]), ViewPos.RIGHT, BrainColorMode.NONE, obj.color_map); all_edges = [edges1 edges2 edges3 edges4]; end - if obj.functional_connectivity_exists + if obj.color_functional_connectivity plot_axis = subplot("Position", [0.075, 0.175, 0.35, 0.75]); else plot_axis = subplot("Position", [0.075, 0.025, 0.35, 0.85]); end if obj.surface_parcels && ~islogical(obj.network_atlas.parcels) - obj.single_plot(plot_axis, ViewPos.DORSAL, BrainColorMode.COLOR_ROIS, obj.colo_maps); + obj.singlePlot(plot_axis, ViewPos.DORSAL, BrainColorMode.COLOR_ROIS, obj.color_map); else - obj.single_plot(plot_axis, ViewPos.DORSAL, BrainColorMode.NONE); + obj.singlePlot(plot_axis, ViewPos.DORSAL, BrainColorMode.NONE, obj.color_map); end light("Position", [0, 100, 100], "Style", "local"); @@ -106,7 +105,7 @@ function drawBrainPlots(obj) legend_entry.DisplayName = obj.network_atlas.nets(obj.network1).name; else for network = [obj.network1, obj.network2] - legend_entry = bar(plot_axs, NaN); + legend_entry = bar(plot_axis, NaN); legend_entry.FaceColor = obj.network_atlas.nets(network).color; legend_entry.DisplayName = obj.network_atlas.nets(network).name; end @@ -115,6 +114,8 @@ function drawBrainPlots(obj) nla.gfx.hideAxes(plot_axis); obj.drawColorMap(plot_axis); + + obj.addTitle(); end function setROIandConnectivity(obj) @@ -122,21 +123,21 @@ function setROIandConnectivity(obj) network1_ROI_indexes = obj.network_atlas.nets(obj.network1).indexes; network2_ROI_indexes = obj.network_atlas.nets(obj.network2).indexes; - for ROI_index1 = 1:numel(network1_ROI_indexes) - network1_ROI_index = network1_ROI_indexes(ROI_index1); + for ROI_index1_iterator = 1:numel(network1_ROI_indexes) + network1_ROI_index = network1_ROI_indexes(ROI_index1_iterator); [coefficient1, coefficient2, function_connectivity1, function_connectivity2] = obj.getCoefficients(network1_ROI_index, network2_ROI_indexes); - obj.ROI_values(ROI_index1) = (sum(coefficient1) + sum(coefficient2)) / (numel(coffecient1) + numel(coefficient2)); - obj.function_connectivity_values(ROI_index1) = (sum(function_connectivity1) + sum(function_connectivity2)) / (numel(function_connectivity1) + numel(function_connectivity2)); + obj.ROI_values(network1_ROI_index) = (sum(coefficient1) + sum(coefficient2)) / (numel(coefficient1) + numel(coefficient2)); + obj.function_connectivity_values(network1_ROI_index) = (sum(function_connectivity1) + sum(function_connectivity2)) / (numel(function_connectivity1) + numel(function_connectivity2)); end - for ROI_index2 = 1:numel(network2_ROI_indexes) - network2_ROI_index = network2_ROI_indexes(ROI_index2); + for ROI_index2_iterator = 1:numel(network2_ROI_indexes) + network2_ROI_index = network2_ROI_indexes(ROI_index2_iterator); [coefficient1, coefficient2, function_connectivity1, function_connectivity2] = obj.getCoefficients(network2_ROI_index, network1_ROI_indexes); - ROI_value = (sum(coefficient1) + sum(coefficient2)) / (numel(coffecient1) + numel(coefficient2)); + ROI_value = (sum(coefficient1) + sum(coefficient2)) / (numel(coefficient1) + numel(coefficient2)); function_connectivity_value = (sum(function_connectivity1) + sum(function_connectivity2)) / (numel(function_connectivity1) + numel(function_connectivity2)); if obj.network1 == obj.network2 obj.ROI_values(network2_ROI_index) = (obj.ROI_values(network2_ROI_index) + ROI_value) ./ 2; - obj.function_connectivity_values(network2_ROI_index) = (obj.function_connectivity_values(network2_ROI_index) + function_connectivity_values) ./ 2; + obj.function_connectivity_values(network2_ROI_index) = (obj.function_connectivity_values(network2_ROI_index) + function_connectivity_value) ./ 2; else obj.ROI_values(network2_ROI_index) = ROI_value; obj.function_connectivity_values(network2_ROI_index) = function_connectivity_value; @@ -146,7 +147,6 @@ function setROIandConnectivity(obj) function [coefficient1, coefficient2, function_connectivity1, function_connectivity2] =... getCoefficients(obj, network1_index, network2_indexes) - coefficient1 = obj.edge_test_result.coeff.get(network1_index, network2_indexes); coefficient2 = obj.edge_test_result.coeff.get(network2_indexes, network1_index); @@ -161,29 +161,29 @@ function setROIandConnectivity(obj) probability_significance1 = obj.edge_test_result.prob_sig.get(network1_index, network2_indexes); probability_significance2 = obj.edge_test_result.prob_sig.get(network2_indexes, network1_index); - coefficient1 = coefficient1(logical()); - coefficient2 = coefficient2(logical()); + coefficient1 = coefficient1(logical(probability_significance1)); + coefficient2 = coefficient2(logical(probability_significance2)); function_connectivity1 = function_connectivity1(logical(probability_significance1)); function_connectivity2 = function_connectivity2(logical(probability_significance2)); end end - function colors = mapColorsToLimits(obj) + function colors = mapColorsToLimits(obj, value, function_connectivity_average) import nla.gfx.valToColor - color_rows = size(colors); + color_rows = size(obj.color_map); color_map_positive = obj.color_map(1:(color_rows/2), :); color_map_negative = obj.color_map(((color_rows/2) + 1):end, :); - if functional_connectivity_exists - colors_positive = valToColor(obj.function_connectivity_vals, -0.5, 0.5, color_map_positive); - colors_negative = valToColor(obj.function_connectivity_vals, -0.5, 0.5, color_map_negative); - colors(obj.ROI_values > 0, :) = colors_positive(obj.ROI_values > 0, :); - colors(obj.ROI_values <= 0, :) = colors_negative(obj.ROI_values <= 0, :); + if obj.color_functional_connectivity + colors_positive = valToColor(function_connectivity_average, -0.5, 0.5, color_map_positive); + colors_negative = valToColor(function_connectivity_average, -0.5, 0.5, color_map_negative); + colors(value > 0, :) = colors_positive(value > 0, :); + colors(value <= 0, :) = colors_negative(value <= 0, :); else - colors = valToColor(obj.ROI_values, obj.lower_limit, obj.upper_limit, obj.color_map); + colors = valToColor(value, obj.lower_limit, obj.upper_limit, obj.color_map); end - colors(isnan(obj.ROI_values), :) = 0.5; + colors(isnan(value), :) = 0.5; end function edges = drawEdges(obj, ROI_position, plot_axis) @@ -207,21 +207,23 @@ function setROIandConnectivity(obj) [coefficient, ~, function_connectivity_vector, ~] = obj.getCoefficients(network_point1, network_point2); function_connectivity_average = mean(function_connectivity_vector); - if ~isempty(cofficient) - color_value = obj.mapValuesToLimits(); + if ~isempty(coefficient) + color_value = obj.mapColorsToLimits(coefficient, function_connectivity_average); color_value = [reshape(color_value, [1, 3]), 0.5]; edge = plot3(plot_axis, [ROI_position(network_point1, 1), ROI_position(network_point2, 1)],... [ROI_position(network_point1, 2), ROI_position(network_point2, 2)],... [ROI_position(network_point1, 3), ROI_position(network_point2, 3)],... "Color", color_value, "LineWidth", 5); + % colorbar(plot_axis, 'off'); + % hold(plot_axis, 'on'); edge.Annotation.LegendInformation.IconDisplayStyle = "off"; - edges = [edges edge]; + edges = [edges, edge]; end end end end - function drawCortex(obj, anatomy, plot_axis, mesh_type, mesh_alpha, view_position, left_color, right_color) + function drawCortex(obj, anatomy, plot_axis, view_position, left_color, right_color) import nla.gfx.ViewPos % Set some defaults up @@ -234,22 +236,22 @@ function drawCortex(obj, anatomy, plot_axis, mesh_type, mesh_alpha, view_positio end % Re-position hemisphere meshes to transverse/axial orientation - [left_mesh, right_mesh] = nla.gfx.anatToMesh(anatomy, mesh_type, view_position); + [left_mesh, right_mesh] = nla.gfx.anatToMesh(anatomy, obj.mesh_type, view_position); % Set lighting and view positioning - if view_position == ViewPos.LAT || ViewPos.MED + if view_position == ViewPos.LAT || view_position == ViewPos.MED view(plot_axis, [-90, 0]); % local light is akin to a lightbulb at that location in space % infinite light has light that originates at that point and only goes in one direction light(plot_axis, "Position", [-100, 200, 0], "Style", "local"); light(plot_axis, "Position", [-50, -500, 100], "Style", "infinite"); light(plot_axis, "Position", [-50, 0, 0], "Style", "infinite"); - else: + else view(plot_axis, [0, 0]); switch view_position case ViewPos.DORSAL view(plot_axis, [0, 90]); - light(plot_axis, "Position", [100, 300, 100], "Style", "infiinite"); + light(plot_axis, "Position", [100, 300, 100], "Style", "infinite"); case ViewPos.LEFT view(plot_axis, [-90, 0]); light(plot_axis, "Position", [-100, 0, 0], "Style", "infinite"); @@ -261,52 +263,59 @@ function drawCortex(obj, anatomy, plot_axis, mesh_type, mesh_alpha, view_positio light(plot_axis, "Position", [100, 300, 100], "Style", "infinite"); case ViewPos.BACK light(plot_axis, "Position", [0, -200, 0], "Style", "infinite"); + end light(plot_axis, "Position", [-500, -20, 0], "Style", "local"); light(plot_axis, "Position", [500, -20, 0], "Style", "local"); light(plot_axis, "Position", [0, -200, 50], "Style", "local"); + end + left_hemisphere = obj.drawCortexHemisphere(plot_axis, anatomy.hemi_l, left_mesh, left_color); + right_hemisphere = obj.drawCortexHemisphere(plot_axis, anatomy.hemi_r, right_mesh, right_color); - left_hemisphere = obj.drawCortexHemisphere(plot_axis, anatomy.hemi_l, left_mesh, left_color, mesh_alpha); - right_hemisphere = obj.drawCortexHemisphere(plot_axis, anatomy.hemi_r, right_mesh, right_color, mesh_alpha); - + hold(plot_axis, "on"); axis(plot_axis, "image"); axis(plot_axis, "off"); end - function hemisphere = drawCortexHemisphere(obj, plot_axis, hemisphere_anatomy, mesh, color, mesh_alpha) - properties = struct( - "Faces", hemisphere_anatomy.elements(:, 1:3), "Vertices", mesh,... + function hemisphere = drawCortexHemisphere(obj, plot_axis, hemisphere_anatomy, mesh, color) + hemisphere = patch(plot_axis, "Faces", hemisphere_anatomy.elements(:, 1:3), "Vertices", mesh,... "EdgeColor", "none", "FaceColor", "interp", "FaceVertexCData", color,... - "FaceLightin", "gourand", "FaceAlpha", mesh_alpha,... - "AmbientStrength", 0.25, "DiffuseStrengh", 0.75, "SpecularStrength", 0.1... - ); - hemisphere = patch(plot_axis, properties); + "FaceLightin", "gouraud", "FaceAlpha", obj.mesh_alpha, "AmbientStrength", 0.25,... + "DiffuseStrength", 0.75, "SpecularStrength", 0.1); hemisphere.Annotation.LegendInformation.IconDisplayStyle = "off"; end - function edges = singlePlot(obj, plot_axis, view_position, mesh_type, color_mode, color_matrix, upper_limit, lower_limit) + function edges = singlePlot(obj, plot_axis, view_position, color_mode, color_matrix, upper_limit, lower_limit) if exist("upper_limit", "var") - self.upper_limit = upper_limit; + obj.upper_limit = upper_limit; end if exist("lower_limit", "var") - self.lower_limit = lower_limit; + obj.lower_limit = lower_limit; end connectivity_map = ~isnan(obj.ROI_values); - [ROI_final_positions, ROI_colors = obj.getROIPositions(mesh_type, view_position, color_mode, color_matrix); - if ~isequal(color_mode, nla.BrainColorMode.NONE) && obj.surface_parcels && ~islogical(obj.network_atlas.parcels) && isequal(size(obj.network_atlas.parcels.ctx_l,1), size(obj.network_atlas.anat.hemi_l.nodes, 1)) && isequal(size(obj.network_atlas.parcels.ctx_r, 1), size(obj.network_atlas.anat.hemi_r.nodes, 1)) - ROI_color_map = [0.5 0.5 0.5; ROI_colors]; - obj.drawCortex(obj.network_atlas.anat, plot_axis, mesh_type, mesh_alpha, view_position, ROI_color_map(obj.network_atlas.parcels.ctx_l + 1, :), ROI_color_map(obj.network_atlas.parcels.ctx_r + 1, :)); + edges = []; + if color_mode == nla.gfx.BrainColorMode.NONE + [ROI_final_positions, ROI_colors] = obj.getROIPositions(view_position, color_mode, color_matrix); + obj.drawCortex(obj.network_atlas.anat, plot_axis, view_position); + edges = [edges, obj.drawEdges(ROI_final_positions, plot_axis)]; else - obj.drawCortex(obj.network_atlas.anat, plot_axis, mesh_type, mesh_alpha, view_position); - if ~isequal(color_mode, nla.BrainColorMode.NONE) - for roi = 1:obj.network_atlas.numROIs(): - nla.gfx.drawSphere(plot_axis, ROI_final_positions(roi, :), ROI_colors(roi, :), obj.ROI_radius); + obj.mesh_alpha = 1; + [ROI_final_positions, ROI_colors] = obj.getROIPositions(view_position, nla.gfx.BrainColorMode.COLOR_ROIS) + if ~isequal(color_mode, nla.gfx.BrainColorMode.NONE) && obj.surface_parcels && ~islogical(obj.network_atlas.parcels) && isequal(size(obj.network_atlas.parcels.ctx_l,1), size(obj.network_atlas.anat.hemi_l.nodes, 1)) && isequal(size(obj.network_atlas.parcels.ctx_r, 1), size(obj.network_atlas.anat.hemi_r.nodes, 1)) + ROI_color_map = [0.5 0.5 0.5; ROI_colors]; + obj.drawCortex(obj.network_atlas.anat, plot_axis, view_position, ROI_color_map(obj.network_atlas.parcels.ctx_l + 1, :), ROI_color_map(obj.network_atlas.parcels.ctx_r + 1, :)); + else + drawCortex(ax, net_atlas.anat, ctx, mesh_alpha, view_pos); + if color_mode ~= BrainColorMode.NONE + for i = 1:net_atlas.numROIs() + % render a sphere at each ROI location + nla.gfx.drawSphere(ax, ROI_final_pos(i, :), ROI_color(i, :), ROI_radius); + end end end - edges = obj.drawEdges(ROI_final_positions, plot_axis); end if ~isfield(obj.edge_test_options, "show_ROI_centroids") || (isfield(obj.edge_test_options, "show_ROI_centroids") && isequal(obj.edge_test_options.show_ROI_centroids, true)); @@ -314,7 +323,7 @@ function drawCortex(obj, anatomy, plot_axis, mesh_type, mesh_alpha, view_positio end colorbar(plot_axis, "off"); - hold(plot_axis, on); + hold(plot_axis, "on"); end function drawROISpheres(obj, ROI_position, plot_axis, connectivity_map) @@ -331,10 +340,10 @@ function drawROISpheres(obj, ROI_position, plot_axis, connectivity_map) end end - function [ROI_final_positions, ROI_colors] = getROIPositions(obj, mesh_type, view_position, color_mode, color_matrix) + function [ROI_final_positions, ROI_colors] = getROIPositions(obj, view_position, color_mode, color_matrix) import nla.gfx.BrainColorMode - [left_mesh, right_mesh] = nla.gfx.anatToMesh(obj.network_atlas.anat, mesh_type, view_position); + [left_mesh, right_mesh] = nla.gfx.anatToMesh(obj.network_atlas.anat, obj.mesh_type, view_position); ROI_positions = [obj.network_atlas.ROIs.pos]'; [left_indexes, left_distances] = knnsearch(obj.network_atlas.anat.hemi_l.nodes, ROI_positions); @@ -345,9 +354,9 @@ function drawROISpheres(obj, ROI_position, plot_axis, connectivity_map) ROI_index = obj.network_atlas.nets(network).indexes(network_indexes); offset = [NaN NaN NaN]; if left_distances(ROI_index) < right_distances(ROI_index) - offset = left_mesh(left_indexes(ROI_index, :) - obj.network_atlas.anat.hemi_l.nodes(ROI_index), :); - else: - offset = right_mesh(right_indexes(ROI_index, :) - obj.network_atlas.anat.hemi_r.nodes(ROI_index), :); + offset = left_mesh(left_indexes(ROI_index), :) - obj.network_atlas.anat.hemi_l.nodes(left_indexes(ROI_index), :); + else + offset = right_mesh(right_indexes(ROI_index), :) - obj.network_atlas.anat.hemi_r.nodes(right_indexes(ROI_index), :); end ROI_final_positions(ROI_index, :) = ROI_positions(ROI_index, :) + offset; @@ -358,13 +367,15 @@ function drawROISpheres(obj, ROI_position, plot_axis, connectivity_map) ROI_colors(ROI_index, :) = color_matrix(network, :); case BrainColorMode.COLOR_ROIS ROI_colors(ROI_index, :) = color_matrix(ROI_index, :); + otherwise + ROI_colors = false; end end end end function drawColorMap(obj, plot_axis) - if obj.functional_connectivity_exists + if obj.color_functional_connectivity colormap(plot_axis, obj.color_map); color_bar = colorbar(plot_axis); color_bar.Location = "southoutside"; @@ -386,10 +397,28 @@ function drawColorMap(obj, plot_axis) end end - %% GETTERS for dependent properties + function addTitle(obj) + figure_title = sprintf("Brain Visualization: Average of edge-level correlations between nets in [%s - %s] Network Pair", obj.network_atlas.nets(obj.network1).name, obj.network_atlas.nets(obj.network2).name); + if obj.is_noncorrelation_input + figure_title = [figure_title sprintf(" (Edge-level P < %.2g)", obj.edge_test_options.prob_max)]; + end + obj.figure_plot.Name = figure_title; + end + + %% + % GETTERS for dependent properties function value = get.is_noncorrelation_input(obj) % Convenience method to determine if inputs were correlation coefficients, or "significance" values value = any(strcmp(obj.noncorrelation_input_tests, obj.test_name)); end + + function value = get.functional_connectivity_exists(obj) + value = isfield(obj.edge_test_options, "func_conn"); + end + + function value = get.color_functional_connectivity(obj) + value = false; + end + %% end end \ No newline at end of file diff --git a/+nla/+gfx/drawCortexHemi.m b/+nla/+gfx/drawCortexHemi.m index b6fec47c..eebba827 100755 --- a/+nla/+gfx/drawCortexHemi.m +++ b/+nla/+gfx/drawCortexHemi.m @@ -6,7 +6,7 @@ % color: 3x1 vector, cortex mesh color % mesh_alpha: transparency of cortex mesh - obj = patch(ax, 'Faces',anat_hemi.elements(:,1:3),'Vertices', mesh,... + obj = patch(ax, 'Faces', anat_hemi.elements(:,1:3),'Vertices', mesh,... 'EdgeColor','none','FaceColor','interp','FaceVertexCData', color,... 'FaceLighting','gouraud','FaceAlpha',mesh_alpha,... 'AmbientStrength',0.25,'DiffuseStrength',0.75,'SpecularStrength',0.1); diff --git a/+nla/+net/+result/NetworkResultPlotParameter.m b/+nla/+net/+result/NetworkResultPlotParameter.m index a16d1f8d..995522d3 100644 --- a/+nla/+net/+result/NetworkResultPlotParameter.m +++ b/+nla/+net/+result/NetworkResultPlotParameter.m @@ -100,9 +100,11 @@ function brainFigureButtonCallback(network1, network2) wait_text = sprintf("Generating %s - %s network-pair brain plot", obj.network_atlas.nets(network1).name,... obj.network_atlas.nets(network2).name); wait_popup = waitbar(0.05, wait_text); - nla.gfx.drawBrainVis(edge_test_options, obj.updated_test_options, obj.network_atlas,... - nla.gfx.MeshType.STD, 0.25, 3, true, edge_test_result, network1, network2,... - any(strcmp(obj.noncorrelation_input_tests, obj.network_test_results.test_name))); + % nla.gfx.drawBrainVis(edge_test_options, obj.updated_test_options, obj.network_atlas,... + % nla.gfx.MeshType.STD, 0.25, 3, true, edge_test_result, network1, network2,... + % any(strcmp(obj.noncorrelation_input_tests, obj.network_test_results.test_name))); + brain_plot = nla.gfx.brain.BrainPlot(edge_test_result, edge_test_options, obj.updated_test_options, network1, network2, obj.network_atlas); + brain_plot.drawBrainPlots() waitbar(0.95); close(wait_popup); end From 4f559115cb0199d6ac9cbeeabef02c7cd8e09f55 Mon Sep 17 00:00:00 2001 From: Jim Pollaro Date: Fri, 26 Jul 2024 09:47:45 -0500 Subject: [PATCH 04/14] last function change before class --- +nla/+gfx/drawBrainVis.m | 47 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/+nla/+gfx/drawBrainVis.m b/+nla/+gfx/drawBrainVis.m index b9e845bd..aa6a7311 100755 --- a/+nla/+gfx/drawBrainVis.m +++ b/+nla/+gfx/drawBrainVis.m @@ -157,7 +157,20 @@ function drawEdges(ROI_pos, ax, net_atlas, net1, net2, color_map, color_map_p, c end end - function onePlot(ax, pos, color_mode, color_mat) + function onePlot(varargin) + if nargin >= 3 + ax = varargin{1}; + pos = varargin{2}; + color_mode = varargin{3}; + end + if nargin >= 4 + color_mat = varargin{4}; + end + if nargin > 4 + ulimit = str2double(varargin{5}); + llimit = str2double(varargin{6}); + end + if color_mode == nla.gfx.BrainColorMode.NONE ROI_final_pos = nla.gfx.drawROIsOnCortex(ax, net_atlas, ctx, mesh_alpha, ROI_radius, pos, surface_parcels,... nla.gfx.BrainColorMode.NONE); @@ -213,6 +226,37 @@ function onePlot(ax, pos, color_mode, color_mat) hold(ax, 'off'); nla.gfx.hideAxes(ax); + function openModal(source, ~) + d = figure("WindowStyle", "normal", "Units", "pixels", "Position", [source.Parent.Position(1), source.Parent.Position(2), 250, 150]); + upper_limit_box = uicontrol("Style", "edit", "Units", "pixels", "Position", [d.Position(3) / 2, d.Position(4) / 2 + 5, 50, 25], "String", ulimit); + lower_limit_box = uicontrol("Style", "edit", "Units", "pixels", "Position", [d.Position(3) / 2, d.Position(4) / 2 - upper_limit_box.Position(4) - 5, 50, 25], "String", llimit); + uicontrol("Style", "text", "String", "Upper Limit", "Units", "pixels", "Position", [upper_limit_box.Position(1) - 80, upper_limit_box.Position(2) - 5, 80, upper_limit_box.Position(4)]); + uicontrol("Style", "text", "String", "Lower Limit", "Units", "pixels", "Position", [lower_limit_box.Position(1) - 80, lower_limit_box.Position(2) - 5, 80, lower_limit_box.Position(4)]); + + apply_button_position = [d.Position(3) / 2 - 85, 10, 80, 25]; + close_button_position = [apply_button_position(1) + 85, apply_button_position(2), apply_button_position(3), apply_button_position(4)]; + uicontrol("String", "Apply", "Units", "pixels", "Position", apply_button_position, "Callback", {@applyScale, upper_limit_box, lower_limit_box}); + uicontrol("String", "Close", "Units", "pixels", "Position", close_button_position, "Callback", @(~, ~)close(d)); + end + + function applyScale(~, ~, upper_limit_box, lower_limit_box) + upper_limit = get(upper_limit_box, "String"); + lower_limit = get(lower_limit_box, "String"); + figure(fig) + if surface_parcels && ~islogical(net_atlas.parcels) + onePlot(subplot('Position',[.45,0.505,.53,.45]), nla.gfx.ViewPos.LAT, nla.gfx.BrainColorMode.COLOR_ROIS, color_mat, upper_limit, lower_limit); + onePlot(subplot('Position',[.45,0.055,.53,.45]), nla.gfx.ViewPos.MED, nla.gfx.BrainColorMode.COLOR_ROIS, color_mat, upper_limit, lower_limit); + else + onePlot(subplot('Position',[.45,0.505,.26,.45]), nla.gfx.ViewPos.BACK, nla.gfx.BrainColorMode.NONE, false, upper_limit, lower_limit); + onePlot(subplot('Position',[.73,0.505,.26,.45]), nla.gfx.ViewPos.FRONT, nla.gfx.BrainColorMode.NONE, false, upper_limit, lower_limit); + onePlot(subplot('Position',[.45,0.055,.26,.45]), nla.gfx.ViewPos.LEFT, nla.gfx.BrainColorMode.NONE, false, upper_limit, lower_limit); + onePlot(subplot('Position',[.73,0.055,.26,.45]), nla.gfx.ViewPos.RIGHT, nla.gfx.BrainColorMode.NONE, false, upper_limit, lower_limit); + end + % ROI_final_pos = nla.gfx.drawROIsOnCortex(ax, net_atlas, ctx, mesh_alpha, ROI_radius, pos, surface_parcels,... + % nla.gfx.BrainColorMode.NONE); + % drawEdges(ROI_final_pos, ax, net_atlas, net1, net2, color_map, color_map_p, color_map_n, color_fx, fc_exists, lower_limit, upper_limit); + end + %% Display colormap if color_fc % legend(ax, 'Location', 'best'); @@ -232,6 +276,7 @@ function onePlot(ax, pos, color_mode, color_mat) cb = colorbar(ax); cb.Location = 'southoutside'; cb.Label.String = 'Coefficient Magnitude'; + cb.ButtonDownFcn = @openModal; ticks = [0:num_ticks]; cb.Ticks = double(ticks) ./ num_ticks; From fd14f7a56205602c6bd7c17a9d459ef8c3a7fcef Mon Sep 17 00:00:00 2001 From: Jim Pollaro Date: Wed, 31 Jul 2024 10:36:53 -0500 Subject: [PATCH 05/14] first shot at new brain plots --- +nla/+gfx/+brain/BrainPlot.m | 395 +++++++++++++++++++++++++++++++++++ 1 file changed, 395 insertions(+) create mode 100644 +nla/+gfx/+brain/BrainPlot.m diff --git a/+nla/+gfx/+brain/BrainPlot.m b/+nla/+gfx/+brain/BrainPlot.m new file mode 100644 index 00000000..c7ed07e5 --- /dev/null +++ b/+nla/+gfx/+brain/BrainPlot.m @@ -0,0 +1,395 @@ +classdef BrainPlot < handle + + properties + plot_figure + edge_test_options + network_test_options + network_atlas + edge_test_result + network1 + network2 + test_name + upper_limit + lower_limit + color_map = cat(1, winter(1000), flip(autumn(1000))); + ROI_values + function_connectivity_values + ROI_radius + surface_parcels + end + + properties (Constant) + noncorrelation_input_tests = ["chi_squared", "hypergeometric"] % These are tests that do not use correlation coefficients as inputs + end + + properties (Dependent) + is_noncorrelation_input + functional_connectivity_exists + end + + methods + + function obj = BrainPlot(edge_test_result, edge_test_options, network_test_options, network1, network2, network_atlas) + brain_input_parser = inputParser; + addRequired(brain_input_parser, "edge_test_result"); + addRequired(brain_input_parser, "edge_test_options"); + addRequired(brain_input_parser, "network_test_options"); + addRequired(brain_input_parser, "network1"); + addRequired(brain_input_parser, "network2"); + addRequired(brain_input_parser, "network_atlas"); + + validNumberInput = @(x) isnumeric(x) && isscalar(x) + addParameter(brain_input_parser, "ROI_radius", 0, validNumberInput); + addParameter(brain_input_parser, "surface_parcels", false, @isboolean); + + parse(brain_input_parser, edge_test_result, edge_test_options, network_test_options, network1, network2, network_atlas, varargin{:}); + properties = {"edge_test_result", "edge_test_options", "network_test_options", "network1", "network2", "network_atlas", "ROI_radius", "surface_parcels"}; + for property = properties + obj.(property{1}) = brain_input_parser.Results.(property{1}); + end + + %% + % everything below here we'll need, but we need other values set first. and they need to be editable + obj.plot_figure = nla.gfx.createFigure(1550, 750); + figure_title = sprintf("Brain Visualization: Average of edge-level correlations between nets in [%s - %s] Network Pair",... + obj.network_atlas.nets(obj.network1).name, obj.network_atlas.nets(obj.network2).name); + if obj.is_noncorrelation_input + figure_title = [figure_title sprintf(" (Edge-level P < %.2g)", obj.edge_test_options.prob_max)]; + end + obj.plot_figure.Name = figure_title; + + obj.upper_limit = obj.edge_test_result.coeff_range(1); + obj.lower_limit = obj.edge_test_result.coeff_range(2); + + obj.ROI_values = nan(obj.network_atlas.numROIs(), 1); + obj.function_connectivity_values = nan(obj.network_atlas.numROIs(), 1) + %% + end + + function drawBrainPlots(obj) + import nla.gfx.ViewPos nla.gfx.BrainColorMode + + obj.setROIandConnectivity(); + + all_edges = []; + if obj.surface_parcels && ~islogical(obj.network_atlas.parcels) + edges1 = obj.single_plot(subplot("Position", [0.45, 0.505, 0.53, 0.45], ViewPos.LAT, BrainColorMode.COLOR_ROIS, obj.color_map)); + edges2 = obj.single_plot(subplot("Position", [0.45, 0.055, 0.53, 0.45], ViewPos.MED, BrainColorMode.COLOR_ROIS, obj.color_map)); + all_edges = [edges1 edges2]; + else + edges1 = obj.single_plot(subplot("Position", [0.45, 0.505, 0.26, 0.45], ViewPos.BACK, BrainColorMode.NONE)); + edges2 = obj.single_plot(subplot("Position", [0.73, 0.505, 0.26, 0.45], ViewPos.FRONT, BrainColorMode.NONE]); + edges3 = obj.single_plot(subplot("Position", [0.45, 0.055, 0.26, 0.45], ViewPos.LEFT, BrainColorMode.NONE)); + edges4 = obj.single_plot(subplot("Position", [0.73, 0.055, 0.26, 0.45], ViewPos.RIGHT, BrainColorMode.None)); + all_edges = [edges1 edges2 edges3 edges4]; + end + + if obj.functional_connectivity_exists + plot_axis = subplot("Position", [0.075, 0.175, 0.35, 0.75]); + else + plot_axis = subplot("Position", [0.075, 0.025, 0.35, 0.85]); + end + + if obj.surface_parcels && ~islogical(obj.network_atlas.parcels) + obj.single_plot(plot_axis, ViewPos.DORSAL, BrainColorMode.COLOR_ROIS, obj.colo_maps); + else + obj.single_plot(plot_axis, ViewPos.DORSAL, BrainColorMode.NONE); + end + + light("Position", [0, 100, 100], "Style", "local"); + + % Display Legend + hold(plot_axis, "on"); + if obj.network1 == obj.network2 + legend_entry = bar(plot_axis, NaN); + legend_entry.FaceColor = obj.network_atlas.nets(obj.network1).color; + legend_entry.DisplayName = obj.network_atlas.nets(obj.network1).name; + else + for network = [obj.network1, obj.network2] + legend_entry = bar(plot_axs, NaN); + legend_entry.FaceColor = obj.network_atlas.nets(network).color; + legend_entry.DisplayName = obj.network_atlas.nets(network).name; + end + end + hold(plot_axis, "off"); + nla.gfx.hideAxes(plot_axis); + + obj.drawColorMap(plot_axis); + end + + function setROIandConnectivity(obj) + + network1_ROI_indexes = obj.network_atlas.nets(obj.network1).indexes; + network2_ROI_indexes = obj.network_atlas.nets(obj.network2).indexes; + + for ROI_index1 = 1:numel(network1_ROI_indexes) + network1_ROI_index = network1_ROI_indexes(ROI_index1); + [coefficient1, coefficient2, function_connectivity1, function_connectivity2] = obj.getCoefficients(network1_ROI_index, network2_ROI_indexes); + obj.ROI_values(ROI_index1) = (sum(coefficient1) + sum(coefficient2)) / (numel(coffecient1) + numel(coefficient2)); + obj.function_connectivity_values(ROI_index1) = (sum(function_connectivity1) + sum(function_connectivity2)) / (numel(function_connectivity1) + numel(function_connectivity2)); + end + + for ROI_index2 = 1:numel(network2_ROI_indexes) + network2_ROI_index = network2_ROI_indexes(ROI_index2); + [coefficient1, coefficient2, function_connectivity1, function_connectivity2] = obj.getCoefficients(network2_ROI_index, network1_ROI_indexes); + ROI_value = (sum(coefficient1) + sum(coefficient2)) / (numel(coffecient1) + numel(coefficient2)); + function_connectivity_value = (sum(function_connectivity1) + sum(function_connectivity2)) / (numel(function_connectivity1) + numel(function_connectivity2)); + if obj.network1 == obj.network2 + obj.ROI_values(network2_ROI_index) = (obj.ROI_values(network2_ROI_index) + ROI_value) ./ 2; + obj.function_connectivity_values(network2_ROI_index) = (obj.function_connectivity_values(network2_ROI_index) + function_connectivity_values) ./ 2; + else + obj.ROI_values(network2_ROI_index) = ROI_value; + obj.function_connectivity_values(network2_ROI_index) = function_connectivity_value; + end + end + end + + function [coefficient1, coefficient2, function_connectivity1, function_connectivity2] =... + getCoefficients(obj, network1_index, network2_indexes) + + coefficient1 = obj.edge_test_result.coeff.get(network1_index, network2_indexes); + coefficient2 = obj.edge_test_result.coeff.get(network2_indexes, network1_index); + + function_connectivity1 = false; + function_connectivity2 = false; + if obj.functional_connectivity_exists + function_connectivity1 = mean(obj.edge_test_options.func_conn.get(network1_index, network2_indexes), 2); + function_connectivity2 = mean(obj.edge_test_options.func_conn.get(network2_indexes, network1_index), 2); + end + + if obj.is_noncorrelation_input + probability_significance1 = obj.edge_test_result.prob_sig.get(network1_index, network2_indexes); + probability_significance2 = obj.edge_test_result.prob_sig.get(network2_indexes, network1_index); + + coefficient1 = coefficient1(logical()); + coefficient2 = coefficient2(logical()); + function_connectivity1 = function_connectivity1(logical(probability_significance1)); + function_connectivity2 = function_connectivity2(logical(probability_significance2)); + end + end + + function colors = mapColorsToLimits(obj) + import nla.gfx.valToColor + + color_rows = size(colors); + color_map_positive = obj.color_map(1:(color_rows/2), :); + color_map_negative = obj.color_map(((color_rows/2) + 1):end, :); + + if functional_connectivity_exists + colors_positive = valToColor(obj.function_connectivity_vals, -0.5, 0.5, color_map_positive); + colors_negative = valToColor(obj.function_connectivity_vals, -0.5, 0.5, color_map_negative); + colors(obj.ROI_values > 0, :) = colors_positive(obj.ROI_values > 0, :); + colors(obj.ROI_values <= 0, :) = colors_negative(obj.ROI_values <= 0, :); + else + colors = valToColor(obj.ROI_values, obj.lower_limit, obj.upper_limit, obj.color_map); + end + colors(isnan(obj.ROI_values), :) = 0.5; + end + + function edges = drawEdges(obj, ROI_position, plot_axis) + + point1_indexes = obj.network_atlas.nets(obj.network1).indexes; + point2_indexes = obj.network_atlas.nets(obj.network2).indexes; + + edges = []; + for point1_index = 1:numel(point1_indexes) + point1 = point1_indexes(point1_index); + for point2_index = 1:numel(point2_indexes) + point2 = point2_indexes(point2_index); + if point1 < point2 + network_point1 = point2; + network_point2 = point1; + else + network_point1 = point1; + network_point2 = point2; + end + + [coefficient, ~, function_connectivity_vector, ~] = obj.getCoefficients(network_point1, network_point2); + function_connectivity_average = mean(function_connectivity_vector); + + if ~isempty(cofficient) + color_value = obj.mapValuesToLimits(); + color_value = [reshape(color_value, [1, 3]), 0.5]; + edge = plot3(plot_axis, [ROI_position(network_point1, 1), ROI_position(network_point2, 1)],... + [ROI_position(network_point1, 2), ROI_position(network_point2, 2)],... + [ROI_position(network_point1, 3), ROI_position(network_point2, 3)],... + "Color", color_value, "LineWidth", 5); + edge.Annotation.LegendInformation.IconDisplayStyle = "off"; + edges = [edges edge]; + end + end + end + end + + function drawCortex(obj, anatomy, plot_axis, mesh_type, mesh_alpha, view_position, left_color, right_color) + import nla.gfx.ViewPos + + % Set some defaults up + plot_axis.Color = 'w'; + if ~exist("left_color", "var") + left_color = repmat(0.5, [size(anatomy.hemi_l.nodes, 1), 3]); + end + if ~exist("right_color", "var") + right_color = repmat(0.5, [size(anatomy.hemi_r.nodes, 1), 3]); + end + + % Re-position hemisphere meshes to transverse/axial orientation + [left_mesh, right_mesh] = nla.gfx.anatToMesh(anatomy, mesh_type, view_position); + + % Set lighting and view positioning + if view_position == ViewPos.LAT || ViewPos.MED + view(plot_axis, [-90, 0]); + % local light is akin to a lightbulb at that location in space + % infinite light has light that originates at that point and only goes in one direction + light(plot_axis, "Position", [-100, 200, 0], "Style", "local"); + light(plot_axis, "Position", [-50, -500, 100], "Style", "infinite"); + light(plot_axis, "Position", [-50, 0, 0], "Style", "infinite"); + else: + view(plot_axis, [0, 0]); + switch view_position + case ViewPos.DORSAL + view(plot_axis, [0, 90]); + light(plot_axis, "Position", [100, 300, 100], "Style", "infiinite"); + case ViewPos.LEFT + view(plot_axis, [-90, 0]); + light(plot_axis, "Position", [-100, 0, 0], "Style", "infinite"); + case ViewPos.RIGHT + view(plot_axis, [90, 0]); + light(plot_axis, "Position", [100, 0, 0], "Style", "infinite"); + case ViewPos.FRONT + view(plot_axis, [180, 0]); + light(plot_axis, "Position", [100, 300, 100], "Style", "infinite"); + case ViewPos.BACK + light(plot_axis, "Position", [0, -200, 0], "Style", "infinite"); + + light(plot_axis, "Position", [-500, -20, 0], "Style", "local"); + light(plot_axis, "Position", [500, -20, 0], "Style", "local"); + light(plot_axis, "Position", [0, -200, 50], "Style", "local"); + + left_hemisphere = obj.drawCortexHemisphere(plot_axis, anatomy.hemi_l, left_mesh, left_color, mesh_alpha); + right_hemisphere = obj.drawCortexHemisphere(plot_axis, anatomy.hemi_r, right_mesh, right_color, mesh_alpha); + + axis(plot_axis, "image"); + axis(plot_axis, "off"); + end + + function hemisphere = drawCortexHemisphere(obj, plot_axis, hemisphere_anatomy, mesh, color, mesh_alpha) + properties = struct( + "Faces", hemisphere_anatomy.elements(:, 1:3), "Vertices", mesh,... + "EdgeColor", "none", "FaceColor", "interp", "FaceVertexCData", color,... + "FaceLightin", "gourand", "FaceAlpha", mesh_alpha,... + "AmbientStrength", 0.25, "DiffuseStrengh", 0.75, "SpecularStrength", 0.1... + ); + hemisphere = patch(plot_axis, properties); + hemisphere.Annotation.LegendInformation.IconDisplayStyle = "off"; + end + + function edges = singlePlot(obj, plot_axis, view_position, mesh_type, color_mode, color_matrix, upper_limit, lower_limit) + + if exist("upper_limit", "var") + self.upper_limit = upper_limit; + end + if exist("lower_limit", "var") + self.lower_limit = lower_limit; + end + + connectivity_map = ~isnan(obj.ROI_values); + + [ROI_final_positions, ROI_colors = obj.getROIPositions(mesh_type, view_position, color_mode, color_matrix); + if ~isequal(color_mode, nla.BrainColorMode.NONE) && obj.surface_parcels && ~islogical(obj.network_atlas.parcels) && isequal(size(obj.network_atlas.parcels.ctx_l,1), size(obj.network_atlas.anat.hemi_l.nodes, 1)) && isequal(size(obj.network_atlas.parcels.ctx_r, 1), size(obj.network_atlas.anat.hemi_r.nodes, 1)) + ROI_color_map = [0.5 0.5 0.5; ROI_colors]; + obj.drawCortex(obj.network_atlas.anat, plot_axis, mesh_type, mesh_alpha, view_position, ROI_color_map(obj.network_atlas.parcels.ctx_l + 1, :), ROI_color_map(obj.network_atlas.parcels.ctx_r + 1, :)); + else + obj.drawCortex(obj.network_atlas.anat, plot_axis, mesh_type, mesh_alpha, view_position); + if ~isequal(color_mode, nla.BrainColorMode.NONE) + for roi = 1:obj.network_atlas.numROIs(): + nla.gfx.drawSphere(plot_axis, ROI_final_positions(roi, :), ROI_colors(roi, :), obj.ROI_radius); + end + end + edges = obj.drawEdges(ROI_final_positions, plot_axis); + end + + if ~isfield(obj.edge_test_options, "show_ROI_centroids") || (isfield(obj.edge_test_options, "show_ROI_centroids") && isequal(obj.edge_test_options.show_ROI_centroids, true)); + obj.drawROISpheres(ROI_final_positions, plot_axis, connectivity_map); + end + + colorbar(plot_axis, "off"); + hold(plot_axis, on); + end + + function drawROISpheres(obj, ROI_position, plot_axis, connectivity_map) + + for network = [obj.network1, obj.network2] + network_indexes = obj.network_atlas.nets(network).indexes; + for index_iterator = 1:numel(network_indexes) + index = network_indexes(index_iterator); + + if connectivity_map(index) + nla.gfx.drawSphere(plot_axis, ROI_position(index, :), obj.network_atlas.nets(network).color, obj.ROI_radius); + end + end + end + end + + function [ROI_final_positions, ROI_colors] = getROIPositions(obj, mesh_type, view_position, color_mode, color_matrix) + import nla.gfx.BrainColorMode + + [left_mesh, right_mesh] = nla.gfx.anatToMesh(obj.network_atlas.anat, mesh_type, view_position); + ROI_positions = [obj.network_atlas.ROIs.pos]'; + + [left_indexes, left_distances] = knnsearch(obj.network_atlas.anat.hemi_l.nodes, ROI_positions); + [right_indexes, right_distances] = knnsearch(obj.network_atlas.anat.hemi_r.nodes, ROI_positions); + + for network = 1:obj.network_atlas.numNets() + for network_indexes = 1:numel(obj.network_atlas.nets(network).indexes) + ROI_index = obj.network_atlas.nets(network).indexes(network_indexes); + offset = [NaN NaN NaN]; + if left_distances(ROI_index) < right_distances(ROI_index) + offset = left_mesh(left_indexes(ROI_index, :) - obj.network_atlas.anat.hemi_l.nodes(ROI_index), :); + else: + offset = right_mesh(right_indexes(ROI_index, :) - obj.network_atlas.anat.hemi_r.nodes(ROI_index), :); + end + ROI_final_positions(ROI_index, :) = ROI_positions(ROI_index, :) + offset; + + switch color_mode + case BrainColorMode.DEFAULT_NETS + ROI_colors(ROI_index, :) = obj.network_atlas.nets(network).color; + case BrainColorMode.COLOR_NETS + ROI_colors(ROI_index, :) = color_matrix(network, :); + case BrainColorMode.COLOR_ROIS + ROI_colors(ROI_index, :) = color_matrix(ROI_index, :); + end + end + end + end + + function drawColorMap(obj, plot_axis) + if obj.functional_connectivity_exists + colormap(plot_axis, obj.color_map); + color_bar = colorbar(plot_axis); + color_bar.Location = "southoutside"; + else + colormap(plot_axis, obj.color_map); + color_bar = colorbar(plot_axis); + color_bar.Location = "southoutside"; + color_bar.ButtonDownFcn = @openModal; + + number_of_ticks = 10; + ticks = [0:number_of_ticks]; + color_bar.Ticks = double(ticks) ./ number_of_ticks; + tick_labels = {}; + for tick = ticks + tick_labels{tick + 1} = sprintf("%.2g", obj.lower_limit + (tick * ((double(obj.upper_limit - obj.lower_limit) / number_of_ticks)))); + end + color_bar.TickLabels = tick_labels; + caxis(plot_axis, [0, 1]); + end + end + + %% GETTERS for dependent properties + function value = get.is_noncorrelation_input(obj) + % Convenience method to determine if inputs were correlation coefficients, or "significance" values + value = any(strcmp(obj.noncorrelation_input_tests, obj.test_name)); + end + end +end \ No newline at end of file From 702454806b97a1587701bca1e09670cef62465ad Mon Sep 17 00:00:00 2001 From: Jim Pollaro Date: Fri, 2 Aug 2024 13:57:00 -0500 Subject: [PATCH 06/14] Finally got brain plots to match original --- +nla/+gfx/+brain/BrainPlot.m | 197 ++++++++++-------- +nla/+gfx/drawCortexHemi.m | 2 +- .../+net/+result/NetworkResultPlotParameter.m | 8 +- 3 files changed, 119 insertions(+), 88 deletions(-) diff --git a/+nla/+gfx/+brain/BrainPlot.m b/+nla/+gfx/+brain/BrainPlot.m index c7ed07e5..65dfbb3b 100644 --- a/+nla/+gfx/+brain/BrainPlot.m +++ b/+nla/+gfx/+brain/BrainPlot.m @@ -12,10 +12,12 @@ upper_limit lower_limit color_map = cat(1, winter(1000), flip(autumn(1000))); - ROI_values - function_connectivity_values + ROI_values = [] + function_connectivity_values = [] ROI_radius surface_parcels + mesh_type + mesh_alpha end properties (Constant) @@ -25,11 +27,12 @@ properties (Dependent) is_noncorrelation_input functional_connectivity_exists + color_functional_connectivity end methods - function obj = BrainPlot(edge_test_result, edge_test_options, network_test_options, network1, network2, network_atlas) + function obj = BrainPlot(edge_test_result, edge_test_options, network_test_options, network1, network2, network_atlas, varargin) brain_input_parser = inputParser; addRequired(brain_input_parser, "edge_test_result"); addRequired(brain_input_parser, "edge_test_options"); @@ -38,12 +41,14 @@ addRequired(brain_input_parser, "network2"); addRequired(brain_input_parser, "network_atlas"); - validNumberInput = @(x) isnumeric(x) && isscalar(x) - addParameter(brain_input_parser, "ROI_radius", 0, validNumberInput); - addParameter(brain_input_parser, "surface_parcels", false, @isboolean); + validNumberInput = @(x) isnumeric(x) && isscalar(x); + addParameter(brain_input_parser, "ROI_radius", 3, validNumberInput); + addParameter(brain_input_parser, "surface_parcels", true, @isboolean); + addParameter(brain_input_parser, "mesh_type", nla.gfx.MeshType.STD, @isenum); + addParameter(brain_input_parser, "mesh_alpha", 0.25, validNumberInput); parse(brain_input_parser, edge_test_result, edge_test_options, network_test_options, network1, network2, network_atlas, varargin{:}); - properties = {"edge_test_result", "edge_test_options", "network_test_options", "network1", "network2", "network_atlas", "ROI_radius", "surface_parcels"}; + properties = {"edge_test_result", "edge_test_options", "network_test_options", "network1", "network2", "network_atlas", "ROI_radius", "surface_parcels", "mesh_type", "mesh_alpha"}; for property = properties obj.(property{1}) = brain_input_parser.Results.(property{1}); end @@ -51,18 +56,12 @@ %% % everything below here we'll need, but we need other values set first. and they need to be editable obj.plot_figure = nla.gfx.createFigure(1550, 750); - figure_title = sprintf("Brain Visualization: Average of edge-level correlations between nets in [%s - %s] Network Pair",... - obj.network_atlas.nets(obj.network1).name, obj.network_atlas.nets(obj.network2).name); - if obj.is_noncorrelation_input - figure_title = [figure_title sprintf(" (Edge-level P < %.2g)", obj.edge_test_options.prob_max)]; - end - obj.plot_figure.Name = figure_title; - obj.upper_limit = obj.edge_test_result.coeff_range(1); - obj.lower_limit = obj.edge_test_result.coeff_range(2); + obj.upper_limit = obj.edge_test_result.coeff_range(2); + obj.lower_limit = obj.edge_test_result.coeff_range(1); obj.ROI_values = nan(obj.network_atlas.numROIs(), 1); - obj.function_connectivity_values = nan(obj.network_atlas.numROIs(), 1) + obj.function_connectivity_values = nan(obj.network_atlas.numROIs(), 1); %% end @@ -73,27 +72,27 @@ function drawBrainPlots(obj) all_edges = []; if obj.surface_parcels && ~islogical(obj.network_atlas.parcels) - edges1 = obj.single_plot(subplot("Position", [0.45, 0.505, 0.53, 0.45], ViewPos.LAT, BrainColorMode.COLOR_ROIS, obj.color_map)); - edges2 = obj.single_plot(subplot("Position", [0.45, 0.055, 0.53, 0.45], ViewPos.MED, BrainColorMode.COLOR_ROIS, obj.color_map)); + edges1 = obj.singlePlot(subplot("Position", [0.45, 0.505, 0.53, 0.45]), ViewPos.LAT, BrainColorMode.COLOR_ROIS, obj.color_map); + edges2 = obj.singlePlot(subplot("Position", [0.45, 0.055, 0.53, 0.45]), ViewPos.MED, BrainColorMode.COLOR_ROIS, obj.color_map); all_edges = [edges1 edges2]; else - edges1 = obj.single_plot(subplot("Position", [0.45, 0.505, 0.26, 0.45], ViewPos.BACK, BrainColorMode.NONE)); - edges2 = obj.single_plot(subplot("Position", [0.73, 0.505, 0.26, 0.45], ViewPos.FRONT, BrainColorMode.NONE]); - edges3 = obj.single_plot(subplot("Position", [0.45, 0.055, 0.26, 0.45], ViewPos.LEFT, BrainColorMode.NONE)); - edges4 = obj.single_plot(subplot("Position", [0.73, 0.055, 0.26, 0.45], ViewPos.RIGHT, BrainColorMode.None)); + edges1 = obj.singlePlot(subplot("Position", [0.45, 0.505, 0.26, 0.45]), ViewPos.BACK, BrainColorMode.NONE, obj.color_map); + edges2 = obj.singlePlot(subplot("Position", [0.73, 0.505, 0.26, 0.45]), ViewPos.FRONT, BrainColorMode.NONE, obj.color_map); + edges3 = obj.singlePlot(subplot("Position", [0.45, 0.055, 0.26, 0.45]), ViewPos.LEFT, BrainColorMode.NONE, obj.color_map); + edges4 = obj.singlePlot(subplot("Position", [0.73, 0.055, 0.26, 0.45]), ViewPos.RIGHT, BrainColorMode.NONE, obj.color_map); all_edges = [edges1 edges2 edges3 edges4]; end - if obj.functional_connectivity_exists + if obj.color_functional_connectivity plot_axis = subplot("Position", [0.075, 0.175, 0.35, 0.75]); else plot_axis = subplot("Position", [0.075, 0.025, 0.35, 0.85]); end if obj.surface_parcels && ~islogical(obj.network_atlas.parcels) - obj.single_plot(plot_axis, ViewPos.DORSAL, BrainColorMode.COLOR_ROIS, obj.colo_maps); + obj.singlePlot(plot_axis, ViewPos.DORSAL, BrainColorMode.COLOR_ROIS, obj.color_map); else - obj.single_plot(plot_axis, ViewPos.DORSAL, BrainColorMode.NONE); + obj.singlePlot(plot_axis, ViewPos.DORSAL, BrainColorMode.NONE, obj.color_map); end light("Position", [0, 100, 100], "Style", "local"); @@ -106,7 +105,7 @@ function drawBrainPlots(obj) legend_entry.DisplayName = obj.network_atlas.nets(obj.network1).name; else for network = [obj.network1, obj.network2] - legend_entry = bar(plot_axs, NaN); + legend_entry = bar(plot_axis, NaN); legend_entry.FaceColor = obj.network_atlas.nets(network).color; legend_entry.DisplayName = obj.network_atlas.nets(network).name; end @@ -115,6 +114,8 @@ function drawBrainPlots(obj) nla.gfx.hideAxes(plot_axis); obj.drawColorMap(plot_axis); + + obj.addTitle(); end function setROIandConnectivity(obj) @@ -122,21 +123,21 @@ function setROIandConnectivity(obj) network1_ROI_indexes = obj.network_atlas.nets(obj.network1).indexes; network2_ROI_indexes = obj.network_atlas.nets(obj.network2).indexes; - for ROI_index1 = 1:numel(network1_ROI_indexes) - network1_ROI_index = network1_ROI_indexes(ROI_index1); + for ROI_index1_iterator = 1:numel(network1_ROI_indexes) + network1_ROI_index = network1_ROI_indexes(ROI_index1_iterator); [coefficient1, coefficient2, function_connectivity1, function_connectivity2] = obj.getCoefficients(network1_ROI_index, network2_ROI_indexes); - obj.ROI_values(ROI_index1) = (sum(coefficient1) + sum(coefficient2)) / (numel(coffecient1) + numel(coefficient2)); - obj.function_connectivity_values(ROI_index1) = (sum(function_connectivity1) + sum(function_connectivity2)) / (numel(function_connectivity1) + numel(function_connectivity2)); + obj.ROI_values(network1_ROI_index) = (sum(coefficient1) + sum(coefficient2)) / (numel(coefficient1) + numel(coefficient2)); + obj.function_connectivity_values(network1_ROI_index) = (sum(function_connectivity1) + sum(function_connectivity2)) / (numel(function_connectivity1) + numel(function_connectivity2)); end - for ROI_index2 = 1:numel(network2_ROI_indexes) - network2_ROI_index = network2_ROI_indexes(ROI_index2); + for ROI_index2_iterator = 1:numel(network2_ROI_indexes) + network2_ROI_index = network2_ROI_indexes(ROI_index2_iterator); [coefficient1, coefficient2, function_connectivity1, function_connectivity2] = obj.getCoefficients(network2_ROI_index, network1_ROI_indexes); - ROI_value = (sum(coefficient1) + sum(coefficient2)) / (numel(coffecient1) + numel(coefficient2)); + ROI_value = (sum(coefficient1) + sum(coefficient2)) / (numel(coefficient1) + numel(coefficient2)); function_connectivity_value = (sum(function_connectivity1) + sum(function_connectivity2)) / (numel(function_connectivity1) + numel(function_connectivity2)); if obj.network1 == obj.network2 obj.ROI_values(network2_ROI_index) = (obj.ROI_values(network2_ROI_index) + ROI_value) ./ 2; - obj.function_connectivity_values(network2_ROI_index) = (obj.function_connectivity_values(network2_ROI_index) + function_connectivity_values) ./ 2; + obj.function_connectivity_values(network2_ROI_index) = (obj.function_connectivity_values(network2_ROI_index) + function_connectivity_value) ./ 2; else obj.ROI_values(network2_ROI_index) = ROI_value; obj.function_connectivity_values(network2_ROI_index) = function_connectivity_value; @@ -146,7 +147,6 @@ function setROIandConnectivity(obj) function [coefficient1, coefficient2, function_connectivity1, function_connectivity2] =... getCoefficients(obj, network1_index, network2_indexes) - coefficient1 = obj.edge_test_result.coeff.get(network1_index, network2_indexes); coefficient2 = obj.edge_test_result.coeff.get(network2_indexes, network1_index); @@ -161,29 +161,29 @@ function setROIandConnectivity(obj) probability_significance1 = obj.edge_test_result.prob_sig.get(network1_index, network2_indexes); probability_significance2 = obj.edge_test_result.prob_sig.get(network2_indexes, network1_index); - coefficient1 = coefficient1(logical()); - coefficient2 = coefficient2(logical()); + coefficient1 = coefficient1(logical(probability_significance1)); + coefficient2 = coefficient2(logical(probability_significance2)); function_connectivity1 = function_connectivity1(logical(probability_significance1)); function_connectivity2 = function_connectivity2(logical(probability_significance2)); end end - function colors = mapColorsToLimits(obj) + function colors = mapColorsToLimits(obj, value, function_connectivity_average) import nla.gfx.valToColor - color_rows = size(colors); + color_rows = size(obj.color_map); color_map_positive = obj.color_map(1:(color_rows/2), :); color_map_negative = obj.color_map(((color_rows/2) + 1):end, :); - if functional_connectivity_exists - colors_positive = valToColor(obj.function_connectivity_vals, -0.5, 0.5, color_map_positive); - colors_negative = valToColor(obj.function_connectivity_vals, -0.5, 0.5, color_map_negative); - colors(obj.ROI_values > 0, :) = colors_positive(obj.ROI_values > 0, :); - colors(obj.ROI_values <= 0, :) = colors_negative(obj.ROI_values <= 0, :); + if obj.color_functional_connectivity + colors_positive = valToColor(function_connectivity_average, -0.5, 0.5, color_map_positive); + colors_negative = valToColor(function_connectivity_average, -0.5, 0.5, color_map_negative); + colors(value > 0, :) = colors_positive(value > 0, :); + colors(value <= 0, :) = colors_negative(value <= 0, :); else - colors = valToColor(obj.ROI_values, obj.lower_limit, obj.upper_limit, obj.color_map); + colors = valToColor(value, obj.lower_limit, obj.upper_limit, obj.color_map); end - colors(isnan(obj.ROI_values), :) = 0.5; + colors(isnan(value), :) = 0.5; end function edges = drawEdges(obj, ROI_position, plot_axis) @@ -207,21 +207,23 @@ function setROIandConnectivity(obj) [coefficient, ~, function_connectivity_vector, ~] = obj.getCoefficients(network_point1, network_point2); function_connectivity_average = mean(function_connectivity_vector); - if ~isempty(cofficient) - color_value = obj.mapValuesToLimits(); + if ~isempty(coefficient) + color_value = obj.mapColorsToLimits(coefficient, function_connectivity_average); color_value = [reshape(color_value, [1, 3]), 0.5]; edge = plot3(plot_axis, [ROI_position(network_point1, 1), ROI_position(network_point2, 1)],... [ROI_position(network_point1, 2), ROI_position(network_point2, 2)],... [ROI_position(network_point1, 3), ROI_position(network_point2, 3)],... "Color", color_value, "LineWidth", 5); + % colorbar(plot_axis, 'off'); + % hold(plot_axis, 'on'); edge.Annotation.LegendInformation.IconDisplayStyle = "off"; - edges = [edges edge]; + edges = [edges, edge]; end end end end - function drawCortex(obj, anatomy, plot_axis, mesh_type, mesh_alpha, view_position, left_color, right_color) + function drawCortex(obj, anatomy, plot_axis, view_position, left_color, right_color) import nla.gfx.ViewPos % Set some defaults up @@ -234,22 +236,22 @@ function drawCortex(obj, anatomy, plot_axis, mesh_type, mesh_alpha, view_positio end % Re-position hemisphere meshes to transverse/axial orientation - [left_mesh, right_mesh] = nla.gfx.anatToMesh(anatomy, mesh_type, view_position); + [left_mesh, right_mesh] = nla.gfx.anatToMesh(anatomy, obj.mesh_type, view_position); % Set lighting and view positioning - if view_position == ViewPos.LAT || ViewPos.MED + if view_position == ViewPos.LAT || view_position == ViewPos.MED view(plot_axis, [-90, 0]); % local light is akin to a lightbulb at that location in space % infinite light has light that originates at that point and only goes in one direction light(plot_axis, "Position", [-100, 200, 0], "Style", "local"); light(plot_axis, "Position", [-50, -500, 100], "Style", "infinite"); light(plot_axis, "Position", [-50, 0, 0], "Style", "infinite"); - else: + else view(plot_axis, [0, 0]); switch view_position case ViewPos.DORSAL view(plot_axis, [0, 90]); - light(plot_axis, "Position", [100, 300, 100], "Style", "infiinite"); + light(plot_axis, "Position", [100, 300, 100], "Style", "infinite"); case ViewPos.LEFT view(plot_axis, [-90, 0]); light(plot_axis, "Position", [-100, 0, 0], "Style", "infinite"); @@ -261,52 +263,59 @@ function drawCortex(obj, anatomy, plot_axis, mesh_type, mesh_alpha, view_positio light(plot_axis, "Position", [100, 300, 100], "Style", "infinite"); case ViewPos.BACK light(plot_axis, "Position", [0, -200, 0], "Style", "infinite"); + end light(plot_axis, "Position", [-500, -20, 0], "Style", "local"); light(plot_axis, "Position", [500, -20, 0], "Style", "local"); light(plot_axis, "Position", [0, -200, 50], "Style", "local"); + end + left_hemisphere = obj.drawCortexHemisphere(plot_axis, anatomy.hemi_l, left_mesh, left_color); + right_hemisphere = obj.drawCortexHemisphere(plot_axis, anatomy.hemi_r, right_mesh, right_color); - left_hemisphere = obj.drawCortexHemisphere(plot_axis, anatomy.hemi_l, left_mesh, left_color, mesh_alpha); - right_hemisphere = obj.drawCortexHemisphere(plot_axis, anatomy.hemi_r, right_mesh, right_color, mesh_alpha); - + hold(plot_axis, "on"); axis(plot_axis, "image"); axis(plot_axis, "off"); end - function hemisphere = drawCortexHemisphere(obj, plot_axis, hemisphere_anatomy, mesh, color, mesh_alpha) - properties = struct( - "Faces", hemisphere_anatomy.elements(:, 1:3), "Vertices", mesh,... + function hemisphere = drawCortexHemisphere(obj, plot_axis, hemisphere_anatomy, mesh, color) + hemisphere = patch(plot_axis, "Faces", hemisphere_anatomy.elements(:, 1:3), "Vertices", mesh,... "EdgeColor", "none", "FaceColor", "interp", "FaceVertexCData", color,... - "FaceLightin", "gourand", "FaceAlpha", mesh_alpha,... - "AmbientStrength", 0.25, "DiffuseStrengh", 0.75, "SpecularStrength", 0.1... - ); - hemisphere = patch(plot_axis, properties); + "FaceLightin", "gouraud", "FaceAlpha", obj.mesh_alpha, "AmbientStrength", 0.25,... + "DiffuseStrength", 0.75, "SpecularStrength", 0.1); hemisphere.Annotation.LegendInformation.IconDisplayStyle = "off"; end - function edges = singlePlot(obj, plot_axis, view_position, mesh_type, color_mode, color_matrix, upper_limit, lower_limit) + function edges = singlePlot(obj, plot_axis, view_position, color_mode, color_matrix, upper_limit, lower_limit) if exist("upper_limit", "var") - self.upper_limit = upper_limit; + obj.upper_limit = upper_limit; end if exist("lower_limit", "var") - self.lower_limit = lower_limit; + obj.lower_limit = lower_limit; end connectivity_map = ~isnan(obj.ROI_values); - [ROI_final_positions, ROI_colors = obj.getROIPositions(mesh_type, view_position, color_mode, color_matrix); - if ~isequal(color_mode, nla.BrainColorMode.NONE) && obj.surface_parcels && ~islogical(obj.network_atlas.parcels) && isequal(size(obj.network_atlas.parcels.ctx_l,1), size(obj.network_atlas.anat.hemi_l.nodes, 1)) && isequal(size(obj.network_atlas.parcels.ctx_r, 1), size(obj.network_atlas.anat.hemi_r.nodes, 1)) - ROI_color_map = [0.5 0.5 0.5; ROI_colors]; - obj.drawCortex(obj.network_atlas.anat, plot_axis, mesh_type, mesh_alpha, view_position, ROI_color_map(obj.network_atlas.parcels.ctx_l + 1, :), ROI_color_map(obj.network_atlas.parcels.ctx_r + 1, :)); + edges = []; + if color_mode == nla.gfx.BrainColorMode.NONE + [ROI_final_positions, ROI_colors] = obj.getROIPositions(view_position, color_mode, color_matrix); + obj.drawCortex(obj.network_atlas.anat, plot_axis, view_position); + edges = [edges, obj.drawEdges(ROI_final_positions, plot_axis)]; else - obj.drawCortex(obj.network_atlas.anat, plot_axis, mesh_type, mesh_alpha, view_position); - if ~isequal(color_mode, nla.BrainColorMode.NONE) - for roi = 1:obj.network_atlas.numROIs(): - nla.gfx.drawSphere(plot_axis, ROI_final_positions(roi, :), ROI_colors(roi, :), obj.ROI_radius); + obj.mesh_alpha = 1; + [ROI_final_positions, ROI_colors] = obj.getROIPositions(view_position, nla.gfx.BrainColorMode.COLOR_ROIS) + if ~isequal(color_mode, nla.gfx.BrainColorMode.NONE) && obj.surface_parcels && ~islogical(obj.network_atlas.parcels) && isequal(size(obj.network_atlas.parcels.ctx_l,1), size(obj.network_atlas.anat.hemi_l.nodes, 1)) && isequal(size(obj.network_atlas.parcels.ctx_r, 1), size(obj.network_atlas.anat.hemi_r.nodes, 1)) + ROI_color_map = [0.5 0.5 0.5; ROI_colors]; + obj.drawCortex(obj.network_atlas.anat, plot_axis, view_position, ROI_color_map(obj.network_atlas.parcels.ctx_l + 1, :), ROI_color_map(obj.network_atlas.parcels.ctx_r + 1, :)); + else + drawCortex(ax, net_atlas.anat, ctx, mesh_alpha, view_pos); + if color_mode ~= BrainColorMode.NONE + for i = 1:net_atlas.numROIs() + % render a sphere at each ROI location + nla.gfx.drawSphere(ax, ROI_final_pos(i, :), ROI_color(i, :), ROI_radius); + end end end - edges = obj.drawEdges(ROI_final_positions, plot_axis); end if ~isfield(obj.edge_test_options, "show_ROI_centroids") || (isfield(obj.edge_test_options, "show_ROI_centroids") && isequal(obj.edge_test_options.show_ROI_centroids, true)); @@ -314,7 +323,7 @@ function drawCortex(obj, anatomy, plot_axis, mesh_type, mesh_alpha, view_positio end colorbar(plot_axis, "off"); - hold(plot_axis, on); + hold(plot_axis, "on"); end function drawROISpheres(obj, ROI_position, plot_axis, connectivity_map) @@ -331,10 +340,10 @@ function drawROISpheres(obj, ROI_position, plot_axis, connectivity_map) end end - function [ROI_final_positions, ROI_colors] = getROIPositions(obj, mesh_type, view_position, color_mode, color_matrix) + function [ROI_final_positions, ROI_colors] = getROIPositions(obj, view_position, color_mode, color_matrix) import nla.gfx.BrainColorMode - [left_mesh, right_mesh] = nla.gfx.anatToMesh(obj.network_atlas.anat, mesh_type, view_position); + [left_mesh, right_mesh] = nla.gfx.anatToMesh(obj.network_atlas.anat, obj.mesh_type, view_position); ROI_positions = [obj.network_atlas.ROIs.pos]'; [left_indexes, left_distances] = knnsearch(obj.network_atlas.anat.hemi_l.nodes, ROI_positions); @@ -345,9 +354,9 @@ function drawROISpheres(obj, ROI_position, plot_axis, connectivity_map) ROI_index = obj.network_atlas.nets(network).indexes(network_indexes); offset = [NaN NaN NaN]; if left_distances(ROI_index) < right_distances(ROI_index) - offset = left_mesh(left_indexes(ROI_index, :) - obj.network_atlas.anat.hemi_l.nodes(ROI_index), :); - else: - offset = right_mesh(right_indexes(ROI_index, :) - obj.network_atlas.anat.hemi_r.nodes(ROI_index), :); + offset = left_mesh(left_indexes(ROI_index), :) - obj.network_atlas.anat.hemi_l.nodes(left_indexes(ROI_index), :); + else + offset = right_mesh(right_indexes(ROI_index), :) - obj.network_atlas.anat.hemi_r.nodes(right_indexes(ROI_index), :); end ROI_final_positions(ROI_index, :) = ROI_positions(ROI_index, :) + offset; @@ -358,13 +367,15 @@ function drawROISpheres(obj, ROI_position, plot_axis, connectivity_map) ROI_colors(ROI_index, :) = color_matrix(network, :); case BrainColorMode.COLOR_ROIS ROI_colors(ROI_index, :) = color_matrix(ROI_index, :); + otherwise + ROI_colors = false; end end end end function drawColorMap(obj, plot_axis) - if obj.functional_connectivity_exists + if obj.color_functional_connectivity colormap(plot_axis, obj.color_map); color_bar = colorbar(plot_axis); color_bar.Location = "southoutside"; @@ -386,10 +397,28 @@ function drawColorMap(obj, plot_axis) end end - %% GETTERS for dependent properties + function addTitle(obj) + figure_title = sprintf("Brain Visualization: Average of edge-level correlations between nets in [%s - %s] Network Pair", obj.network_atlas.nets(obj.network1).name, obj.network_atlas.nets(obj.network2).name); + if obj.is_noncorrelation_input + figure_title = [figure_title sprintf(" (Edge-level P < %.2g)", obj.edge_test_options.prob_max)]; + end + obj.figure_plot.Name = figure_title; + end + + %% + % GETTERS for dependent properties function value = get.is_noncorrelation_input(obj) % Convenience method to determine if inputs were correlation coefficients, or "significance" values value = any(strcmp(obj.noncorrelation_input_tests, obj.test_name)); end + + function value = get.functional_connectivity_exists(obj) + value = isfield(obj.edge_test_options, "func_conn"); + end + + function value = get.color_functional_connectivity(obj) + value = false; + end + %% end end \ No newline at end of file diff --git a/+nla/+gfx/drawCortexHemi.m b/+nla/+gfx/drawCortexHemi.m index b6fec47c..eebba827 100755 --- a/+nla/+gfx/drawCortexHemi.m +++ b/+nla/+gfx/drawCortexHemi.m @@ -6,7 +6,7 @@ % color: 3x1 vector, cortex mesh color % mesh_alpha: transparency of cortex mesh - obj = patch(ax, 'Faces',anat_hemi.elements(:,1:3),'Vertices', mesh,... + obj = patch(ax, 'Faces', anat_hemi.elements(:,1:3),'Vertices', mesh,... 'EdgeColor','none','FaceColor','interp','FaceVertexCData', color,... 'FaceLighting','gouraud','FaceAlpha',mesh_alpha,... 'AmbientStrength',0.25,'DiffuseStrength',0.75,'SpecularStrength',0.1); diff --git a/+nla/+net/+result/NetworkResultPlotParameter.m b/+nla/+net/+result/NetworkResultPlotParameter.m index 09c9acbc..42ea2a2b 100644 --- a/+nla/+net/+result/NetworkResultPlotParameter.m +++ b/+nla/+net/+result/NetworkResultPlotParameter.m @@ -107,9 +107,11 @@ function brainFigureButtonCallback(network1, network2) wait_text = sprintf("Generating %s - %s network-pair brain plot", obj.network_atlas.nets(network1).name,... obj.network_atlas.nets(network2).name); wait_popup = waitbar(0.05, wait_text); - nla.gfx.drawBrainVis(edge_test_options, obj.updated_test_options, obj.network_atlas,... - nla.gfx.MeshType.STD, 0.25, 3, true, edge_test_result, network1, network2,... - any(strcmp(obj.noncorrelation_input_tests, obj.network_test_results.test_name))); + % nla.gfx.drawBrainVis(edge_test_options, obj.updated_test_options, obj.network_atlas,... + % nla.gfx.MeshType.STD, 0.25, 3, true, edge_test_result, network1, network2,... + % any(strcmp(obj.noncorrelation_input_tests, obj.network_test_results.test_name))); + brain_plot = nla.gfx.brain.BrainPlot(edge_test_result, edge_test_options, obj.updated_test_options, network1, network2, obj.network_atlas); + brain_plot.drawBrainPlots() waitbar(0.95); close(wait_popup); end From 0bc4779ffbf6adea6b04e787b4931623904ef2a8 Mon Sep 17 00:00:00 2001 From: Jim Pollaro Date: Thu, 5 Sep 2024 13:19:32 -0500 Subject: [PATCH 07/14] adding modal to brain scale --- +nla/+gfx/+brain/BrainPlot.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/+nla/+gfx/+brain/BrainPlot.m b/+nla/+gfx/+brain/BrainPlot.m index 65dfbb3b..95cbc356 100644 --- a/+nla/+gfx/+brain/BrainPlot.m +++ b/+nla/+gfx/+brain/BrainPlot.m @@ -405,6 +405,11 @@ function addTitle(obj) obj.figure_plot.Name = figure_title; end + function openModal(obj, source, ~) + d = figure("WindowStyle", "normal", "Units", "pixels", "Position", [source.Position(1), source.Position(2), source.Position(3) * 10, source.Position(4) * 10]); + upper_limit_box = uicontrol("Style", "edit", "Units", "pixels"); + end + %% % GETTERS for dependent properties function value = get.is_noncorrelation_input(obj) From 68dbd0ffe499e8043eb922c04236eaf5cd5c51b3 Mon Sep 17 00:00:00 2001 From: Jim Pollaro Date: Mon, 16 Sep 2024 15:04:42 -0500 Subject: [PATCH 08/14] trying to figure out color mapping --- +nla/+gfx/+brain/BrainPlot.m | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/+nla/+gfx/+brain/BrainPlot.m b/+nla/+gfx/+brain/BrainPlot.m index 95cbc356..a8b9229e 100644 --- a/+nla/+gfx/+brain/BrainPlot.m +++ b/+nla/+gfx/+brain/BrainPlot.m @@ -298,27 +298,27 @@ function drawCortex(obj, anatomy, plot_axis, view_position, left_color, right_co edges = []; if color_mode == nla.gfx.BrainColorMode.NONE - [ROI_final_positions, ROI_colors] = obj.getROIPositions(view_position, color_mode, color_matrix); + [ROI_final_positions, ~] = obj.getROIPositions(view_position, color_mode, color_matrix); obj.drawCortex(obj.network_atlas.anat, plot_axis, view_position); edges = [edges, obj.drawEdges(ROI_final_positions, plot_axis)]; else obj.mesh_alpha = 1; - [ROI_final_positions, ROI_colors] = obj.getROIPositions(view_position, nla.gfx.BrainColorMode.COLOR_ROIS) + [ROI_final_positions, ROI_colors] = obj.getROIPositions(view_position, nla.gfx.BrainColorMode.COLOR_ROIS); if ~isequal(color_mode, nla.gfx.BrainColorMode.NONE) && obj.surface_parcels && ~islogical(obj.network_atlas.parcels) && isequal(size(obj.network_atlas.parcels.ctx_l,1), size(obj.network_atlas.anat.hemi_l.nodes, 1)) && isequal(size(obj.network_atlas.parcels.ctx_r, 1), size(obj.network_atlas.anat.hemi_r.nodes, 1)) ROI_color_map = [0.5 0.5 0.5; ROI_colors]; obj.drawCortex(obj.network_atlas.anat, plot_axis, view_position, ROI_color_map(obj.network_atlas.parcels.ctx_l + 1, :), ROI_color_map(obj.network_atlas.parcels.ctx_r + 1, :)); else - drawCortex(ax, net_atlas.anat, ctx, mesh_alpha, view_pos); + drawCortex(ax, net_atlas.anat, ctx, obj.mesh_alpha, view_pos); if color_mode ~= BrainColorMode.NONE for i = 1:net_atlas.numROIs() % render a sphere at each ROI location - nla.gfx.drawSphere(ax, ROI_final_pos(i, :), ROI_color(i, :), ROI_radius); + nla.gfx.drawSphere(ax, ROI_final_positions(i, :), ROI_colors(i, :), obj.ROI_radius); end end end end - if ~isfield(obj.edge_test_options, "show_ROI_centroids") || (isfield(obj.edge_test_options, "show_ROI_centroids") && isequal(obj.edge_test_options.show_ROI_centroids, true)); + if (~isfield(obj.edge_test_options, "show_ROI_centroids")) || (isfield(obj.edge_test_options, "show_ROI_centroids") && isequal(obj.edge_test_options.show_ROI_centroids, true)) obj.drawROISpheres(ROI_final_positions, plot_axis, connectivity_map); end @@ -386,9 +386,9 @@ function drawColorMap(obj, plot_axis) color_bar.ButtonDownFcn = @openModal; number_of_ticks = 10; - ticks = [0:number_of_ticks]; + ticks = 0:number_of_ticks; color_bar.Ticks = double(ticks) ./ number_of_ticks; - tick_labels = {}; + tick_labels = cell(number_of_ticks + 1, 1); for tick = ticks tick_labels{tick + 1} = sprintf("%.2g", obj.lower_limit + (tick * ((double(obj.upper_limit - obj.lower_limit) / number_of_ticks)))); end @@ -407,9 +407,21 @@ function addTitle(obj) function openModal(obj, source, ~) d = figure("WindowStyle", "normal", "Units", "pixels", "Position", [source.Position(1), source.Position(2), source.Position(3) * 10, source.Position(4) * 10]); - upper_limit_box = uicontrol("Style", "edit", "Units", "pixels"); + + upper_limit_box_position = [90, d.Position(4) - 30, 100, 30]; + upper_limit_box = uicontrol("Style", "edit", "Units", "pixels", "String", obj.upper_limit, "Position", upper_limit_box_position); + lower_limit_box_position = [90, d.Position(4) - 30, 100, 30]; + lower_limit_box = uicontrol("Style", "edit", "Units", "pixels", "String", obj.lower_limit, "Position", lower_limit_box_position); + apply_button_position = [10, 10, 100, 30]; + uicontrol("String", "Apply", "Callback", {@obj.applyScale, upper_limit_box, lower_limit_box}, "Units", "pixels", "Position", apply_button_position); % Apply Button + close_button_position = [apply_button_position(1) + apply_button_position(3) + 10, apply_button_position(2), apply_button_position(3), apply_button_position(4)]; + uicontrol("String", "Close", "Callback", @(~, ~)close(d), "Units", "pixels", "Position", close_button_position); end + % function applyScale(obj, upper_value, lower_value) + + % end + %% % GETTERS for dependent properties function value = get.is_noncorrelation_input(obj) From 2b6a77e5e6f6c9716cd2cdea4aa9b89ec9a6be11 Mon Sep 17 00:00:00 2001 From: Jim Pollaro Date: Tue, 26 Nov 2024 09:33:50 -0600 Subject: [PATCH 09/14] some fixes, but still won't work --- +nla/+gfx/+brain/BrainPlot.m | 78 +++++++++++++++++++++++++++--------- 1 file changed, 58 insertions(+), 20 deletions(-) diff --git a/+nla/+gfx/+brain/BrainPlot.m b/+nla/+gfx/+brain/BrainPlot.m index a8b9229e..6a94ff9d 100644 --- a/+nla/+gfx/+brain/BrainPlot.m +++ b/+nla/+gfx/+brain/BrainPlot.m @@ -18,10 +18,12 @@ surface_parcels mesh_type mesh_alpha + all_edges = [] end properties (Constant) noncorrelation_input_tests = ["chi_squared", "hypergeometric"] % These are tests that do not use correlation coefficients as inputs + default_settings = struct("upper_limit", 0.5, "lower_limit", -0.5) end properties (Dependent) @@ -70,17 +72,16 @@ function drawBrainPlots(obj) obj.setROIandConnectivity(); - all_edges = []; if obj.surface_parcels && ~islogical(obj.network_atlas.parcels) edges1 = obj.singlePlot(subplot("Position", [0.45, 0.505, 0.53, 0.45]), ViewPos.LAT, BrainColorMode.COLOR_ROIS, obj.color_map); edges2 = obj.singlePlot(subplot("Position", [0.45, 0.055, 0.53, 0.45]), ViewPos.MED, BrainColorMode.COLOR_ROIS, obj.color_map); - all_edges = [edges1 edges2]; + obj.all_edges = [edges1 edges2]; else edges1 = obj.singlePlot(subplot("Position", [0.45, 0.505, 0.26, 0.45]), ViewPos.BACK, BrainColorMode.NONE, obj.color_map); edges2 = obj.singlePlot(subplot("Position", [0.73, 0.505, 0.26, 0.45]), ViewPos.FRONT, BrainColorMode.NONE, obj.color_map); edges3 = obj.singlePlot(subplot("Position", [0.45, 0.055, 0.26, 0.45]), ViewPos.LEFT, BrainColorMode.NONE, obj.color_map); edges4 = obj.singlePlot(subplot("Position", [0.73, 0.055, 0.26, 0.45]), ViewPos.RIGHT, BrainColorMode.NONE, obj.color_map); - all_edges = [edges1 edges2 edges3 edges4]; + obj.all_edges = [edges1 edges2 edges3 edges4]; end if obj.color_functional_connectivity @@ -168,16 +169,24 @@ function setROIandConnectivity(obj) end end - function colors = mapColorsToLimits(obj, value, function_connectivity_average) + function colors = mapColorsToLimits(obj, value, function_connectivity_average, varargin) import nla.gfx.valToColor + if isempty(varargin) + scale_min = -0.5; + scale_max = 0.5; + else + scale_min = str2double(varargin{1}); + scale_max = str2double(varargin{2}); + end + color_rows = size(obj.color_map); color_map_positive = obj.color_map(1:(color_rows/2), :); color_map_negative = obj.color_map(((color_rows/2) + 1):end, :); if obj.color_functional_connectivity - colors_positive = valToColor(function_connectivity_average, -0.5, 0.5, color_map_positive); - colors_negative = valToColor(function_connectivity_average, -0.5, 0.5, color_map_negative); + colors_positive = valToColor(function_connectivity_average, scale_min, scale_max, color_map_positive); + colors_negative = valToColor(function_connectivity_average, scale_min, scale_max, color_map_negative); colors(value > 0, :) = colors_positive(value > 0, :); colors(value <= 0, :) = colors_negative(value <= 0, :); else @@ -208,12 +217,7 @@ function setROIandConnectivity(obj) function_connectivity_average = mean(function_connectivity_vector); if ~isempty(coefficient) - color_value = obj.mapColorsToLimits(coefficient, function_connectivity_average); - color_value = [reshape(color_value, [1, 3]), 0.5]; - edge = plot3(plot_axis, [ROI_position(network_point1, 1), ROI_position(network_point2, 1)],... - [ROI_position(network_point1, 2), ROI_position(network_point2, 2)],... - [ROI_position(network_point1, 3), ROI_position(network_point2, 3)],... - "Color", color_value, "LineWidth", 5); + edge = obj.assignColorToEdge(ROI_position, network_point1, network_point2, plot_axis, coefficient, function_connectivity_average); % colorbar(plot_axis, 'off'); % hold(plot_axis, 'on'); edge.Annotation.LegendInformation.IconDisplayStyle = "off"; @@ -223,6 +227,22 @@ function setROIandConnectivity(obj) end end + function edge = assignColorToEdge(obj, ROI_position, network_point1, network_point2, plot_axis, coefficient, function_connectivity_average, varargin) + if ~isempty(coefficient) + if ~isempty(varargin) + color_value = obj.mapColorsToLimits(coefficient, function_connectivity_average, varargin{1}, varargin{2}); + else + color_value = obj.mapColorsToLimits(coefficient, function_connectivity_average); + end + color_value = [reshape(color_value, [1, 3]), 0.5]; + edge = plot3(plot_axis, [ROI_position(network_point1, 1), ROI_position(network_point2, 1)],... + [ROI_position(network_point1, 2), ROI_position(network_point2, 2)],... + [ROI_position(network_point1, 3), ROI_position(network_point2, 3)],... + "Color", color_value, "LineWidth", 5); + set(edge, "UserData", struct("plot_axis", plot_axis, "ROI_position", ROI_position, "network_point1", network_point1, "network_point2", network_point2, "coefficient", coefficient, "function_connectivity_average", function_connectivity_average)) + end + end + function drawCortex(obj, anatomy, plot_axis, view_position, left_color, right_color) import nla.gfx.ViewPos @@ -379,11 +399,12 @@ function drawColorMap(obj, plot_axis) colormap(plot_axis, obj.color_map); color_bar = colorbar(plot_axis); color_bar.Location = "southoutside"; + set(color_bar, 'ButtonDownFcn', @obj.openModal); else colormap(plot_axis, obj.color_map); color_bar = colorbar(plot_axis); color_bar.Location = "southoutside"; - color_bar.ButtonDownFcn = @openModal; + set(color_bar, 'ButtonDownFcn', @obj.openModal); number_of_ticks = 10; ticks = 0:number_of_ticks; @@ -402,25 +423,42 @@ function addTitle(obj) if obj.is_noncorrelation_input figure_title = [figure_title sprintf(" (Edge-level P < %.2g)", obj.edge_test_options.prob_max)]; end - obj.figure_plot.Name = figure_title; + obj.plot_figure.Name = figure_title; end function openModal(obj, source, ~) - d = figure("WindowStyle", "normal", "Units", "pixels", "Position", [source.Position(1), source.Position(2), source.Position(3) * 10, source.Position(4) * 10]); + d = figure("WindowStyle", "normal", "Units", "pixels", "Position", [obj.plot_figure.Position(1) + 10, obj.plot_figure.Position(2) + 10, obj.plot_figure.Position(3) / 2, obj.plot_figure.Position(4) / 2]); - upper_limit_box_position = [90, d.Position(4) - 30, 100, 30]; + upper_limit_box_position = [120, 90, 100, 30]; upper_limit_box = uicontrol("Style", "edit", "Units", "pixels", "String", obj.upper_limit, "Position", upper_limit_box_position); - lower_limit_box_position = [90, d.Position(4) - 30, 100, 30]; + uicontrol("Style", "text", "Units", "pixels", "String", "Upper Limit", "Position", [upper_limit_box_position(1) - 90, upper_limit_box_position(2) - 2, 80, upper_limit_box_position(4) - 5]); + lower_limit_box_position = [120, 50, 100, 30]; lower_limit_box = uicontrol("Style", "edit", "Units", "pixels", "String", obj.lower_limit, "Position", lower_limit_box_position); + uicontrol("Style", "text", "Units", "pixels", "String", "Lower Limit", "Position", [lower_limit_box_position(1) - 90, lower_limit_box_position(2) - 2, 80, lower_limit_box_position(4) - 5]); apply_button_position = [10, 10, 100, 30]; uicontrol("String", "Apply", "Callback", {@obj.applyScale, upper_limit_box, lower_limit_box}, "Units", "pixels", "Position", apply_button_position); % Apply Button - close_button_position = [apply_button_position(1) + apply_button_position(3) + 10, apply_button_position(2), apply_button_position(3), apply_button_position(4)]; + default_button_position = [apply_button_position(1) + apply_button_position(3) + 10, apply_button_position(2), apply_button_position(3), apply_button_position(4)]; + uicontrol("String", "Default", "Callback", {@obj.setDefaults, upper_limit_box, lower_limit_box}, "Units", "pixels", "Position", default_button_position); + close_button_position = [default_button_position(1) + default_button_position(3) + 10, default_button_position(2), default_button_position(3), default_button_position(4)]; uicontrol("String", "Close", "Callback", @(~, ~)close(d), "Units", "pixels", "Position", close_button_position); end - % function applyScale(obj, upper_value, lower_value) + function applyScale(obj, upper_value, lower_value) + for edge = obj.all_edges + ROI_position = edge.UserData.ROI_position; + plot_axis = edge.UserData.plot_axis; + coefficient = edge.UserData.coefficient; + network_point1 = edge.UserData.network_point1; + network_point2 = edge.UserData.network_point2; + function_connectivity_average = edge.UserData.function_connectivity_average; + edge = assignColorToEdge(ROI_position, network_point1, network_point2, plot_axis, coefficient, function_connectivity_average, lower_value, upper_value); + end + end - % end + function setDefaults(obj, ~, ~, upper_limit_box, lower_limit_box) + set(upper_limit_box, "String", obj.default_settings.upper_limit); + set(lower_limit_box, "String", obj.default_settings.lower_limit); + end %% % GETTERS for dependent properties From 0ea8d8b38ec45a2991d4c74e9d7ae7e68398a566 Mon Sep 17 00:00:00 2001 From: Jim Pollaro Date: Thu, 23 Jan 2025 13:36:39 -0600 Subject: [PATCH 10/14] finally got scaling to work --- +nla/+gfx/+brain/BrainPlot.m | 56 +++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/+nla/+gfx/+brain/BrainPlot.m b/+nla/+gfx/+brain/BrainPlot.m index 6a94ff9d..b11c3a28 100644 --- a/+nla/+gfx/+brain/BrainPlot.m +++ b/+nla/+gfx/+brain/BrainPlot.m @@ -12,6 +12,8 @@ upper_limit lower_limit color_map = cat(1, winter(1000), flip(autumn(1000))); + color_map_axis + color_bar ROI_values = [] function_connectivity_values = [] ROI_radius @@ -91,10 +93,11 @@ function drawBrainPlots(obj) end if obj.surface_parcels && ~islogical(obj.network_atlas.parcels) - obj.singlePlot(plot_axis, ViewPos.DORSAL, BrainColorMode.COLOR_ROIS, obj.color_map); + edges5 = obj.singlePlot(plot_axis, ViewPos.DORSAL, BrainColorMode.COLOR_ROIS, obj.color_map); else - obj.singlePlot(plot_axis, ViewPos.DORSAL, BrainColorMode.NONE, obj.color_map); + edges5 = obj.singlePlot(plot_axis, ViewPos.DORSAL, BrainColorMode.NONE, obj.color_map); end + obj.all_edges = [obj.all_edges edges5]; light("Position", [0, 100, 100], "Style", "local"); @@ -116,6 +119,8 @@ function drawBrainPlots(obj) obj.drawColorMap(plot_axis); + obj.color_map_axis = plot_axis; + obj.addTitle(); end @@ -239,7 +244,11 @@ function setROIandConnectivity(obj) [ROI_position(network_point1, 2), ROI_position(network_point2, 2)],... [ROI_position(network_point1, 3), ROI_position(network_point2, 3)],... "Color", color_value, "LineWidth", 5); - set(edge, "UserData", struct("plot_axis", plot_axis, "ROI_position", ROI_position, "network_point1", network_point1, "network_point2", network_point2, "coefficient", coefficient, "function_connectivity_average", function_connectivity_average)) + % Matlab says you can save a structure to the "UserData" field of a line. You cannot. so, we do something dumb + edge_data = {}; + edge_data{1} = {"coefficient", "function_connectivity_average"}; + edge_data{2} = {coefficient, function_connectivity_average}; + set(edge, "UserData", edge_data) end end @@ -397,23 +406,23 @@ function drawROISpheres(obj, ROI_position, plot_axis, connectivity_map) function drawColorMap(obj, plot_axis) if obj.color_functional_connectivity colormap(plot_axis, obj.color_map); - color_bar = colorbar(plot_axis); - color_bar.Location = "southoutside"; - set(color_bar, 'ButtonDownFcn', @obj.openModal); + obj.color_bar = colorbar(plot_axis); + obj.color_bar.Location = "southoutside"; + set(obj.color_bar, 'ButtonDownFcn', @obj.openModal); else colormap(plot_axis, obj.color_map); - color_bar = colorbar(plot_axis); - color_bar.Location = "southoutside"; - set(color_bar, 'ButtonDownFcn', @obj.openModal); + obj.color_bar = colorbar(plot_axis); + obj.color_bar.Location = "southoutside"; + set(obj.color_bar, 'ButtonDownFcn', @obj.openModal); number_of_ticks = 10; ticks = 0:number_of_ticks; - color_bar.Ticks = double(ticks) ./ number_of_ticks; + obj.color_bar.Ticks = double(ticks) ./ number_of_ticks; tick_labels = cell(number_of_ticks + 1, 1); for tick = ticks tick_labels{tick + 1} = sprintf("%.2g", obj.lower_limit + (tick * ((double(obj.upper_limit - obj.lower_limit) / number_of_ticks)))); end - color_bar.TickLabels = tick_labels; + obj.color_bar.TickLabels = tick_labels; caxis(plot_axis, [0, 1]); end end @@ -443,17 +452,24 @@ function openModal(obj, source, ~) uicontrol("String", "Close", "Callback", @(~, ~)close(d), "Units", "pixels", "Position", close_button_position); end - function applyScale(obj, upper_value, lower_value) + function applyScale(obj, ~, ~, upper_value, lower_value) + colorbar(obj.color_bar, "off"); + obj.upper_limit = str2double(upper_value.String); + obj.lower_limit = str2double(lower_value.String); for edge = obj.all_edges - ROI_position = edge.UserData.ROI_position; - plot_axis = edge.UserData.plot_axis; - coefficient = edge.UserData.coefficient; - network_point1 = edge.UserData.network_point1; - network_point2 = edge.UserData.network_point2; - function_connectivity_average = edge.UserData.function_connectivity_average; - edge = assignColorToEdge(ROI_position, network_point1, network_point2, plot_axis, coefficient, function_connectivity_average, lower_value, upper_value); + edge_data = edge.UserData; + edge_data_struct = struct(); + edge_data_names = edge_data{1}; + edge_data_values = edge_data{2}; + for idx = 1:numel(edge_data_names) + edge_data_struct.(edge_data_names{idx}) = edge_data_values{idx}; + end + color_value = obj.mapColorsToLimits(edge_data_struct.coefficient, edge_data_struct.function_connectivity_average, obj.lower_limit, obj.upper_limit); + color_value = [reshape(color_value, [1, 3]), 0.5]; + set(edge, "Color", color_value); end - end + obj.drawColorMap(obj.color_map_axis); + end function setDefaults(obj, ~, ~, upper_limit_box, lower_limit_box) set(upper_limit_box, "String", obj.default_settings.upper_limit); From 7b8803b66d3efd66dd424f360abb9801ced490c1 Mon Sep 17 00:00:00 2001 From: Jim Pollaro Date: Mon, 27 Jan 2025 10:55:19 -0600 Subject: [PATCH 11/14] small changes to app version of brain plots that don't seem to work --- +nla/+gfx/+brain/BrainPlotApp.mlapp | Bin 0 -> 21848 bytes +nla/+net/+result/NetworkResultPlotParameter.m | 8 ++++---- 2 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 +nla/+gfx/+brain/BrainPlotApp.mlapp diff --git a/+nla/+gfx/+brain/BrainPlotApp.mlapp b/+nla/+gfx/+brain/BrainPlotApp.mlapp new file mode 100644 index 0000000000000000000000000000000000000000..faaec98abc8a2d710c7c29342ef42341ba5986ec GIT binary patch literal 21848 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 Date: Mon, 10 Feb 2025 13:02:47 -0600 Subject: [PATCH 12/14] Added color scaling to brain plots --- +nla/+gfx/+brain/BrainPlot.m | 21 ++++++++++++++++--- .../+net/+result/NetworkResultPlotParameter.m | 8 +++---- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/+nla/+gfx/+brain/BrainPlot.m b/+nla/+gfx/+brain/BrainPlot.m index b11c3a28..9e710cee 100644 --- a/+nla/+gfx/+brain/BrainPlot.m +++ b/+nla/+gfx/+brain/BrainPlot.m @@ -52,7 +52,7 @@ addParameter(brain_input_parser, "mesh_alpha", 0.25, validNumberInput); parse(brain_input_parser, edge_test_result, edge_test_options, network_test_options, network1, network2, network_atlas, varargin{:}); - properties = {"edge_test_result", "edge_test_options", "network_test_options", "network1", "network2", "network_atlas", "ROI_radius", "surface_parcels", "mesh_type", "mesh_alpha"}; + properties = ["edge_test_result", "edge_test_options", "network_test_options", "network1", "network2", "network_atlas", "ROI_radius", "surface_parcels", "mesh_type", "mesh_alpha"]; for property = properties obj.(property{1}) = brain_input_parser.Results.(property{1}); end @@ -435,8 +435,8 @@ function addTitle(obj) obj.plot_figure.Name = figure_title; end - function openModal(obj, source, ~) - d = figure("WindowStyle", "normal", "Units", "pixels", "Position", [obj.plot_figure.Position(1) + 10, obj.plot_figure.Position(2) + 10, obj.plot_figure.Position(3) / 2, obj.plot_figure.Position(4) / 2]); + function openModal(obj, ~, ~) + d = figure("WindowStyle", "normal", "Units", "pixels", "Position", [obj.plot_figure.Position(1) + 10, obj.plot_figure.Position(2) + 10, 350, 140]); upper_limit_box_position = [120, 90, 100, 30]; upper_limit_box = uicontrol("Style", "edit", "Units", "pixels", "String", obj.upper_limit, "Position", upper_limit_box_position); @@ -453,9 +453,23 @@ function openModal(obj, source, ~) end function applyScale(obj, ~, ~, upper_value, lower_value) + waitbar(0.05, "Please wait while scale is changed..."); colorbar(obj.color_bar, "off"); + total_colors = 2000; obj.upper_limit = str2double(upper_value.String); obj.lower_limit = str2double(lower_value.String); + positive_percent = 0; + negative_percent = 0; + if obj.lower_limit >= 0 + positive_percent = 100; + elseif obj.upper_limit <= 0 + negative_percent = 100; + else + total_spread = obj.upper_limit - obj.lower_limit; + positive_percent = obj.upper_limit / total_spread; + negative_percent = -obj.lower_limit / total_spread; + end + obj.color_map = cat(1, winter(total_colors * negative_percent), flip(autumn(total_colors * positive_percent))); for edge = obj.all_edges edge_data = edge.UserData; edge_data_struct = struct(); @@ -469,6 +483,7 @@ function applyScale(obj, ~, ~, upper_value, lower_value) set(edge, "Color", color_value); end obj.drawColorMap(obj.color_map_axis); + waitbar(0.90); end function setDefaults(obj, ~, ~, upper_limit_box, lower_limit_box) diff --git a/+nla/+net/+result/NetworkResultPlotParameter.m b/+nla/+net/+result/NetworkResultPlotParameter.m index 5ec85060..ccb47038 100644 --- a/+nla/+net/+result/NetworkResultPlotParameter.m +++ b/+nla/+net/+result/NetworkResultPlotParameter.m @@ -107,10 +107,10 @@ function brainFigureButtonCallback(network1, network2) wait_text = sprintf("Generating %s - %s network-pair brain plot", obj.network_atlas.nets(network1).name,... obj.network_atlas.nets(network2).name); wait_popup = waitbar(0.05, wait_text); - nla.gfx.drawBrainVis(edge_test_options, obj.updated_test_options, obj.network_atlas,... - nla.gfx.MeshType.STD, 0.25, 3, true, edge_test_result, network1, network2,... - any(strcmp(obj.noncorrelation_input_tests, obj.network_test_results.test_name))); - % nla.gfx.brain.BrainPlotApp(edge_test_result, edge_test_options, obj.updated_test_options, network1, network2); + % nla.gfx.drawBrainVis(edge_test_options, obj.updated_test_options, obj.network_atlas,... + % nla.gfx.MeshType.STD, 0.25, 3, true, edge_test_result, network1, network2,... + % any(strcmp(obj.noncorrelation_input_tests, obj.network_test_results.test_name))); + brain_plot = nla.gfx.brain.BrainPlot(edge_test_result, edge_test_options, obj.updated_test_options, network1, network2, edge_test_options.net_atlas); brain_plot.drawBrainPlots() waitbar(0.95); close(wait_popup); From bf7e0e7b9558a53ee776f7c7cd0754fb1b45a53c Mon Sep 17 00:00:00 2001 From: Jim Pollaro Date: Mon, 10 Feb 2025 13:09:48 -0600 Subject: [PATCH 13/14] added wait bar for scale change --- +nla/+gfx/+brain/BrainPlot.m | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/+nla/+gfx/+brain/BrainPlot.m b/+nla/+gfx/+brain/BrainPlot.m index 9e710cee..29da4ea1 100644 --- a/+nla/+gfx/+brain/BrainPlot.m +++ b/+nla/+gfx/+brain/BrainPlot.m @@ -453,7 +453,7 @@ function openModal(obj, ~, ~) end function applyScale(obj, ~, ~, upper_value, lower_value) - waitbar(0.05, "Please wait while scale is changed..."); + wait_popup = waitbar(0.05, "Please wait while scale is changed..."); colorbar(obj.color_bar, "off"); total_colors = 2000; obj.upper_limit = str2double(upper_value.String); @@ -469,7 +469,7 @@ function applyScale(obj, ~, ~, upper_value, lower_value) positive_percent = obj.upper_limit / total_spread; negative_percent = -obj.lower_limit / total_spread; end - obj.color_map = cat(1, winter(total_colors * negative_percent), flip(autumn(total_colors * positive_percent))); + obj.color_map = cat(1, winter(round(total_colors * negative_percent)), flip(autumn(round(total_colors * positive_percent)))); for edge = obj.all_edges edge_data = edge.UserData; edge_data_struct = struct(); @@ -484,6 +484,7 @@ function applyScale(obj, ~, ~, upper_value, lower_value) end obj.drawColorMap(obj.color_map_axis); waitbar(0.90); + close(wait_popup) end function setDefaults(obj, ~, ~, upper_limit_box, lower_limit_box) From 356535e9db6fd7b4b18824d8e817276e160b5c47 Mon Sep 17 00:00:00 2001 From: Jim Pollaro Date: Mon, 10 Feb 2025 13:18:25 -0600 Subject: [PATCH 14/14] remove app version of brain plot --- +nla/+gfx/+brain/BrainPlotApp.mlapp | Bin 21848 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 +nla/+gfx/+brain/BrainPlotApp.mlapp diff --git a/+nla/+gfx/+brain/BrainPlotApp.mlapp b/+nla/+gfx/+brain/BrainPlotApp.mlapp deleted file mode 100644 index faaec98abc8a2d710c7c29342ef42341ba5986ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21848 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