Skip to content

Commit 7e7a88e

Browse files
Performance: keep the sources as cell array for as long a possible (#841)
Reduce the required RAM when browsing results (sources) which are saved as matrix decomposition (see #733) --------- Co-authored-by: rcassani <raymundo.cassani@gmail.com>
1 parent 15b9abb commit 7e7a88e

5 files changed

Lines changed: 167 additions & 22 deletions

File tree

toolbox/core/bst_memory.m

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2189,17 +2189,35 @@ function LoadResultsMatrix(iDS, iResult)
21892189
% ===== GET RESULTS VALUES =====
21902190
% === FULL RESULTS ===
21912191
if ~isempty(GlobalData.DataSet(iDS).Results(iResult).ImageGridAmp)
2192-
% Get ImageGridAmp interesting sub-part
2193-
if isempty(iRows)
2194-
ResultsValues = double(GlobalData.DataSet(iDS).Results(iResult).ImageGridAmp(:, iTime));
2195-
if ~isempty(GlobalData.DataSet(iDS).Results(iResult).Std)
2196-
Std = double(GlobalData.DataSet(iDS).Results(iResult).Std(:, iTime, :, :));
2192+
% ImageGridAmp = [nSources, nTimes]
2193+
if isnumeric(GlobalData.DataSet(iDS).Results(iResult).ImageGridAmp)
2194+
% Get ImageGridAmp interesting sub-part
2195+
if isempty(iRows)
2196+
ResultsValues = double(GlobalData.DataSet(iDS).Results(iResult).ImageGridAmp(:, iTime));
2197+
if ~isempty(GlobalData.DataSet(iDS).Results(iResult).Std)
2198+
Std = double(GlobalData.DataSet(iDS).Results(iResult).Std(:, iTime, :, :));
2199+
end
2200+
else
2201+
ResultsValues = double(GlobalData.DataSet(iDS).Results(iResult).ImageGridAmp(iRows, iTime));
2202+
if ~isempty(GlobalData.DataSet(iDS).Results(iResult).Std)
2203+
Std = double(GlobalData.DataSet(iDS).Results(iResult).Std(iRows, iTime, :, :));
2204+
end
21972205
end
2198-
else
2199-
ResultsValues = double(GlobalData.DataSet(iDS).Results(iResult).ImageGridAmp(iRows, iTime));
2200-
if ~isempty(GlobalData.DataSet(iDS).Results(iResult).Std)
2201-
Std = double(GlobalData.DataSet(iDS).Results(iResult).Std(iRows, iTime, :, :));
2206+
% ImageGridAmp = {[nSources,a], [a,b], [b, nTimes]}
2207+
elseif iscell(GlobalData.DataSet(iDS).Results(iResult).ImageGridAmp)
2208+
% Get ImageGridAmp interesting sub-part
2209+
ResultsValues = GlobalData.DataSet(iDS).Results(iResult).ImageGridAmp;
2210+
assert(isempty(GlobalData.DataSet(iDS).Results(iResult).Std), 'Storing Std a cell is not supported yet.')
2211+
ResultsValues{end} = ResultsValues{end}(:, iTime);
2212+
2213+
if ~isempty(iRows)
2214+
ResultsValues{1} = ResultsValues{1}(iRows, :);
22022215
end
2216+
2217+
% Compute full results
2218+
ResultsValues = double(bst_multiply_cellmat(ResultsValues));
2219+
% Std should be empty if ImageGridAmp is a cell
2220+
Std = [];
22032221
end
22042222
% === KERNEL ONLY ===
22052223
elseif ~isempty(GlobalData.DataSet(iDS).Results(iResult).ImagingKernel)
@@ -2586,13 +2604,29 @@ function LoadResultsMatrix(iDS, iResult)
25862604
[maxGFP, iMax] = max(GFP);
25872605
% Get the results values at this particular time point
25882606
sources = GetResultsValues(iDS, iResult, [], iMax);
2607+
% Store minimum and maximum of displayed data
2608+
DataMinMax = [min(sources(:)), max(sources(:))];
25892609
% Full results
25902610
else
2591-
% Get the maximum on the full results matrix
2592-
sources = GlobalData.DataSet(iDS).Results(iResult).ImageGridAmp;
2611+
% Get the maximum on the full results matrix (process by time blocks)
2612+
nSamples = GlobalData.DataSet(iDS).Results(iResult).NumberOfSamples;
2613+
if isnumeric(GlobalData.DataSet(iDS).Results(iResult).ImageGridAmp)
2614+
nSources = size(GlobalData.DataSet(iDS).Results(iResult).ImageGridAmp, 1);
2615+
elseif iscell(GlobalData.DataSet(iDS).Results(iResult).ImageGridAmp)
2616+
nSources = size(GlobalData.DataSet(iDS).Results(iResult).ImageGridAmp{1}, 1);
2617+
end
2618+
ProcessOptions = bst_get('ProcessOptions');
2619+
MaxSizeDouble = ProcessOptions.MaxBlockSize;
2620+
blockSize = max(floor(MaxSizeDouble / nSources), 1);
2621+
nBlocks = ceil(nSamples / blockSize);
2622+
DataMinMax = [Inf, -Inf];
2623+
for iBlock = 1 : nBlocks
2624+
iTime = [((iBlock-1) * blockSize + 1) : min(iBlock * blockSize, nSamples)];
2625+
sources = GetResultsValues(iDS, iResult, [], iTime);
2626+
DataMinMax(1) = min(DataMinMax(1), min(sources(:)));
2627+
DataMinMax(2) = max(DataMinMax(2), max(sources(:)));
2628+
end
25932629
end
2594-
% Store minimum and maximum of displayed data
2595-
DataMinMax = [min(sources(:)), max(sources(:))];
25962630
end
25972631

25982632

toolbox/io/in_bst.m

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,17 @@
8989
% FULL RESULTS
9090
if isfield(sMatrix, 'ImageGridAmp') && ~isempty(sMatrix.ImageGridAmp)
9191
iTime = GetTimeIndices(TimeBounds, sMatrix.Time);
92-
sMatrix.ImageGridAmp = sMatrix.ImageGridAmp(:,iTime);
9392
sMatrix.Time = sMatrix.Time(iTime);
93+
% Get results for iTime
94+
if isnumeric(sMatrix.ImageGridAmp)
95+
sMatrix.ImageGridAmp = sMatrix.ImageGridAmp(:,iTime);
96+
elseif iscell(sMatrix.ImageGridAmp)
97+
% ImageGridAmp = {[nSources,a], [a,b], [b, nTimes]}
98+
sMatrix.ImageGridAmp{end} = sMatrix.ImageGridAmp{end}(:, iTime);
99+
if isLoadFull
100+
sMatrix.ImageGridAmp = bst_multiply_cellmat(sMatrix.ImageGridAmp);
101+
end
102+
end
94103
sMatrix.ImagingKernel = [];
95104
matName = 'ImageGridAmp';
96105
% KERNEL ONLY

toolbox/io/in_bst_results.m

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -261,13 +261,8 @@
261261
end
262262
end
263263
% If full results are saved as factor decomposition
264-
elseif isfield(Results,'ImageGridAmp') && iscell(Results.ImageGridAmp)
265-
% ImageGridAmp = ImageGridAmp{1} * ImageGridAmp{2} * ... * ImageGridAmp{N}
266-
tmp = Results.ImageGridAmp{1};
267-
for iDecomposition = 2 : length(Results.ImageGridAmp)
268-
tmp = tmp * Results.ImageGridAmp{iDecomposition};
269-
end
270-
Results.ImageGridAmp = full(tmp);
264+
elseif LoadFull && isfield(Results,'ImageGridAmp') && iscell(Results.ImageGridAmp)
265+
Results.ImageGridAmp = bst_multiply_cellmat(Results.ImageGridAmp);
271266
end
272267

273268

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
function M = bst_multiply_cellmat(X)
2+
% BST_MULTIPLY_CELLMAT: Multiply all the matrices in cell array
3+
%
4+
% USAGE: M = bst_multiply_cellmat(X)
5+
%
6+
% INPUT:
7+
% - X : 1D cell array of 2D matrices to be multiplied
8+
%
9+
% OUTPUT:
10+
% - M : Result of chain matrix multiplication
11+
% M = X{1} * X{2} * ... * X{N}
12+
13+
% @=============================================================================
14+
% This function is part of the Brainstorm software:
15+
% https://neuroimage.usc.edu/brainstorm
16+
%
17+
% Copyright (c) University of Southern California & McGill University
18+
% This software is distributed under the terms of the GNU General Public License
19+
% as published by the Free Software Foundation. Further details on the GPLv3
20+
% license can be found at http://www.gnu.org/copyleft/gpl.html.
21+
%
22+
% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE
23+
% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY
24+
% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
25+
% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY
26+
% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE.
27+
%
28+
% For more information type "brainstorm license" at command prompt.
29+
% =============================================================================@
30+
%
31+
% Authors: Edouard Delaire, 2025
32+
% Raymundo Cassani, 2025
33+
34+
% Do nothing
35+
if ~iscell(X) || ~isvector(X)
36+
M = X;
37+
return
38+
end
39+
40+
nMat = length(X);
41+
% One matrix
42+
if nMat == 1
43+
M = X{1};
44+
return
45+
end
46+
47+
% Check sizes
48+
matSizes = cellfun(@size, X, 'UniformOutput', 0);
49+
if ~all(cellfun(@(x) length(x)==2, matSizes))
50+
error('All matrices in cell array must be 2D')
51+
end
52+
dimDiff = diff([matSizes{1 : +1 : nMat}]);
53+
if ~all(dimDiff(2:2:nMat) == 0)
54+
error('Matrices cannot be multiplied')
55+
end
56+
57+
% Choose direction of association for multiplication
58+
if nMat == 3
59+
% Cost is the number of scalar multiplications
60+
cost1 = matSizes{1}(1) * matSizes{1}(2) * matSizes{2}(2) + matSizes{1}(1) * matSizes{2}(2) * matSizes{3}(2); % (A*B)*C
61+
cost2 = matSizes{2}(2) * matSizes{2}(1) * matSizes{3}(2) + matSizes{1}(1) * matSizes{2}(1) * matSizes{3}(2); % A*(B*C)
62+
fromRight = cost2 < cost1;
63+
else
64+
% Heuristic rule
65+
% Note, the optimal association approach can be obtained computing the cost recursively
66+
fromRight = size(X{end}, 2) < size(X{1}, 1);
67+
end
68+
69+
% Keep useful indices for inner dimensions
70+
if fromRight
71+
for im = nMat : -1 : 2
72+
usefulInnerIx = any(X{im}, 2);
73+
X{im} = X{im}(usefulInnerIx, :);
74+
X{im-1} = X{im-1}(:, usefulInnerIx);
75+
end
76+
else
77+
for im = 1 : nMat - 1
78+
usefulInnerIx = any(X{im}, 1);
79+
X{im} = X{im}(:, usefulInnerIx);
80+
X{im+1} = X{im+1}(usefulInnerIx, :);
81+
end
82+
83+
end
84+
85+
% Multiply matrices
86+
if fromRight
87+
% M = X{1} * ( X{2} * ( X{3} * X{4} ) )
88+
M = X{end};
89+
for iDecomposition = (nMat - 1) : -1 : 1
90+
M = X{iDecomposition} * M;
91+
end
92+
else
93+
% M = ( ( X{1} * X{2} ) * X{3} ) * X{4}
94+
M = X{1};
95+
for iDecomposition = 2 : nMat
96+
M = M * X{iDecomposition};
97+
end
98+
end
99+
100+
% Ensure results are full matrices
101+
M = full(M);
102+
end
103+

toolbox/process/functions/process_extract_scout.m

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -665,7 +665,11 @@
665665
% FULL RESULTS
666666
if isfield(sResults, 'ImageGridAmp') && ~isempty(sResults.ImageGridAmp)
667667
if nargout > 1
668-
matSourceValues = sResults.ImageGridAmp;
668+
if isnumeric(sResults.ImageGridAmp)
669+
matSourceValues = sResults.ImageGridAmp;
670+
elseif iscell(sResults.ImageGridAmp)
671+
matSourceValues = bst_multiply_cellmat(sResults.ImageGridAmp);
672+
end
669673
end
670674
% Drop large data field.
671675
sResults = rmfield(sResults, 'ImageGridAmp');

0 commit comments

Comments
 (0)