diff --git a/src/ndi/+ndi/+calc/+stimulus/tuningcurve.m b/src/ndi/+ndi/+calc/+stimulus/tuningcurve.m index 65cd9b57f..3aa359652 100644 --- a/src/ndi/+ndi/+calc/+stimulus/tuningcurve.m +++ b/src/ndi/+ndi/+calc/+stimulus/tuningcurve.m @@ -222,7 +222,7 @@ constraints_mod = constraint; if numel(deal_constraints_dim)==1 - eval(['[' deal_str ']=1;']); + sz_{1} = i; else eval(['[' deal_str ']=ind2sub(deal_constraints_dim,i);']); end @@ -230,7 +230,7 @@ deal_here = deal_constraints{j}(sz_{j}); if isstruct(deal_here) constraints_mod(end+1) = deal_here; - deal_log_str = cat(2,deal_str,['dealing ' deal_here.field ' = ' num2str(deal_here.param1) ',']); + deal_log_str = cat(2,deal_log_str,['dealing ' deal_here.field ' = ' num2str(deal_here.param1) ',']); stim_prop_index = find(strcmp(deal_here.field,stim_property_list)); % has to be a match, all dealt properties are in stim_property_list stim_property_list_values(stim_prop_index) = deal_here.param1; diff --git a/tests/+ndi/+unittest/+calc/+stimulus/testCalcTuningCurve.m b/tests/+ndi/+unittest/+calc/+stimulus/testCalcTuningCurve.m index 6b3e7f1b6..d62694572 100644 --- a/tests/+ndi/+unittest/+calc/+stimulus/testCalcTuningCurve.m +++ b/tests/+ndi/+unittest/+calc/+stimulus/testCalcTuningCurve.m @@ -14,5 +14,81 @@ function testTuningCurveCalc(testCase) S = ndi.calc.stimulus.tuningcurve(testCase.Session); S.verifySelfTests(testCase,'requireDistinct',true); end + + function testDealSingleProperty(testCase) + % TESTDEALSINGLEPROPERTY - dealing one property makes one curve per value + % + % Builds a mock contrast x spatial_frequency stimulus set, computes + % contrast tuning curves with spatial_frequency dealt, and checks that + % each curve is built at a different spatial frequency: its + % stim_property_list value differs from the others, and every stimid + % in the curve points at a stimulus with that spatial frequency. + + S = testCase.Session; + tc = ndi.calc.stimulus.tuningcurve(S); + + contrastValues = [0.25 0.5 1]; + sfValues = [0.1 0.5 1 2]; + [C, SF] = ndgrid(contrastValues, sfValues); + X = [C(:) SF(:)]; + R = 2 + 10*C(:) .* SF(:); + paramStruct = struct('spatial_frequency',0.5,'angle',0,'contrast',1); + + docs = ndi.mock.fun.stimulus_response(S, paramStruct, ... + {'contrast','spatial_frequency'}, X, R, 0, 2, 'RandomSeed', 1); + + stimResponseDoc = {}; + for k=1:numel(docs) + if isa(docs{k},'ndi.document') && ... + strcmpi(docs{k}.document_properties.document_class.class_name,'stimulus_response_scalar') + stimResponseDoc{end+1} = docs{k}; %#ok + end + end + testCase.assertNotEmpty(stimResponseDoc, 'No stimulus_response_scalar document was made.'); + stimResponseDoc = stimResponseDoc{1}; + + stimPresDoc = S.database_search(ndi.query('base.id','exact_string', ... + stimResponseDoc.dependency_value('stimulus_presentation_id'),'')); + testCase.assertNumElements(stimPresDoc, 1); + stimuli = stimPresDoc{1}.document_properties.stimulus_presentation.stimuli; + + parameters.input_parameters.independent_label = 'contrast'; + parameters.input_parameters.independent_parameter = 'contrast'; + parameters.input_parameters.best_algorithm = 'empirical_maximum'; + parameters.input_parameters.selection = ... + struct('property','contrast','operation','hasfield','value','varies'); + parameters.input_parameters.selection(2) = ... + struct('property','spatial_frequency','operation','hasfield','value','deal'); + parameters.depends_on = struct('name','stimulus_response_scalar_id','value',stimResponseDoc.id()); + + tcDocs = tc.run('Replace',parameters); + if ~iscell(tcDocs) + tcDocs = {tcDocs}; + end + testCase.assertNumElements(tcDocs, numel(sfValues), ... + 'Expected one tuning curve per dealt spatial_frequency value.'); + + dealtValues = nan(1,numel(tcDocs)); + for k=1:numel(tcDocs) + props = tcDocs{k}.document_properties; + testCase.verifyEqual(cellstr(props.tuningcurve_calc.stim_property_list.names), {'spatial_frequency'}); + dealtValues(k) = props.tuningcurve_calc.stim_property_list.values; + testCase.verifySubstring(props.tuningcurve_calc.log, ... + ['dealing spatial_frequency = ' num2str(dealtValues(k))]); + + stimids = props.stimulus_tuningcurve.stimid; + stimids = stimids(~isnan(stimids)); + testCase.verifyNumElements(stimids, numel(contrastValues)); + for n=1:numel(stimids) + p = stimuli(stimids(n)).parameters; + testCase.verifyEqual(p.spatial_frequency, dealtValues(k), 'AbsTol', 1e-12, ... + sprintf('Curve %d (spatial_frequency %g) includes stimid %d at spatial_frequency %g.', ... + k, dealtValues(k), stimids(n), p.spatial_frequency)); + end + end + + testCase.verifyEqual(sort(dealtValues), sfValues, 'AbsTol', 1e-12, ... + 'Each tuning curve should be built at a different dealt value.'); + end end end