Skip to content

Commit

Permalink
Merge branch 'master' into change/improve-type-validator-error-message
Browse files Browse the repository at this point in the history
  • Loading branch information
ehennestad authored Dec 9, 2024
2 parents 9ebaa33 + 42e9995 commit b1624c1
Show file tree
Hide file tree
Showing 38 changed files with 4,495 additions and 940 deletions.
2 changes: 1 addition & 1 deletion +file/Attribute.m
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
else
obj.value = [];
obj.readonly = false;
end
end

if isKey(source, 'dims')
obj.dimnames = source('dims');
Expand Down
19 changes: 19 additions & 0 deletions +file/Dataset.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
dtype;
isConstrainedSet;
required;
value;
readonly; %determines whether value can be changed or not
scalar;
shape;
dimnames;
Expand All @@ -22,12 +24,15 @@
obj.type = '';
obj.dtype = 'any';
obj.required = true;
obj.value = [];
obj.readonly = false;
obj.scalar = true;
obj.definesType = false;

obj.shape = {};
obj.dimnames = {};
obj.attributes = [];


if nargin < 1
return;
Expand All @@ -42,6 +47,20 @@
if isKey(source, nameKey)
obj.name = source(nameKey);
end

% Todo: same as for attribute, should consolidate
valueKey = 'value';
defaultKey = 'default_value';
if isKey(source, defaultKey)
obj.value = source(defaultKey);
obj.readonly = false;
elseif isKey(source, valueKey)
obj.value = source(valueKey);
obj.readonly = true;
else
obj.value = [];
obj.readonly = false;
end

typeKeys = {'neurodata_type_def', 'data_type_def'};
parentKeys = {'neurodata_type_inc', 'data_type_inc'};
Expand Down
12 changes: 9 additions & 3 deletions +file/fillClass.m
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,22 @@
optional = [optional {propertyName}];
end

if isa(prop, 'file.Attribute')
if isa(prop, 'file.Attribute') || isa(prop, 'file.Dataset')
if prop.readonly
readonly = [readonly {propertyName}];
end

if ~isempty(prop.value)
defaults = [defaults {propertyName}];
if isa(prop, 'file.Attribute')
defaults = [defaults {propertyName}];
else % file.Dataset
if isRequired || all(isPropertyRequired)
defaults = [defaults {propertyName}];
end
end
end

if ~isempty(prop.dependent)
if isa(prop, 'file.Attribute') && ~isempty(prop.dependent)
%extract prefix
parentName = strrep(propertyName, ['_' prop.name], '');
parent = classprops(parentName);
Expand Down
4 changes: 2 additions & 2 deletions +file/fillValidators.m
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
nm = propnames{i};
prop = props(nm);


if isa(prop, 'file.Attribute') && prop.readonly && ~isempty(prop.value)
if (isa(prop, 'file.Attribute') || isa(prop, 'file.Dataset')) ...
&& prop.readonly && ~isempty(prop.value)
% Need to add a validator for inherited and readonly properties. In
% the superclass these properties might not be read only and due to
% inheritance its not possible to change property attributes
Expand Down
20 changes: 17 additions & 3 deletions +tests/+unit/PynwbTutorialTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,22 @@
'streaming.py', ... % Requires that HDF5 library is installed with the ROS3 driver enabled which is not a given
'object_id.py', ... % Does not export nwb file
'plot_configurator.py', ... % Does not export nwb file
'plot_zarr_io', ... % Does not export nwb file in nwb format
'brain_observatory.py', ... % Requires allen sdk
'extensions.py'}; % Discrepancy between tutorial and schema: https://github.com/NeurodataWithoutBorders/pynwb/issues/1952

% SkippedFiles - Name of exported nwb files to skip reading with matnwb
SkippedFiles = {'family_nwb_file_0.nwb'} % requires family driver from h5py

% PythonDependencies - Package dependencies for running pynwb tutorials
PythonDependencies = {'hdmf-zarr', 'dataframe-image', 'matplotlib'}
PythonDependencies = {'dataframe-image', 'matplotlib'}
end

properties (Access = private)
PythonEnvironment % Stores the value of the environment variable
% "PYTHONPATH" to restore when test is finished.

Debug (1,1) logical = false
end

methods (TestClassSetup)
Expand Down Expand Up @@ -66,6 +69,12 @@ function setupClass(testCase)
L = dir('temp_venv/lib/python*/site-*'); % Find the site-packages folder
pythonPath = fullfile(L.folder, L.name);
setenv('PYTHONPATH', pythonPath)

pythonPath = tests.util.getPythonPath();

if testCase.Debug
[~, m] = system(sprintf('%s -m pip list', pythonPath)); disp(m)
end
end
end

Expand Down Expand Up @@ -172,7 +181,12 @@ function installPythonDependencies(testCase)
for i = 1:numel(testCase.PythonDependencies)
iName = testCase.PythonDependencies{i};
installCmdStr = sprintf('%s install %s', pipExecutable, iName);
evalc( "system(installCmdStr)" ); % Install without command window output

