diff --git a/+tests/+unit/dataPipeTest.m b/+tests/+unit/dataPipeTest.m index 90352829..06ec2c94 100644 --- a/+tests/+unit/dataPipeTest.m +++ b/+tests/+unit/dataPipeTest.m @@ -1,136 +1,260 @@ function tests = dataPipeTest() -tests = functiontests(localfunctions); + tests = functiontests(localfunctions); end function setupOnce(testCase) -rootPath = fullfile(fileparts(mfilename('fullpath')), '..', '..'); -testCase.applyFixture(matlab.unittest.fixtures.PathFixture(rootPath)); + rootPath = fullfile(fileparts(mfilename('fullpath')), '..', '..'); + testCase.applyFixture(matlab.unittest.fixtures.PathFixture(rootPath)); end function setup(testCase) -testCase.applyFixture(matlab.unittest.fixtures.WorkingFolderFixture); -generateCore('savedir', '.'); -rehash(); + testCase.applyFixture(matlab.unittest.fixtures.WorkingFolderFixture); + generateCore('savedir', '.'); + rehash(); end -function testIndex(testCase) -filename = 'testIndexing.h5'; -name = '/test_data'; - -data = rand(100, 100, 100); -Pipe = types.untyped.DataPipe('data', data); - -testCase.verifyEqual(Pipe(:), data(:)); -testCase.verifyEqual(Pipe(:,:,1), data(:,:,1)); - -fid = H5F.create(filename); -Pipe.export(fid, name, {}); % bind the pipe. -H5F.close(fid); - -testCase.verifyEqual(Pipe(:), data(:)); -testCase.verifyEqual(Pipe(:,:,1), data(:,:,1)); -end - -function testAppend(testCase) -filename = 'testIterativeWrite.h5'; - -Pipe = types.untyped.DataPipe(... - 'maxSize', [10 13 15],... - 'axis', 3,... - 'chunkSize', [10 13 1],... - 'dataType', 'uint8',... - 'compressionLevel', 5); - -OneDimensionPipe = types.untyped.DataPipe('maxSize', Inf, 'data', [7, 8, 9]); - -%% create test file -fid = H5F.create(filename); - -initialData = createData(Pipe.dataType, [10 13 10]); -Pipe.internal.data = initialData; -Pipe.export(fid, '/test_data', {}); % bind -OneDimensionPipe.export(fid, '/test_one_dim_data', {}); - -H5F.close(fid); - -%% append data -totalLength = 3; -appendData = zeros([10 13 totalLength], Pipe.dataType); -for i = 1:totalLength - appendData(:,:,i) = createData(Pipe.dataType, Pipe.chunkSize); - Pipe.append(appendData(:,:,i)); +function testInit(testCase) + import types.untyped.datapipe.*; + + warnDebugId = 'NWB:DataPipeTest:Debug'; + warning('off', warnDebugId); + warning(warnDebugId, ''); + + %% extra data type + data = rand(100, 1); + types.untyped.DataPipe('data', data, 'dataType', 'double'); + [~,lastId] = lastwarn(); + testCase.verifyEqual(lastId, 'NWB:DataPipe:RedundantDataType'); + + warning(warnDebugId, ''); + + %% compressionLevel and hasShuffle ignored if filters is provided + pipe = types.untyped.DataPipe('data', data ... + , 'compressionLevel', 3 ... + , 'hasShuffle', true ... + , 'filters', [properties.Compression(4)]); + [~,lastId] = lastwarn(); + testCase.verifyEqual(lastId, 'NWB:DataPipe:FilterOverride'); + testCase.verifyEqual(pipe.compressionLevel, 4); + testCase.verifyTrue(~pipe.hasShuffle); + pipe.compressionLevel = 2; + testCase.verifyEqual(pipe.compressionLevel, 2); + pipe.hasShuffle = true; + testCase.verifyTrue(pipe.hasShuffle); + + warning(warnDebugId, ''); + + %% extraneous properties from file + filename = 'testInit.h5'; + datasetName = '/test_data'; + fid = H5F.create(filename); + pipe.export(fid, datasetName, {}); + H5F.close(fid); + + pipe = types.untyped.DataPipe('filename', filename, 'path', datasetName, 'dataType', 'double'); + [~,lastId] = lastwarn(); + testCase.verifyEqual(lastId, 'NWB:DataPipe:UnusedArguments'); + testCase.verifyEqual(pipe.compressionLevel, 2); + testCase.verifyTrue(pipe.hasShuffle); + + % cleanup + warning('on', warnDebugId); end -for i = 1:totalLength - OneDimensionPipe.append(rand()); +function testIndex(testCase) + filename = 'testIndexing.h5'; + name = '/test_data'; + + data = rand(100, 100, 100); + Pipe = types.untyped.DataPipe('data', data); + + testCase.verifyEqual(Pipe(:), data(:)); + testCase.verifyEqual(Pipe(:,:,1), data(:,:,1)); + + fid = H5F.create(filename); + Pipe.export(fid, name, {}); % bind the pipe. + H5F.close(fid); + + testCase.verifyEqual(Pipe(:), data(:)); + testCase.verifyEqual(Pipe(:,:,1), data(:,:,1)); end -%% verify data -Pipe = types.untyped.DataPipe('filename', filename, 'path', '/test_data'); -readData = Pipe.load(); -testCase.verifyEqual(readData(:,:,1:10), initialData); -testCase.verifyEqual(readData(:,:,11:end), appendData); - -OneDimensionPipe = types.untyped.DataPipe('filename', filename, 'path', '/test_one_dim_data'); -readData = OneDimensionPipe.load(); -testCase.verifyTrue(isvector(readData)); -testCase.verifyEqual(length(readData), 6); -testCase.verifyEqual(readData(1:3), [7, 8, 9] .'); +function testAppend(testCase) + filename = 'testIterativeWrite.h5'; + + Pipe = types.untyped.DataPipe(... + 'maxSize', [10 13 15],... + 'axis', 3,... + 'chunkSize', [10 13 1],... + 'dataType', 'uint8',... + 'compressionLevel', 5); + + OneDimensionPipe = types.untyped.DataPipe('maxSize', Inf, 'data', [7, 8, 9]); + + %% create test file + fid = H5F.create(filename); + + initialData = createData(Pipe.dataType, [10 13 10]); + Pipe.internal.data = initialData; + Pipe.export(fid, '/test_data', {}); % bind + OneDimensionPipe.export(fid, '/test_one_dim_data', {}); + + H5F.close(fid); + + %% append data + totalLength = 3; + appendData = zeros([10 13 totalLength], Pipe.dataType); + for i = 1:totalLength + appendData(:,:,i) = createData(Pipe.dataType, Pipe.chunkSize); + Pipe.append(appendData(:,:,i)); + end + + for i = 1:totalLength + OneDimensionPipe.append(rand()); + end + + %% verify data + Pipe = types.untyped.DataPipe('filename', filename, 'path', '/test_data'); + readData = Pipe.load(); + testCase.verifyEqual(readData(:,:,1:10), initialData); + testCase.verifyEqual(readData(:,:,11:end), appendData); + + OneDimensionPipe = types.untyped.DataPipe('filename', filename, 'path', '/test_one_dim_data'); + readData = OneDimensionPipe.load(); + testCase.verifyTrue(isvector(readData)); + testCase.verifyEqual(length(readData), 6); + testCase.verifyEqual(readData(1:3), [7, 8, 9] .'); end function testExternalFilters(testCase) -import types.untyped.datapipe.dynamic.Filter; -import types.untyped.datapipe.properties.DynamicFilter; -import types.untyped.datapipe.properties.Shuffle; - -testCase.assumeTrue(logical(H5Z.filter_avail(uint32(Filter.LZ4)))); - -filename = 'testExternalWrite.h5'; - -Pipe = types.untyped.DataPipe(... - 'maxSize', [10 13 15],... - 'axis', 3,... - 'chunkSize', [10 13 1],... - 'dataType', 'uint8',... - 'filters', [Shuffle() DynamicFilter(Filter.LZ4)]); - -OneDimensionPipe = types.untyped.DataPipe('maxSize', Inf, 'data', [7, 8, 9]); - -%% create test file -fid = H5F.create(filename); - -initialData = createData(Pipe.dataType, [10 13 10]); -Pipe.internal.data = initialData; -Pipe.export(fid, '/test_data', {}); % bind -OneDimensionPipe.export(fid, '/test_one_dim_data', {}); - -H5F.close(fid); - -%% append data -totalLength = 3; -appendData = zeros([10 13 totalLength], Pipe.dataType); -for i = 1:totalLength - appendData(:,:,i) = createData(Pipe.dataType, Pipe.chunkSize); - Pipe.append(appendData(:,:,i)); + import types.untyped.datapipe.dynamic.Filter; + import types.untyped.datapipe.properties.DynamicFilter; + import types.untyped.datapipe.properties.Shuffle; + + testCase.assumeTrue(logical(H5Z.filter_avail(uint32(Filter.LZ4)))); + + filename = 'testExternalWrite.h5'; + + Pipe = types.untyped.DataPipe(... + 'maxSize', [10 13 15],... + 'axis', 3,... + 'chunkSize', [10 13 1],... + 'dataType', 'uint8',... + 'filters', [Shuffle() DynamicFilter(Filter.LZ4)]); + + OneDimensionPipe = types.untyped.DataPipe('maxSize', Inf, 'data', [7, 8, 9]); + + %% create test file + fid = H5F.create(filename); + + initialData = createData(Pipe.dataType, [10 13 10]); + Pipe.internal.data = initialData; + Pipe.export(fid, '/test_data', {}); % bind + OneDimensionPipe.export(fid, '/test_one_dim_data', {}); + + H5F.close(fid); + + %% append data + totalLength = 3; + appendData = zeros([10 13 totalLength], Pipe.dataType); + for i = 1:totalLength + appendData(:,:,i) = createData(Pipe.dataType, Pipe.chunkSize); + Pipe.append(appendData(:,:,i)); + end + + for i = 1:totalLength + OneDimensionPipe.append(rand()); + end + + %% verify data + Pipe = types.untyped.DataPipe('filename', filename, 'path', '/test_data'); + readData = Pipe.load(); + testCase.verifyEqual(readData(:,:,1:10), initialData); + testCase.verifyEqual(readData(:,:,11:end), appendData); + + OneDimensionPipe = types.untyped.DataPipe('filename', filename, 'path', '/test_one_dim_data'); + readData = OneDimensionPipe.load(); + testCase.verifyTrue(isvector(readData)); + testCase.verifyEqual(length(readData), 6); + testCase.verifyEqual(readData(1:3), [7, 8, 9] .'); end -for i = 1:totalLength - OneDimensionPipe.append(rand()); -end - -%% verify data -Pipe = types.untyped.DataPipe('filename', filename, 'path', '/test_data'); -readData = Pipe.load(); -testCase.verifyEqual(readData(:,:,1:10), initialData); -testCase.verifyEqual(readData(:,:,11:end), appendData); - -OneDimensionPipe = types.untyped.DataPipe('filename', filename, 'path', '/test_one_dim_data'); -readData = OneDimensionPipe.load(); -testCase.verifyTrue(isvector(readData)); -testCase.verifyEqual(length(readData), 6); -testCase.verifyEqual(readData(1:3), [7, 8, 9] .'); +function testBoundPipe(testCase) + import types.untyped.*; + filename = 'bound.h5'; + dsName = '/test_data'; + debugId = 'NWB:DataPipe:Debug'; + warning('off', debugId); + + %% full pipe case + fullpipe = DataPipe('data', rand(100, 1)); + + fid = H5F.create(filename); + fullpipe.export(fid, dsName, {}); + H5F.close(fid); + DataPipe('filename', filename, 'path', dsName); + delete(filename); + + %% multi-axis case + data = rand(100, 1); + maxSize = [200, 2]; + multipipe = DataPipe('data', data, 'maxSize', maxSize); + fid = H5F.create(filename); + try + % this should be impossible normally. + multipipe.export(fid, dsName, {}); + catch ME + testCase.verifyEqual(ME.identifier, 'NWB:BoundPipe:InvalidSize'); + end + H5F.close(fid); + delete(filename); + + fid = H5F.create(filename); + rank = length(maxSize); + dcpl = H5P.create('H5P_DATASET_CREATE'); + H5P.set_chunk(dcpl, datapipe.guessChunkSize(class(data), maxSize)); + did = H5D.create( ... + fid, dsName ... + , io.getBaseType(class(data)) ... + , H5S.create_simple(rank, fliplr(size(data)), fliplr(maxSize)) ... + , 'H5P_DEFAULT', dcpl, 'H5P_DEFAULT'); + H5D.write(did, 'H5ML_DEFAULT', 'H5S_ALL', 'H5S_ALL', 'H5P_DEFAULT', data); + H5D.close(did); + H5F.close(fid); + + warning(debugId, ''); + multipipe = DataPipe('filename', filename, 'path', dsName); + [~,lastId] = lastwarn(); + testCase.verifyEqual(lastId, 'NWB:BoundPipe:InvalidPipeShape'); + + try + multipipe.append(rand(10, 2, 10)); + catch ME + testCase.verifyEqual(ME.identifier, 'NWB:BoundPipe:InvalidDataShape'); + end + + delete(filename); + + %% not chunked behavior + fid = H5F.create(filename); + did = H5D.create( ... + fid, dsName ... + , io.getBaseType(class(data)) ... + , H5S.create_simple(rank, fliplr(size(data)), fliplr(size(data))) ... + , 'H5P_DEFAULT', 'H5P_DEFAULT', 'H5P_DEFAULT'); + H5D.write(did, 'H5ML_DEFAULT', 'H5S_ALL', 'H5S_ALL', 'H5P_DEFAULT', data); + H5D.close(did); + H5F.close(fid); + warning(debugId, ''); + nochunk = DataPipe('filename', filename, 'path', dsName); + [~,lastId] = lastwarn(); + testCase.verifyEqual(lastId, 'NWB:BoundPipe:NotChunked'); + nochunk.load(); % test still loadable. + + %% cleanup + warning('on', debugId); end function data = createData(dataType, size) -data = randi(intmax(dataType), size, dataType); + data = randi(intmax(dataType), size, dataType); end diff --git a/+types/+untyped/+datapipe/BoundPipe.m b/+types/+untyped/+datapipe/BoundPipe.m index 7b6730bd..4539b692 100644 --- a/+types/+untyped/+datapipe/BoundPipe.m +++ b/+types/+untyped/+datapipe/BoundPipe.m @@ -47,14 +47,15 @@ strjoin(arrayfun(@num2str, axis, 'UniformOutput', false), ', ')); formattedMaxSize = sprintf('[%s]', ... strjoin(arrayfun(@num2str, max_size, 'UniformOutput', false), ', ')); - warning('NWB:Untyped:DataPipe:BoundPipe:InvalidPipeShape', ... - ['Multiple possible axes for data pipe detected.' newline ... - ' Dimensions %s are all strictly smaller in size than the maximum size of %s.' newline ... - ' All non-appendable dimensions should fill out the maximum size of their dimension.' newline ... - ' Continuing with axis %d'], formattedAxes, formattedMaxSize, axis(1)); + warning('NWB:BoundPipe:InvalidPipeShape' ... + , [ ... + 'Multiple data dimensions %s are smaller than the dataset''s ' ... + 'maximum dimensions (%s). Attempting to append to this ' ... + 'dataset will most likely produce errors.'] ... + , formattedAxes, formattedMaxSize); axis = axis(1); end - + obj.config.axis = axis; obj.config.offset = current_size(obj.config.axis); tid = H5D.get_type(did); @@ -65,9 +66,13 @@ end pid = H5D.get_create_plist(did); - assert(Chunking.isInDcpl(pid), ['Cannot access a bound pipe if '... - 'the dataset is not chunked.']); - obj.pipeProperties{end+1} = Chunking.fromDcpl(pid); + if Chunking.isInDcpl(pid) + obj.pipeProperties{end+1} = Chunking.fromDcpl(pid); + else + warning('NWB:BoundPipe:NotChunked' ... + , ['Bound pipe is not chunked. Only read access is allowed.\n ' ... + 'Attempting to append to this pipe may cause errors.']); + end if Compression.isInDcpl(pid) obj.pipeProperties{end+1} = Compression.fromDcpl(pid); @@ -150,7 +155,7 @@ end function expandDataset(obj, data_size) - errorId = 'NWB:Types:Untyped:DataPipe:BoundPipe:InvalidSize'; + errorId = 'NWB:BoundPipe:InvalidSize'; did = obj.getDataset('H5F_ACC_RDWR'); sid = H5D.get_space(did); [~, h5_dims, ~] = H5S.get_simple_extent_dims(sid); @@ -164,8 +169,7 @@ function expandDataset(obj, data_size) errorId,... 'Data size cannot exceed maximum allocated size.'); sizes_ind = 1:length(obj.config.maxSize); - non_axes_mask = sizes_ind ~= obj.config.axis... - & ~isinf(obj.config.maxSize); + non_axes_mask = sizes_ind ~= obj.config.axis & ~isinf(obj.config.maxSize); assert(all(... obj.config.maxSize(non_axes_mask) == new_extents(non_axes_mask)),... errorId,... @@ -179,23 +183,28 @@ function expandDataset(obj, data_size) function append(obj, data) rank = length(obj.config.maxSize); data_size = size(data); + data_rank = length(data_size); - if 1 == rank + if 1 == rank && 2 == data_rank + assert(isvector(data) ... + , 'NWB:BoundPipe:InvalidDataShape' ... + , ['data is a non-vector matrix but the pipe is one-dimensional. ' ... + 'Attempting to append will drop data!']); + data_rank = 1; data_size = max(data_size); - elseif length(data_size) < rank + elseif data_rank < rank new_coords = ones(1, rank); - new_coords(1:length(data_size)) = data_size; + new_coords(1:data_rank) = data_size; data_size = new_coords; - elseif length(data_size) > rank - if ~all(data_size(rank+1:end) == 1) - warning('NWB:Types:Untyped:DataPipe:InvalidRank',... - ['Expected rank %d not expected for data of size %s. '... - 'Data may be lost on write.'],... - rank, mat2str(size(data_size))); - end - data_size = data_size(1:rank); + data_rank = rank; end + assert(data_rank <= rank ... + , 'NWB:BoundPipe:InvalidDataShape' ... + , ['Pipe rank (%d) < provided data rank (%d). ' ... + 'Attempting to append will drop data!'] ... + , rank, data_rank); + obj.expandDataset(data_size); sid = obj.makeSelection(data_size); @@ -228,7 +237,7 @@ function append(obj, data) end function setPipeProperty(~, ~) - error('NWB:Untyped:DataPipe:BoundPipe:CannotSetPipeProperty',... + error('NWB:BoundPipe:CannotSetPipeProperty',... 'Bound pipes cannot override their pipe properties.'); end @@ -243,7 +252,7 @@ function setPipeProperty(~, ~) end function removePipeProperty(~, ~) - error('NWB:Untyped:DataPipe:BoundPipe:CannotSetPipeProperty',... + error('NWB:BoundPipe:CannotSetPipeProperty',... 'Bound pipes cannot remove pipe properties.'); end diff --git a/+types/+untyped/DataPipe.m b/+types/+untyped/DataPipe.m index 02db2d45..dcc41f52 100644 --- a/+types/+untyped/DataPipe.m +++ b/+types/+untyped/DataPipe.m @@ -41,16 +41,16 @@ % of the dataset within that file. These arguments cannot be used % with any of the above arguments, which are for setting up a new % DataPipe. - + properties (SetAccess = private) internal; filters; end - + properties (Dependent, SetAccess = private) isBound; end - + properties (Dependent) axis; offset; @@ -59,7 +59,7 @@ compressionLevel; hasShuffle; end - + methods %% Lifecycle function obj = DataPipe(varargin) @@ -68,7 +68,7 @@ import types.untyped.datapipe.Configuration; import types.untyped.datapipe.properties.*; import types.untyped.datapipe.guessChunkSize; - + p = inputParser; p.addParameter('maxSize', []); p.addParameter('axis', 1, @(x) isnumeric(x) && isscalar(x) && x > 0); @@ -90,7 +90,7 @@ @(x) isa(x, 'types.untyped.datapipe.Property')); p.KeepUnmatched = true; p.parse(varargin{:}); - + hasFilename = ~isempty(p.Results.filename); hasPath = ~isempty(p.Results.path); assert(~xor(hasFilename, hasPath),... @@ -100,34 +100,27 @@ 'specified.']); if hasFilename && hasPath obj.internal = BoundPipe(p.Results.filename, p.Results.path); - dependentProperties = {... - 'offset',... - 'chunkSize',... - 'compressionLevel',... - 'dataType',... - 'data',... - }; - - extraProperties = intersect(... - dependentProperties,... - fieldnames(p.Unmatched)... - ); - if ~isempty(extraProperties) - formattedProperties = cell(size(dependentProperties)); - for i = 1:length(dependentProperties) - formattedProperties{i} =... - [' ', dependentProperties{i}]; + meta = metaclass(obj.internal); + proplist = meta.PropertyList; + propnames = {proplist.Name}; + dependents = setdiff(propnames([proplist.Dependent]), {'filename', 'path'}); + + extras = setdiff(intersect(p.Parameters, dependents), p.UsingDefaults); + if ~isempty(extras) + formatted = cell(size(dependents)); + for i = 1:length(dependents) + formatted{i} = [' ', dependents{i}]; end warning('NWB:DataPipe:UnusedArguments',... ['Other keyword arguments were added along with a valid '... 'filename and path. Since the filename and path are valid, the '... 'following extra properties will be superceded by the '... 'configuration on file:\n%s'],... - strjoin(formattedProperties, newline)); + strjoin(formatted, newline)); end return; end - + if isempty(p.Results.maxSize) assert(~isempty(p.Results.data),... 'NWB:DataPipe:MissingArguments',... @@ -140,7 +133,7 @@ config = Configuration(maxSize); config.axis = p.Results.axis; config.offset = p.Results.offset; - + if isempty(p.Results.data) config.dataType = p.Results.dataType; else @@ -151,7 +144,7 @@ end config.dataType = class(p.Results.data); end - + obj.internal = BlueprintPipe(config); if isempty(p.Results.chunkSize) chunkSize = guessChunkSize(config.dataType, config.maxSize); @@ -159,12 +152,13 @@ chunkSize = p.Results.chunkSize; end obj.internal.setPipeProperties(Chunking(chunkSize)); - + hasFilters = ~isempty(p.Results.filters); usingHasCompressionLevel = ~any(strcmp(p.UsingDefaults, 'compressionLevel')); usingHasShuffle = ~any(strcmp(p.UsingDefaults, 'hasShuffle')); if hasFilters && (usingHasCompressionLevel || usingHasShuffle) - warning(['`filters` keyword argument detected. This will ' ... + warning('NWB:DataPipe:FilterOverride' ... + , ['`filters` keyword argument detected. This will ' ... 'override `compressionLevel` and `hasShuffle` keyword ' ... 'arguments. If you wish to use either `compressionLevel` ' ... 'or `hasShuffle`, please add their respective filter ' ... @@ -172,7 +166,7 @@ 'and `types.untyped.datapipe.properties.Shuffle` to the ' ... '`filters` properties array.']); end - + if hasFilters filterCell = num2cell(p.Results.filters); obj.internal.setPipeProperties(filterCell{:}); @@ -181,54 +175,54 @@ obj.internal.setPipeProperties(Compression(... p.Results.compressionLevel)); end - + if logical(p.Results.hasShuffle) obj.internal.setPipeProperties(Shuffle()); end end - + obj.internal.data = p.Results.data; end - + %% SET/GET function tf = get.isBound(obj) tf = isa(obj.internal, 'types.untyped.datapipe.BoundPipe'); end - + function val = get.axis(obj) val = obj.internal.axis; end - + function set.axis(obj, val) obj.internal.axis = val; end - + function val = get.offset(obj) val = obj.internal.offset; end - + function set.offset(obj, val) obj.internal.offset = val; end - + function val = get.dataType(obj) val = obj.internal.dataType; end - + function set.dataType(obj, val) obj.internal.dataType = val; end - + function val = get.chunkSize(obj) val = obj.internal.getPipeProperty(... 'types.untyped.datapipe.properties.Chunking').chunkSize; end - + function set.chunkSize(obj, val) import types.untyped.datapipe.properties.Chunking; obj.internal.setPipeProperty(Chunking(val)); end - + function val = get.compressionLevel(obj) compressionClass = 'types.untyped.datapipe.properties.Compression'; val = -1; @@ -236,7 +230,7 @@ val = obj.internal.getPipeProperty(compressionClass).level; end end - + function set.compressionLevel(obj, val) import types.untyped.datapipe.properties.Compression; validateattributes(val, {'numeric'}, {'scalar'}, 1); @@ -249,12 +243,12 @@ obj.internal.setPipeProperty(Compression(val)); end end - + function tf = get.hasShuffle(obj) tf = obj.internal.hasPipeProperty(... 'types.untyped.datapipe.properties.Shuffle'); end - + function set.hasShuffle(obj, tf) import types.untyped.datapipe.properties.Shuffle; if tf @@ -264,20 +258,20 @@ 'types.untyped.datapipe.properties.Shuffle'); end end - + %% API function data = load(obj, varargin) data = obj.internal.load(varargin{:}); end - + function data = append(obj, data) obj.internal.append(data); end - + function refs = export(obj, fid, fullpath, refs) obj.internal = obj.internal.write(fid, fullpath); end - + %% Display function sz = size(obj, varargin) if isa(obj.internal, 'types.untyped.datapipe.BoundPipe') @@ -289,7 +283,7 @@ 'have a handled size() method.'], class(obj.internal)); end end - + %% Subsref function B = subsref(obj, S) CurrentSubRef = S(1); @@ -297,7 +291,7 @@ B = builtin('subsref', obj, S); return; end - + if isa(obj.internal, 'types.untyped.datapipe.BoundPipe') data = obj.internal.stub(CurrentSubRef.subs{:}); elseif isa(obj.internal, 'types.untyped.datapipe.BlueprintPipe')