Skip to content

Commit

Permalink
Merge branch 'master' into 597-nwb-to-table-bug
Browse files Browse the repository at this point in the history
  • Loading branch information
ehennestad authored Sep 30, 2024
2 parents 79e76a1 + c4bb19c commit 7787ff2
Show file tree
Hide file tree
Showing 12 changed files with 705 additions and 382 deletions.
19 changes: 17 additions & 2 deletions +file/fillExport.m
Original file line number Diff line number Diff line change
Expand Up @@ -235,15 +235,30 @@
propertyChecks{end+1} = sprintf(['~isempty(%1$s) ' ...
'&& ~isa(%1$s, ''types.untyped.SoftLink'') ' ...
'&& ~isa(%1$s, ''types.untyped.ExternalLink'')'], propertyReference);

% Properties that are required and dependent will not be exported
% if the property they depend on are unset (empty). Ensure such cases
% are warned against if they occur.
if prop.required && ~prop.readonly
warnIfNotExportedString = sprintf('obj.warnIfPropertyAttributeNotExported(''%s'', ''%s'', fullpath)', name, depPropname);
warningNeededCheck = sprintf('isempty(obj.%s) && ~isempty(obj.%s)', depPropname, name);
end
end

if ~prop.required
propertyChecks{end+1} = ['~isempty(obj.' name ')'];
end