if testCase.Debug
[~, m] = system(installCmdStr); disp(m)
else
evalc( "system(installCmdStr)" ); % Install without command window output
end
end
end
end
Expand Down Expand Up @@ -208,7 +222,7 @@ function installPythonDependencies(testCase)
end

function pynwbFolder = downloadPynwb()
githubUrl = 'https://github.com/NeurodataWithoutBorders/pynwb/archive/refs/heads/master.zip';
githubUrl = 'https://github.com/NeurodataWithoutBorders/pynwb/archive/refs/heads/dev.zip';
pynwbFolder = downloadZippedGithubRepo(githubUrl, '.'); % Download in current directory
end

Expand Down
4 changes: 2 additions & 2 deletions +tests/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
hdf5plugin
git+https://github.com/NeurodataWithoutBorders/nwbinspector@dev
git+https://github.com/NeurodataWithoutBorders/pynwb.git@dev
git+https://github.com/NeurodataWithoutBorders/nwbinspector.git@dev
git+https://github.com/NeurodataWithoutBorders/pynwb.git@dev
85 changes: 83 additions & 2 deletions +types/+core/Device.m
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@

% OPTIONAL PROPERTIES
properties
description; % (char) Description of the device (e.g., model, firmware version, processing software version, etc.) as free-form text.
manufacturer; % (char) The name of the manufacturer of the device.
description; % (char) Description of the device as free-form text. If there is any software/firmware associated with the device, the names and versions of those can be added to NWBFile.was_generated_by.
manufacturer; % (char) The name of the manufacturer of the device, e.g., Imec, Plexon, Thorlabs.
model_name; % (char) The model name of the device, e.g., Neuropixels 1.0, V-Probe, Bergamo III.
model_number; % (char) The model number (or part/product number) of the device, e.g., PRB_1_4_0480_1, PLX-VP-32-15SE(75)-(260-80)(460-10)-300-(1)CON/32m-V, BERGAMO.
serial_number; % (char) The serial number of the device.
end

methods
Expand All @@ -20,9 +23,15 @@
p.StructExpand = false;
addParameter(p, 'description',[]);
addParameter(p, 'manufacturer',[]);
addParameter(p, 'model_name',[]);
addParameter(p, 'model_number',[]);
addParameter(p, 'serial_number',[]);
misc.parseSkipInvalidName(p, varargin);
obj.description = p.Results.description;
obj.manufacturer = p.Results.manufacturer;
obj.model_name = p.Results.model_name;
obj.model_number = p.Results.model_number;
obj.serial_number = p.Results.serial_number;
if strcmp(class(obj), 'types.core.Device')
cellStringArguments = convertContainedStringsToChars(varargin(1:2:end));
types.util.checkUnset(obj, unique(cellStringArguments));
Expand All @@ -35,6 +44,15 @@
function set.manufacturer(obj, val)
obj.manufacturer = obj.validate_manufacturer(val);
end
function set.model_name(obj, val)
obj.model_name = obj.validate_model_name(val);
end
function set.model_number(obj, val)
obj.model_number = obj.validate_model_number(val);
end
function set.serial_number(obj, val)
obj.serial_number = obj.validate_serial_number(val);
end
%% VALIDATORS

function val = validate_description(obj, val)
Expand Down Expand Up @@ -73,6 +91,60 @@
validshapes = {[1]};
types.util.checkDims(valsz, validshapes);
end
function val = validate_model_name(obj, val)
val = types.util.checkDtype('model_name', 'char', val);
if isa(val, 'types.untyped.DataStub')
if 1 == val.ndims
valsz = [val.dims 1];
else
valsz = val.dims;
end
elseif istable(val)
valsz = [height(val) 1];
elseif ischar(val)
valsz = [size(val, 1) 1];
else
valsz = size(val);
end
validshapes = {[1]};
types.util.checkDims(valsz, validshapes);
end
function val = validate_model_number(obj, val)
val = types.util.checkDtype('model_number', 'char', val);
if isa(val, 'types.untyped.DataStub')
if 1 == val.ndims
valsz = [val.dims 1];
else
valsz = val.dims;
end
elseif istable(val)
valsz = [height(val) 1];
elseif ischar(val)
valsz = [size(val, 1) 1];
else
valsz = size(val);
end
validshapes = {[1]};
types.util.checkDims(valsz, validshapes);
end
function val = validate_serial_number(obj, val)
val = types.util.checkDtype('serial_number', 'char', val);
if isa(val, 'types.untyped.DataStub')
if 1 == val.ndims
valsz = [val.dims 1];
else
valsz = val.dims;
end
elseif istable(val)
valsz = [height(val) 1];
elseif ischar(val)
valsz = [size(val, 1) 1];
else
valsz = size(val);
end
validshapes = {[1]};
types.util.checkDims(valsz, validshapes);
end
%% EXPORT
function refs = export(obj, fid, fullpath, refs)
refs = [email protected](obj, fid, fullpath, refs);
Expand All @@ -85,6 +157,15 @@
if ~isempty(obj.manufacturer)
io.writeAttribute(fid, [fullpath '/manufacturer'], obj.manufacturer);
end
if ~isempty(obj.model_name)
io.writeAttribute(fid, [fullpath '/model_name'], obj.model_name);
end
if ~isempty(obj.model_number)
io.writeAttribute(fid, [fullpath '/model_number'], obj.model_number);
end
if ~isempty(obj.serial_number)
io.writeAttribute(fid, [fullpath '/serial_number'], obj.serial_number);
end
end
end

