7575%
7676% Dot referencing ('.') is not supported.
7777%
78- % sum(mtVar <, nDimension>) is supported, to avoid importing the entire tensor
79- % into memory.
78+ % sum(mtVar <, nDimension, strOutType >) is supported, to avoid importing
79+ % the entire tensor into memory.
8080%
8181% Convenience functions:
8282% SliceFunction: Execute a function on the entire tensor, by slicing it along
144144
145145% Author: Dylan Muir <[email protected] >146146% Created: 19th November, 2010
147+ %
148+ % Thanks to @marcsous (https://github.com/marcsous) for bug reports and
149+ % fixes.
147150
148151classdef MappedTensor < handle
149152 properties (SetAccess = private , GetAccess = private )
175178 methods
176179 %% MappedTensor - CONSTRUCTOR
177180 function [mtVar ] = MappedTensor(varargin )
181+
182+ % - Get a handle to the appropriate shim function (should be done
183+ % before any errors are thrown)
184+ [mtVar .hShimFunc , ...
185+ mtVar .hRepSumFunc , ...
186+ mtVar .hChunkLengthFunc ] = GetMexFunctionHandles ;
187+
178188 % - Filter arguments for properties
179189 vbKeepArg = true(numel(varargin ), 1 );
180190 nArg = 2 ;
208218 otherwise
209219 % - No other properties are supported
210220 error(' MappedTensor:InvalidProperty' , ...
211- ' *** MappedTensor: ''%s'' is not a valid property. Use the '' Class '' keyword to specify the tensor class. ' , varargin{nArg });
221+ ' *** MappedTensor: ''%s'' is not a valid property.' , varargin{nArg });
212222 end
213223 end
214224
233243 % - Should we map a file on disk, or create a temporary file?
234244 if (ischar(varargin{1 }))
235245 % - Open an existing file
236- vnTensorSize = [varargin{2 : end }];
246+ vnTensorSize = double( [varargin{2 : end }]) ;
237247 mtVar.strRealFilename = varargin{1 };
238248 mtVar.bTemporary = false ;
239249
240250 else
241251 % - Create a temporary file
242252 mtVar.bTemporary = true ;
243- vnTensorSize = [varargin{: }];
253+ vnTensorSize = double( [varargin{: }]) ;
244254 end
245255
246256 % - If only one dimension was provided, assume the matrix is
247257 % square (Matlab default semantics)
248258 if (isscalar(vnTensorSize ))
249259 vnTensorSize = vnTensorSize * [1 1 ];
250260 end
251-
261+
262+ % - Validate tensor size argument
263+ try
264+ validateattributes(vnTensorSize , {' numeric' }, {' positive' , ' integer' });
265+ catch
266+ error(' MappedTensor:Arguments' , ...
267+ ' *** MappedTensor: Error: '' vnTensorSize'' must be a positive integer vector.' );
268+ end
269+
252270 % - Make enough space for a temporary tensor
253271 if (mtVar .bTemporary )
254272 mtVar.strRealFilename = create_temp_file(prod(vnTensorSize ) * mtVar .nClassSize + mtVar .nHeaderBytes );
255273 end
256274
257- % - Get a handle to the appropriate shim function
258- [mtVar .hShimFunc , ...
259- mtVar .hRepSumFunc , ...
260- mtVar .hChunkLengthFunc ] = GetMexFunctionHandles ;
261-
262275 % - Open the file
263276 if (isempty(mtVar .strMachineFormat ))
264277 [mtVar .hRealContent , mtVar .strMachineFormat ] = mtVar .hShimFunc(' open' , mtVar .strRealFilename );
279292 ' *** MappedTensor: Error: only '' ieee-be'' and '' ieee-le'' machine formats are supported.' );
280293 end
281294
295+ % - Record the original tensor size, remove trailing unitary dimensions
296+ if (vnTensorSize(end ) == 1 ) && (numel(vnTensorSize ) > 2 )
297+ nLastNonUnitary = max(2 , find(vnTensorSize ~= 1 , 1 , ' last' ));
298+ vnTensorSize = vnTensorSize(1 : nLastNonUnitary );
299+ end
300+
301+ mtVar.vnOriginalSize = vnTensorSize ;
302+
282303 % - Initialise dimension order
283304 mtVar.vnDimensionOrder = 1 : numel(vnTensorSize );
284305
285306 % - Record number of total elements
286307 mtVar.nNumElements = prod(vnTensorSize );
287-
288- % - Record the original tensor size
289- mtVar.vnOriginalSize = vnTensorSize ;
290308 end
291309
292310 % delete - DESTRUCTOR
@@ -475,7 +493,7 @@ function delete(mtVar)
475493 % - Permute input data
476494 tfData = ipermute(tfData , mtVar .vnDimensionOrder );
477495
478- if (~isreal(tfData ))
496+ if (~isreal(tfData )) || (~isreal( mtVar ))
479497 % - Assign to both real and complex parts
480498 mt_write_data(mtVar .hShimFunc , mtVar .hRealContent , subs , mtVar .vnOriginalSize , mtVar .strClass , mtVar .nHeaderBytes , real(tfData ) ./ mtVar .fRealFactor , mtVar .bBigEndian , mtVar .hRepSumFunc , mtVar .hChunkLengthFunc );
481499 mt_write_data(mtVar .hShimFunc , mtVar .hCmplxContent , subs , mtVar .vnOriginalSize , mtVar .strClass , mtVar .nHeaderBytes , imag(tfData ) ./ mtVar .fComplexFactor , mtVar .bBigEndian , mtVar .hRepSumFunc , mtVar .hChunkLengthFunc );
@@ -541,6 +559,18 @@ function delete(mtVar)
541559 end
542560 end
543561
562+ % ndims - METHOD Overloaded ndims function
563+ function [nDim ] = ndims(mtVar , varargin )
564+ % - If varargin contains anything, a cell reference "{}" was attempted
565+ if (~isempty(varargin ))
566+ error(' MappedTensor:cellRefFromNonCell' , ...
567+ ' *** MappedTensor: Cell contents reference from non-cell obejct.' );
568+ end
569+
570+ % - Return the total number of dimensions in the tensor
571+ nDim = length(size(mtVar ));
572+ end
573+
544574 % numel - METHOD Overloaded numel function
545575 function [nNumElem ] = numel(mtVar , varargin )
546576 % - If varargin contains anything, a cell reference "{}" was attempted
@@ -805,34 +835,69 @@ function disp(mtVar)
805835 ' *** MappedTensor: Concatenation is not supported for MappedTensor objects.' );
806836 end
807837
808- %% sum - METHOD Overloaded sum function for usage "sum(mtVar <, dim>)"
838+ %% sum - METHOD Overloaded sum function for usage "sum(mtVar <, dim, outtype >)"
809839 function [tFinalSum ] = sum(mtVar , varargin )
810840 % - Get tensor size
811841 vnTensorSize = size(mtVar );
812842
813- if (exist(' varargin' , ' var' ) && ~isempty(varargin ))
814- % - Check varargin for string parameters and discard
815- vbIsString = cellfun(@ischar , varargin );
816- varargin = varargin(~vbIsString );
817-
818- % - Too many arguments?
819- if (numel(varargin ) > 1 )
820- error(' MappedTensor:sum:InvalidArguments' , ...
821- ' *** MappedTensor/sum: Too many arguments were supplied.' );
843+ % - By default, sum along first non-singleton dimension
844+ DEF_nDim = find(vnTensorSize > 1 , 1 , ' first' );
845+
846+ % - By default, accumulate in a double tensor
847+ DEF_strReturnClass = ' double' ;
848+
849+ % - Check arguments and apply defaults
850+ if (nargin > 3 )
851+ error(' MappedTensor:sum:InvalidArguments' , ...
852+ ' *** MappedTensor/sum: Too many arguments were supplied.' );
853+
854+ elseif (nargin == 3 )
855+
856+ elseif (nargin == 2 )
857+ if (ischar(varargin{1 }))
858+ varargin{2 } = varargin{1 };
859+ varargin{1 } = DEF_nDim ;
860+
861+ else
862+ varargin{2 } = DEF_strReturnClass ;
822863 end
823-
824- % - Was a dimension specified?
825- if (~isnumeric(varargin{1 }) || numel(varargin{1 }) > 1 )
826- error(' MappedTensor:sum:InvalidArguments' , ...
827- ' *** MappedTensor/sum: '' dim'' must be supplied as a scalar number.' );
864+
865+ elseif (nargin == 1 )
866+ varargin{1 } = DEF_nDim ;
867+ varargin{2 } = DEF_strReturnClass ;
868+ end
869+
870+ % - Was a valid dimension specified?
871+ try
872+ validateattributes(varargin{1 }, {' numeric' }, {' positive' , ' integer' , ' scalar' });
873+ catch
874+ error(' MappedTensor:sum:InvalidArguments' , ...
875+ ' *** MappedTensor/sum: '' dim'' must be supplied as a positive scalar number.' );
876+ end
877+ nDim = varargin{1 };
878+
879+ % - Was a valid output argument type specified?
880+ try
881+ strReturnClass = validatestring(lower(varargin{2 }), {' native' , ' double' , ' default' });
882+ catch
883+ error(' MappedTensor:sum:InvalidArguments' , ...
884+ ' *** MappedTensor/sum: '' outtype'' must be one of {'' double'' , '' native'' , '' default'' }.' );
885+ end
886+
887+ % - Get the class for the summation matrix
888+ if (strcmp(strReturnClass , ' native' ))
889+ % - Logicals are always summed in a double tensor
890+ if (islogical(mtVar ))
891+ strOutputClass = ' double' ;
892+ else
893+ strOutputClass = mtVar .strClass ;
828894 end
895+
896+ elseif (strcmp(strReturnClass , ' default' ))
897+ strOutputClass = DEF_strReturnClass ;
829898
830- % - Record dimension to sum along
831- nDim = varargin{1 };
832-
833- else
834- % - By default, sum along first non-singleton dimension
835- nDim = find(vnTensorSize > 1 , 1 , ' first' );
899+ else % if (strcmp(strReturnClass, 'double'))
900+ strOutputClass = strReturnClass ;
836901 end
837902
838903 % -- Sum in chunks to avoid allocating full tensor
@@ -857,7 +922,7 @@ function disp(mtVar)
857922 end
858923
859924 % -- Perform sum by taking dimensions in turn
860- tFinalSum = zeros(vnSumSize );
925+ tFinalSum = zeros(vnSumSize , strOutputClass );
861926
862927 % - Construct referencing structures
863928 sSourceRef = substruct(' ()' , ' :' );
@@ -877,7 +942,7 @@ function disp(mtVar)
877942 % - Call subsasgn, subsref and sum to process data
878943 sSourceRef.subs = cellTheseSourceIndices ;
879944 sDestRef.subs = cellTheseDestIndices ;
880- tFinalSum = subsasgn(tFinalSum , sDestRef , subsref(tFinalSum , sDestRef ) + sum(subsref(mtVar , sSourceRef ), nDim ));
945+ tFinalSum = subsasgn(tFinalSum , sDestRef , subsref(tFinalSum , sDestRef ) + sum(subsref(mtVar , sSourceRef ), nDim , strReturnClass ));
881946
882947 % - Increment first non-max index
883948 nIncrementDim = find(vnSplitIndices <= vnNumDivisions , 1 , ' first' );
@@ -1241,7 +1306,7 @@ function make_complex(mtVar)
12411306 mtVar.strCmplxFilename = create_temp_file(mtVar .nNumElements * mtVar .nClassSize + mtVar .nHeaderBytes );
12421307
12431308 % - open the file
1244- mtVar.hCmplxContent = mtVar .hShimFunc(' open' , mtVar .strCmplxFilename );
1309+ mtVar.hCmplxContent = mtVar .hShimFunc(' open' , mtVar .strCmplxFilename , mtVar . strMachineFormat );
12451310
12461311 % - record that the tensor has a complex part
12471312 mtVar.bIsComplex = true ;
@@ -1315,12 +1380,21 @@ function make_complex(mtVar)
13151380 % - Get the name of a temporary file
13161381 strFilename = tempname ;
13171382
1318- % - Create the file
1319- hFile = fopen(strFilename , ' w+' );
1320-
1321- % - Allocate enough space
1322- fwrite(hFile , 0 , ' uint8' , nNumEntries - 1 );
1323- fclose(hFile );
1383+ % - Attempt fast allocation on some platforms
1384+ if (ispc )
1385+ [bFailed , ~ ] = system(sprintf(' fsutil file createnew %s %i ' , strFilename , nNumEntries ));
1386+ elseif (ismac || isunix )
1387+ [bFailed , ~ ] = system(sprintf(' fallocate -l %i %s ' , nNumEntries , strFilename ));
1388+ else
1389+ bFailed = true ;
1390+ end
1391+
1392+ % - Slow fallback -- use Matlab to write zero data directly
1393+ if (bFailed )
1394+ hFile = fopen(strFilename , ' w+' );
1395+ fwrite(hFile , 0 , ' uint8' , nNumEntries - 1 );
1396+ fclose(hFile );
1397+ end
13241398end
13251399
13261400function [nBytes , strStorageClass ] = ClassSize(strClass )
@@ -1587,7 +1661,7 @@ function isvalidsubscript(oRefs)
15871661
15881662 else
15891663 % - Test for normal indexing
1590- validateattributes(oRefs , {' single ' , ' double ' }, {' integer' , ' real' , ' positive' });
1664+ validateattributes(oRefs , {' numeric ' }, {' integer' , ' real' , ' positive' });
15911665 end
15921666
15931667 catch
@@ -1720,8 +1794,47 @@ function mt_write_data_chunks(hDataFile, mnFileChunkIndices, vnUniqueDataIndices
17201794 end
17211795end
17221796
1797+ % mt_read_all - FUNCTION Read the entire stack
1798+ function [tData ] = mt_read_all(hDataFile , vnTensorSize , strClass , nHeaderBytes , ~)
1799+ % - Allocate data
1800+ [~ , strStorageClass ] = ClassSize(strClass );
1801+ % tData = zeros(vnTensorSize, strStorageClass);
1802+
1803+ % - Seek file to beginning of data
1804+ fseek(hDataFile , nHeaderBytes , ' bof' );
1805+
1806+ % - Normal forward read
1807+ tData = fread(hDataFile , prod(vnTensorSize ), [strStorageClass ' =>' strClass ], 0 );
1808+ end
1809+
1810+ % mt_write_all - FUNCTION Write the entire stack
1811+ function mt_write_all(hDataFile , vnTensorSize , strClass , nHeaderBytes , tData , ~)
1812+
1813+ % - Do we need to replicate the data?
1814+ if (isscalar(tData ) && prod(vnTensorSize ) > 1 )
1815+ tData = repmat(tData , prod(vnTensorSize ), 1 );
1816+
1817+ elseif (numel(tData ) ~= prod(vnTensorSize ))
1818+ % - The was a mismatch in the sizes of the left and right sides
1819+ error(' MappedTensor:index_assign_element_count_mismatch' , ...
1820+ ' *** MappedTensor: In an assignment A(I) = B, the number of elements in B and I must be the same.' );
1821+ end
1822+
1823+ % - Get storage class
1824+ [~ , strStorageClass ] = ClassSize(strClass );
1825+
1826+ % - Seek file to beginning of data
1827+ fseek(hDataFile , nHeaderBytes , ' bof' );
1828+
1829+ % - Normal forward write of data
1830+ fwrite(hDataFile , tData , strStorageClass , 0 );
1831+ end
1832+
17231833% ConvertColonCheckLims - FUNCTION Convert colon referencing to subscript indices; check index limits
17241834function [vnLinearIndices , vnDataSize ] = ConvertColonsCheckLims(cRefs , vnLims , hRepSumFunc )
1835+ % - Fill trailing referenced dimension limits
1836+ vnLims(end + 1 : numel(cRefs )) = 1 ;
1837+
17251838 % - Handle linear indexing
17261839 if (numel(cRefs ) == 1 )
17271840 vnLims = prod(vnLims );
@@ -1749,8 +1862,8 @@ function mt_write_data_chunks(hDataFile, mnFileChunkIndices, vnUniqueDataIndices
17491862 ' *** MappedTensor: Index exceeds matrix dimensions.' );
17501863
17511864 else
1752- % - This dimension was ok
1753- cCheckedRefs{nRefDim } = cRefs{nRefDim };
1865+ % - This dimension was ok, convert to double
1866+ cCheckedRefs{nRefDim } = double( cRefs{nRefDim }) ;
17541867 end
17551868 end
17561869
@@ -1768,6 +1881,9 @@ function mt_write_data_chunks(hDataFile, mnFileChunkIndices, vnUniqueDataIndices
17681881 % - Find colon references
17691882 vbIsColon = cellfun(@iscolon , cRefs );
17701883
1884+ % - Fill trailing referenced dimension limits
1885+ vnLims(end + 1 : numel(cRefs )) = 1 ;
1886+
17711887 % - Catch "reference whole stack" condition
17721888 if (all(vbIsColon ))
17731889 vnLinearIndices = 1 : prod(vnLims );
@@ -1895,6 +2011,12 @@ function mt_write_data_chunks(hDataFile, mnFileChunkIndices, vnUniqueDataIndices
18952011
18962012 case ' write_chunks'
18972013 mt_write_data_chunks(varargin{1 : 7 });
2014+
2015+ case ' read_all'
2016+ varargout{1 } = mt_read_all(varargin{1 : 5 });
2017+
2018+ case ' write_all'
2019+ varargout{1 } = mt_write_all(varargin{1 : 6 });
18982020 end
18992021end
19002022
0 commit comments