if ~isempty(propertyChecks)
dataExportString = sprintf('if %s\n%s\nend' ...
dataExportString = sprintf('if %s\n%s' ...
, strjoin(propertyChecks, ' && '), file.addSpaces(dataExportString, 4) ...
);
if prop.required && ~prop.readonly
dataExportString = sprintf('%s\nelseif %s\n%s\nend' ...
, dataExportString, warningNeededCheck, file.addSpaces(warnIfNotExportedString, 4) ...
);
else
dataExportString = sprintf('%s\nend', dataExportString);
end
end
end
end
72 changes: 72 additions & 0 deletions +tests/+unit/nwbExportTest.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
classdef nwbExportTest < matlab.unittest.TestCase

properties
NwbObject
OutputFolder = "out"
end

methods (TestClassSetup)
function setupClass(testCase)
% Get the root path of the matnwb repository
rootPath = misc.getMatnwbDir();

% Use a fixture to add the folder to the search path
testCase.applyFixture(matlab.unittest.fixtures.PathFixture(rootPath));

% Use a fixture to create a temporary working directory
testCase.applyFixture(matlab.unittest.fixtures.WorkingFolderFixture);
generateCore('savedir', '.');
end
end

methods (TestMethodSetup)
function setupMethod(testCase)
testCase.NwbObject = testCase.initNwbFile();

if isfolder( testCase.OutputFolder )
rmdir(testCase.OutputFolder, "s")
end
mkdir(testCase.OutputFolder)
end
end

methods (Test)
function testExportDependentAttributeWithMissingParentA(testCase)
testCase.NwbObject.general_source_script_file_name = 'my_test_script.m';
nwbFilePath = fullfile(testCase.OutputFolder, 'test_part1.nwb');
testCase.verifyWarning(@(f, fn) nwbExport(testCase.NwbObject, nwbFilePath), 'NWB:DependentAttributeNotExported')

% Add value for dataset which attribute depends on and export again
testCase.NwbObject.general_source_script = 'my test';
nwbFilePath = fullfile(testCase.OutputFolder, 'test_part2.nwb');
testCase.verifyWarningFree(@(f, fn) nwbExport(testCase.NwbObject, nwbFilePath))
end

function testExportDependentAttributeWithMissingParentB(testCase)
time_series = types.core.TimeSeries( ...
'data', linspace(0, 0.4, 50), ...
'starting_time_rate', 10.0, ...
'description', 'a test series', ...
'data_unit', 'n/a' ...
);

testCase.NwbObject.acquisition.set('time_series', time_series);
nwbFilePath = fullfile(testCase.OutputFolder, 'test_part1.nwb');
testCase.verifyWarning(@(f, fn) nwbExport(testCase.NwbObject, nwbFilePath), 'NWB:DependentAttributeNotExported')

% Add value for dataset which attribute depends on and export again
time_series.starting_time = 1;
nwbFilePath = fullfile(testCase.OutputFolder, 'test_part2.nwb');
testCase.verifyWarningFree(@(f, fn) nwbExport(testCase.NwbObject, nwbFilePath))
end
end

methods (Static)
function nwb = initNwbFile()
nwb = NwbFile( ...
'session_description', 'test file for nwb export', ...
'identifier', 'export_test', ...
'session_start_time', datetime("now", 'TimeZone', 'local') );
end
end
end
2 changes: 2 additions & 0 deletions +types/+core/ImageSeries.m
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@
end
if ~isempty(obj.external_file) && ~isa(obj.external_file, 'types.untyped.SoftLink') && ~isa(obj.external_file, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/external_file/starting_frame'], obj.external_file_starting_frame, 'forceArray');
elseif isempty(obj.external_file) && ~isempty(obj.external_file_starting_frame)
obj.warnIfPropertyAttributeNotExported('external_file_starting_frame', 'external_file', fullpath)
end
if ~isempty(obj.format)
if startsWith(class(obj.format), 'types.untyped.')
Expand Down
4 changes: 4 additions & 0 deletions +types/+core/ImagingPlane.m
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,8 @@
end
if ~isempty(obj.grid_spacing) && ~isa(obj.grid_spacing, 'types.untyped.SoftLink') && ~isa(obj.grid_spacing, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/grid_spacing/unit'], obj.grid_spacing_unit);
elseif isempty(obj.grid_spacing) && ~isempty(obj.grid_spacing_unit)
obj.warnIfPropertyAttributeNotExported('grid_spacing_unit', 'grid_spacing', fullpath)
end
if ~isempty(obj.imaging_rate)
if startsWith(class(obj.imaging_rate), 'types.untyped.')
Expand Down Expand Up @@ -429,6 +431,8 @@
end
if ~isempty(obj.origin_coords) && ~isa(obj.origin_coords, 'types.untyped.SoftLink') && ~isa(obj.origin_coords, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/origin_coords/unit'], obj.origin_coords_unit);
elseif isempty(obj.origin_coords) && ~isempty(obj.origin_coords_unit)
obj.warnIfPropertyAttributeNotExported('origin_coords_unit', 'origin_coords', fullpath)
end
if ~isempty(obj.reference_frame)
if startsWith(class(obj.reference_frame), 'types.untyped.')
Expand Down
46 changes: 46 additions & 0 deletions +types/+core/ImagingRetinotopy.m
Original file line number Diff line number Diff line change
Expand Up @@ -785,12 +785,18 @@
end
if ~isempty(obj.axis_1_phase_map) && ~isa(obj.axis_1_phase_map, 'types.untyped.SoftLink') && ~isa(obj.axis_1_phase_map, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/axis_1_phase_map/dimension'], obj.axis_1_phase_map_dimension, 'forceArray');
elseif isempty(obj.axis_1_phase_map) && ~isempty(obj.axis_1_phase_map_dimension)
obj.warnIfPropertyAttributeNotExported('axis_1_phase_map_dimension', 'axis_1_phase_map', fullpath)
end
if ~isempty(obj.axis_1_phase_map) && ~isa(obj.axis_1_phase_map, 'types.untyped.SoftLink') && ~isa(obj.axis_1_phase_map, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/axis_1_phase_map/field_of_view'], obj.axis_1_phase_map_field_of_view, 'forceArray');
elseif isempty(obj.axis_1_phase_map) && ~isempty(obj.axis_1_phase_map_field_of_view)
obj.warnIfPropertyAttributeNotExported('axis_1_phase_map_field_of_view', 'axis_1_phase_map', fullpath)
end
if ~isempty(obj.axis_1_phase_map) && ~isa(obj.axis_1_phase_map, 'types.untyped.SoftLink') && ~isa(obj.axis_1_phase_map, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/axis_1_phase_map/unit'], obj.axis_1_phase_map_unit);
elseif isempty(obj.axis_1_phase_map) && ~isempty(obj.axis_1_phase_map_unit)
obj.warnIfPropertyAttributeNotExported('axis_1_phase_map_unit', 'axis_1_phase_map', fullpath)
end
if ~isempty(obj.axis_1_power_map)
if startsWith(class(obj.axis_1_power_map), 'types.untyped.')
Expand All @@ -801,12 +807,18 @@
end
if ~isempty(obj.axis_1_power_map) && ~isa(obj.axis_1_power_map, 'types.untyped.SoftLink') && ~isa(obj.axis_1_power_map, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/axis_1_power_map/dimension'], obj.axis_1_power_map_dimension, 'forceArray');
elseif isempty(obj.axis_1_power_map) && ~isempty(obj.axis_1_power_map_dimension)
obj.warnIfPropertyAttributeNotExported('axis_1_power_map_dimension', 'axis_1_power_map', fullpath)
end
if ~isempty(obj.axis_1_power_map) && ~isa(obj.axis_1_power_map, 'types.untyped.SoftLink') && ~isa(obj.axis_1_power_map, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/axis_1_power_map/field_of_view'], obj.axis_1_power_map_field_of_view, 'forceArray');
elseif isempty(obj.axis_1_power_map) && ~isempty(obj.axis_1_power_map_field_of_view)
obj.warnIfPropertyAttributeNotExported('axis_1_power_map_field_of_view', 'axis_1_power_map', fullpath)
end
if ~isempty(obj.axis_1_power_map) && ~isa(obj.axis_1_power_map, 'types.untyped.SoftLink') && ~isa(obj.axis_1_power_map, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/axis_1_power_map/unit'], obj.axis_1_power_map_unit);
elseif isempty(obj.axis_1_power_map) && ~isempty(obj.axis_1_power_map_unit)
obj.warnIfPropertyAttributeNotExported('axis_1_power_map_unit', 'axis_1_power_map', fullpath)
end
if startsWith(class(obj.axis_2_phase_map), 'types.untyped.')
refs = obj.axis_2_phase_map.export(fid, [fullpath '/axis_2_phase_map'], refs);
Expand All @@ -815,12 +827,18 @@
end
if ~isempty(obj.axis_2_phase_map) && ~isa(obj.axis_2_phase_map, 'types.untyped.SoftLink') && ~isa(obj.axis_2_phase_map, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/axis_2_phase_map/dimension'], obj.axis_2_phase_map_dimension, 'forceArray');
elseif isempty(obj.axis_2_phase_map) && ~isempty(obj.axis_2_phase_map_dimension)
obj.warnIfPropertyAttributeNotExported('axis_2_phase_map_dimension', 'axis_2_phase_map', fullpath)
end
if ~isempty(obj.axis_2_phase_map) && ~isa(obj.axis_2_phase_map, 'types.untyped.SoftLink') && ~isa(obj.axis_2_phase_map, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/axis_2_phase_map/field_of_view'], obj.axis_2_phase_map_field_of_view, 'forceArray');
elseif isempty(obj.axis_2_phase_map) && ~isempty(obj.axis_2_phase_map_field_of_view)
obj.warnIfPropertyAttributeNotExported('axis_2_phase_map_field_of_view', 'axis_2_phase_map', fullpath)
end
if ~isempty(obj.axis_2_phase_map) && ~isa(obj.axis_2_phase_map, 'types.untyped.SoftLink') && ~isa(obj.axis_2_phase_map, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/axis_2_phase_map/unit'], obj.axis_2_phase_map_unit);
elseif isempty(obj.axis_2_phase_map) && ~isempty(obj.axis_2_phase_map_unit)
obj.warnIfPropertyAttributeNotExported('axis_2_phase_map_unit', 'axis_2_phase_map', fullpath)
end
if ~isempty(obj.axis_2_power_map)
if startsWith(class(obj.axis_2_power_map), 'types.untyped.')
Expand All @@ -831,12 +849,18 @@
end
if ~isempty(obj.axis_2_power_map) && ~isa(obj.axis_2_power_map, 'types.untyped.SoftLink') && ~isa(obj.axis_2_power_map, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/axis_2_power_map/dimension'], obj.axis_2_power_map_dimension, 'forceArray');
elseif isempty(obj.axis_2_power_map) && ~isempty(obj.axis_2_power_map_dimension)
obj.warnIfPropertyAttributeNotExported('axis_2_power_map_dimension', 'axis_2_power_map', fullpath)
end
if ~isempty(obj.axis_2_power_map) && ~isa(obj.axis_2_power_map, 'types.untyped.SoftLink') && ~isa(obj.axis_2_power_map, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/axis_2_power_map/field_of_view'], obj.axis_2_power_map_field_of_view, 'forceArray');
elseif isempty(obj.axis_2_power_map) && ~isempty(obj.axis_2_power_map_field_of_view)
obj.warnIfPropertyAttributeNotExported('axis_2_power_map_field_of_view', 'axis_2_power_map', fullpath)
end
if ~isempty(obj.axis_2_power_map) && ~isa(obj.axis_2_power_map, 'types.untyped.SoftLink') && ~isa(obj.axis_2_power_map, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/axis_2_power_map/unit'], obj.axis_2_power_map_unit);
elseif isempty(obj.axis_2_power_map) && ~isempty(obj.axis_2_power_map_unit)
obj.warnIfPropertyAttributeNotExported('axis_2_power_map_unit', 'axis_2_power_map', fullpath)
end
if startsWith(class(obj.axis_descriptions), 'types.untyped.')
refs = obj.axis_descriptions.export(fid, [fullpath '/axis_descriptions'], refs);
Expand All @@ -852,18 +876,28 @@
end
if ~isempty(obj.focal_depth_image) && ~isa(obj.focal_depth_image, 'types.untyped.SoftLink') && ~isa(obj.focal_depth_image, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/focal_depth_image/bits_per_pixel'], obj.focal_depth_image_bits_per_pixel);
elseif isempty(obj.focal_depth_image) && ~isempty(obj.focal_depth_image_bits_per_pixel)
obj.warnIfPropertyAttributeNotExported('focal_depth_image_bits_per_pixel', 'focal_depth_image', fullpath)
end
if ~isempty(obj.focal_depth_image) && ~isa(obj.focal_depth_image, 'types.untyped.SoftLink') && ~isa(obj.focal_depth_image, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/focal_depth_image/dimension'], obj.focal_depth_image_dimension, 'forceArray');
elseif isempty(obj.focal_depth_image) && ~isempty(obj.focal_depth_image_dimension)
obj.warnIfPropertyAttributeNotExported('focal_depth_image_dimension', 'focal_depth_image', fullpath)
end
if ~isempty(obj.focal_depth_image) && ~isa(obj.focal_depth_image, 'types.untyped.SoftLink') && ~isa(obj.focal_depth_image, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/focal_depth_image/field_of_view'], obj.focal_depth_image_field_of_view, 'forceArray');
elseif isempty(obj.focal_depth_image) && ~isempty(obj.focal_depth_image_field_of_view)
obj.warnIfPropertyAttributeNotExported('focal_depth_image_field_of_view', 'focal_depth_image', fullpath)
end
if ~isempty(obj.focal_depth_image) && ~isa(obj.focal_depth_image, 'types.untyped.SoftLink') && ~isa(obj.focal_depth_image, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/focal_depth_image/focal_depth'], obj.focal_depth_image_focal_depth);
elseif isempty(obj.focal_depth_image) && ~isempty(obj.focal_depth_image_focal_depth)
obj.warnIfPropertyAttributeNotExported('focal_depth_image_focal_depth', 'focal_depth_image', fullpath)
end
if ~isempty(obj.focal_depth_image) && ~isa(obj.focal_depth_image, 'types.untyped.SoftLink') && ~isa(obj.focal_depth_image, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/focal_depth_image/format'], obj.focal_depth_image_format);
elseif isempty(obj.focal_depth_image) && ~isempty(obj.focal_depth_image_format)
obj.warnIfPropertyAttributeNotExported('focal_depth_image_format', 'focal_depth_image', fullpath)
end
if ~isempty(obj.sign_map)
if startsWith(class(obj.sign_map), 'types.untyped.')
Expand All @@ -874,9 +908,13 @@
end
if ~isempty(obj.sign_map) && ~isa(obj.sign_map, 'types.untyped.SoftLink') && ~isa(obj.sign_map, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/sign_map/dimension'], obj.sign_map_dimension, 'forceArray');
elseif isempty(obj.sign_map) && ~isempty(obj.sign_map_dimension)
obj.warnIfPropertyAttributeNotExported('sign_map_dimension', 'sign_map', fullpath)
end
if ~isempty(obj.sign_map) && ~isa(obj.sign_map, 'types.untyped.SoftLink') && ~isa(obj.sign_map, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/sign_map/field_of_view'], obj.sign_map_field_of_view, 'forceArray');
elseif isempty(obj.sign_map) && ~isempty(obj.sign_map_field_of_view)
obj.warnIfPropertyAttributeNotExported('sign_map_field_of_view', 'sign_map', fullpath)
end
if startsWith(class(obj.vasculature_image), 'types.untyped.')
refs = obj.vasculature_image.export(fid, [fullpath '/vasculature_image'], refs);
Expand All @@ -885,15 +923,23 @@
end
if ~isempty(obj.vasculature_image) && ~isa(obj.vasculature_image, 'types.untyped.SoftLink') && ~isa(obj.vasculature_image, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/vasculature_image/bits_per_pixel'], obj.vasculature_image_bits_per_pixel);
elseif isempty(obj.vasculature_image) && ~isempty(obj.vasculature_image_bits_per_pixel)
obj.warnIfPropertyAttributeNotExported('vasculature_image_bits_per_pixel', 'vasculature_image', fullpath)
end
if ~isempty(obj.vasculature_image) && ~isa(obj.vasculature_image, 'types.untyped.SoftLink') && ~isa(obj.vasculature_image, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/vasculature_image/dimension'], obj.vasculature_image_dimension, 'forceArray');
elseif isempty(obj.vasculature_image) && ~isempty(obj.vasculature_image_dimension)
obj.warnIfPropertyAttributeNotExported('vasculature_image_dimension', 'vasculature_image', fullpath)
end
if ~isempty(obj.vasculature_image) && ~isa(obj.vasculature_image, 'types.untyped.SoftLink') && ~isa(obj.vasculature_image, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/vasculature_image/field_of_view'], obj.vasculature_image_field_of_view, 'forceArray');
elseif isempty(obj.vasculature_image) && ~isempty(obj.vasculature_image_field_of_view)
obj.warnIfPropertyAttributeNotExported('vasculature_image_field_of_view', 'vasculature_image', fullpath)
end
if ~isempty(obj.vasculature_image) && ~isa(obj.vasculature_image, 'types.untyped.SoftLink') && ~isa(obj.vasculature_image, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/vasculature_image/format'], obj.vasculature_image_format);
elseif isempty(obj.vasculature_image) && ~isempty(obj.vasculature_image_format)
obj.warnIfPropertyAttributeNotExported('vasculature_image_format', 'vasculature_image', fullpath)
end
end
end
Expand Down
2 changes: 2 additions & 0 deletions +types/+core/NWBFile.m
Original file line number Diff line number Diff line change
Expand Up @@ -1010,6 +1010,8 @@
end
if ~isempty(obj.general_source_script) && ~isa(obj.general_source_script, 'types.untyped.SoftLink') && ~isa(obj.general_source_script, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/general/source_script/file_name'], obj.general_source_script_file_name);
elseif isempty(obj.general_source_script) && ~isempty(obj.general_source_script_file_name)
obj.warnIfPropertyAttributeNotExported('general_source_script_file_name', 'general_source_script', fullpath)
end
io.writeGroup(fid, [fullpath '/general']);
if ~isempty(obj.general_stimulus)
Expand Down
Loading

0 comments on commit 7787ff2

Please sign in to comment.