Expand Down
2 changes: 1 addition & 1 deletion +types/+core/EventWaveform.m
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
classdef EventWaveform < types.core.NWBDataInterface & types.untyped.GroupClass
% EVENTWAVEFORM Represents either the waveforms of detected events, as extracted from a raw data trace in /acquisition, or the event waveforms that were stored during experiment acquisition.
% EVENTWAVEFORM DEPRECATED. Represents either the waveforms of detected events, as extracted from a raw data trace in /acquisition, or the event waveforms that were stored during experiment acquisition.


% OPTIONAL PROPERTIES
Expand Down
53 changes: 10 additions & 43 deletions +types/+core/IZeroClampSeries.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
methods
function obj = IZeroClampSeries(varargin)
% IZEROCLAMPSERIES Constructor for IZeroClampSeries
varargin = [{'stimulus_description' 'N/A'} varargin];
varargin = [{'bias_current' types.util.correctType(0, 'single') 'bridge_balance' types.util.correctType(0, 'single') 'capacitance_compensation' types.util.correctType(0, 'single') 'stimulus_description' 'N/A'} varargin];
obj = [email protected](varargin{:});


Expand All @@ -33,58 +33,25 @@
%% VALIDATORS

function val = validate_bias_current(obj, val)
val = types.util.checkDtype('bias_current', 'single', val);
if isa(val, 'types.untyped.DataStub')
if 1 == val.ndims
valsz = [val.dims 1];
else
valsz = val.dims;
end
elseif istable(val)
valsz = [height(val) 1];
elseif ischar(val)
valsz = [size(val, 1) 1];
if isequal(val, 0)
val = 0;
else
valsz = size(val);
error('NWB:Type:ReadOnlyProperty', 'Unable to set the ''bias_current'' property of class ''<a href="matlab:doc types.core.IZeroClampSeries">IZeroClampSeries</a>'' because it is read-only.')
end
validshapes = {[1]};
types.util.checkDims(valsz, validshapes);
end
function val = validate_bridge_balance(obj, val)
val = types.util.checkDtype('bridge_balance', 'single', val);
if isa(val, 'types.untyped.DataStub')
if 1 == val.ndims
valsz = [val.dims 1];
else
valsz = val.dims;
end
elseif istable(val)
valsz = [height(val) 1];
elseif ischar(val)
valsz = [size(val, 1) 1];
if isequal(val, 0)
val = 0;
else
valsz = size(val);
error('NWB:Type:ReadOnlyProperty', 'Unable to set the ''bridge_balance'' property of class ''<a href="matlab:doc types.core.IZeroClampSeries">IZeroClampSeries</a>'' because it is read-only.')
end
validshapes = {[1]};
types.util.checkDims(valsz, validshapes);
end
function val = validate_capacitance_compensation(obj, val)
val = types.util.checkDtype('capacitance_compensation', 'single', val);
if isa(val, 'types.untyped.DataStub')
if 1 == val.ndims
valsz = [val.dims 1];
else
valsz = val.dims;
end
elseif istable(val)
valsz = [height(val) 1];
elseif ischar(val)
valsz = [size(val, 1) 1];
if isequal(val, 0)
val = 0;
else
valsz = size(val);
error('NWB:Type:ReadOnlyProperty', 'Unable to set the ''capacitance_compensation'' property of class ''<a href="matlab:doc types.core.IZeroClampSeries">IZeroClampSeries</a>'' because it is read-only.')
end
validshapes = {[1]};
types.util.checkDims(valsz, validshapes);
end
function val = validate_stimulus_description(obj, val)
if isequal(val, 'N/A')
Expand Down
2 changes: 1 addition & 1 deletion +types/+core/ImageMaskSeries.m
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
classdef ImageMaskSeries < types.core.ImageSeries & types.untyped.GroupClass
% IMAGEMASKSERIES An alpha mask that is applied to a presented visual stimulus. The 'data' array contains an array of mask values that are applied to the displayed image. Mask values are stored as RGBA. Mask can vary with time. The timestamps array indicates the starting time of a mask, and that mask pattern continues until it's explicitly changed.
% IMAGEMASKSERIES DEPRECATED. An alpha mask that is applied to a presented visual stimulus. The 'data' array contains an array of mask values that are applied to the displayed image. Mask values are stored as RGBA. Mask can vary with time. The timestamps array indicates the starting time of a mask, and that mask pattern continues until it's explicitly changed.


% OPTIONAL PROPERTIES
Expand Down
Loading

0 comments on commit b1624c1

Please sign in to comment.