Repository navigation
fix(roisignals): correct neuropil masks and the options output of extractF - #151
Open
ehennestad wants to merge 5 commits into
Open
ehennestad wants to merge 5 commits into
ehennestad wants to merge 5 commits into
Conversation
estimateNeuropilMasks ignores the pixels in pixelsToIgnore when it grows a neuropil mask. With an image mask it computed pixelsToIgnore as footprintAllRois & ~imageMask, which is only true for roi pixels that lie outside the image mask. extractF and SignalExtractor replace an empty imageMask with true(imageSize), so pixelsToIgnore was false everywhere: excludeRoiFromNeuropil had no effect, and the neuropil signal of a roi included the pixels of neighboring rois. Pixels outside a user-defined image mask also stayed in the neuropil masks. Combine the two conditions with |, so that a pixel is left out if it belongs to a roi or if it lies outside the image mask. Neuropil signals that were extracted through extractF or SignalExtractor before this change include pixels of neighboring rois. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
extractF parses its options into params and resolves roiInd, imageMask, extractFcn and roiMaskFormat in updateParameters. The second output was P, the struct of default values that the options are parsed against, so callers always received the defaults. The extractSignals session method saves this output as OptionsSignalExtraction, which therefore did not record options that differed from the defaults. Return params as the second output. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
estimateNeuropilMasks parsed varargin with utility.parsenvpairs against all signal extraction parameters, although it uses five of them. It now declares roiInd, imageMask, excludeRoiFromNeuropil, neuropilExpansionFactor and cellNeuropilSeparation in an arguments block. An option that is not given takes its default value from nansen.twophoton.roisignals.extract.getDefaultParameters, which stays the only place where the default values are defined. - roiInd="all" is resolved to all rois. It was used as a character index before, so the function only worked when the caller had resolved it. - An imageMask whose size differs from the roi masks throws NANSEN:RoiSignals:ImageMaskSizeMismatch. - createMasks passes the five options by name. - The help text describes how a mask is grown and what each option does. EstimateNeuropilMasksTest covers the options. Two of its tests fail if the | in pixelsToIgnore is changed back to &. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
extractF parsed varargin with utility.parsenvpairs, which accepts a struct, name-value pairs with char names, or both in either order, and silently ignores names it does not know. Names given as strings, and therefore all options given with Name=Value syntax, were also ignored. extractF now takes an optional options struct followed by name-value arguments that are declared in an arguments block: - A misspelled option name throws an error. - Options in the struct are passed on as name-value arguments, so they are validated in the same way. Fields that are not options are ignored, because callers pass the struct from getDefaultParameters, which also holds the fields that configure the options editor. - An option given by name replaces the field of the same name in the struct, as before. - No option has a default value in the arguments block. Default values are added afterwards from getDefaultParameters, because a default value in the block could not be told apart from a value given by name and would replace the value in the struct. The struct has to come before the name-value arguments. The extractSignals session method passed it last and is updated. All other callers already pass the struct first. The help text documents the size and layout of signalArray, every option, the second output and the behavior for stacks with more than one current channel. The unused variable method is removed. ExtractFTest compares the signals with the mean pixel values of hand-made masks and covers the calling conventions and the second output. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Contributor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two defects in roi signal extraction. I found them by comparing
nansen.twophoton.roisignals.extractFwith mean pixel values computed by hand on a synthetic stack. The roi signals were exact. The neuropil signals and the returned options were not.nansen.processing.roi.estimateNeuropilMasksgrows a neuropil mask around each roi and ignores the pixels inpixelsToIgnorewhile it does so. With an image mask it computedpixelsToIgnore = footprintAllRois & ~params.imageMask, which is only true for roi pixels that lie outside the image mask.extractFandnansen.processing.SignalExtractorreplace an emptyimageMaskwithtrue(imageSize), sopixelsToIgnorewas false everywhere. The defaultexcludeRoiFromNeuropil = truetherefore had no effect: a neuropil mask left out the pixels of its own roi, but held the pixels of every other roi within its reach. On a synthetic stack with a bright roi 3 pixels away from another roi, the mean neuropil signal of that other roi was 3466 instead of 2047. A user-definedimageMaskhad almost no effect for the same reason. The two conditions are now combined with|.This changes extracted values.
RoiSignals_NeuropilFfrom the Extract Signals session methods, and neuropil signals saved from the roimanager, include pixels of neighboring rois if they were extracted before this change.dffClassicdoes not use the neuropil signal and is not affected.dffChenEtAl2013anddffRoiMinusDffNpilare affected: the activity of a neighboring roi was subtracted from the roi signal as if it were neuropil.extractFreturnedPas its second output.Pis the struct of default values that the options are parsed against. The parsed options, withroiInd,imageMask,extractFcnandroiMaskFormatresolved, are inparams. The Extract Signals session method saves the second output asOptionsSignalExtraction, which therefore always recorded the default values. The second output is nowparams.Both functions now declare their options in an
argumentsblock instead of parsingvararginwithutility.parsenvpairs.estimateNeuropilMaskstakes its five options as name-value arguments, resolvesroiInd = "all"itself and throwsNANSEN:RoiSignals:ImageMaskSizeMismatchfor an image mask of the wrong size.createMaskspasses the options by name.extractFtakes an optional options struct followed by name-value arguments, because most callers pass a struct, often the one fromgetDefaultParameters, which also holds the fields that configure the options editor. Options in the struct are passed on as name-value arguments, so they are validated in the same way. Fields that are not options are ignored, and an option given by name still replaces the field of the same name. No option has a default value in the block, because a default could not be told apart from a value given by name and would replace the value in the struct. Default values are added afterwards fromgetDefaultParameters, which stays the only place where they are defined.Two things behave differently for callers of
extractF. A misspelled option name throws an error instead of being ignored. The struct has to come before the name-value arguments: theextractSignalssession method passed it last and is updated, and all other callers in the repository already pass it first.Name=Valuesyntax now works.utility.parsenvpairsonly recognizes option names that are character vectors, so names given as strings were ignored before.The help text of both functions is rewritten. It documents the size and layout of
signalArray, every option with its default value, the second output, the behavior for stacks with more than one current channel, and how a neuropil mask is grown.nansen.unittest.roisignals.EstimateNeuropilMasksTestandnansen.unittest.roisignals.ExtractFTestare new. They compare the signals with the mean pixel values of hand-made masks and cover the mask options, the calling conventions ofextractFand its second output. IntestNeuropilSignalLeavesOutNeighboringRoisthe rois are brighter than the background, so the neuropil signal only equals the background if no neuropil mask holds a pixel of any roi.The two fixes are the first two commits and change one line each, so they can be picked without the
argumentsblocks.Not part of this PR:
NansenModules/NANSEN-TwoPhotonholds its own copies ofestimateNeuropilMasksandextractFwith the same two defects. The draft stack WP0: two-photon migration hygiene (no moves) #129 to WP8: remove the two-photon module from NANSEN and require it from its own repository #137 removes the copies in this repository, so the fixes have to be ported to the module, and the new tests have to move to thetwophotontestpackage when WP2: move the ROI-signals cluster into the two-photon module #132 is rebased.roiInd,createMasksonly builds masks for the rois thatRoI.getNeighboringRoiIndicesreturns, which are the rois whose center lies within two radii. A roi slightly further away can still lie where the neuropil mask grows, so the neuropil signal of a single-roi extraction can differ from the full extraction. This was hidden while no roi pixels were left out at all.utility.parsenvpairsignores option names given as strings, socomputeDff(F, dffFcn="dffChenEtAl2013")returns thedffClassicresult without a warning.computeDffalso returns ΔF/F of the first roi only when it is given a samples-by-rois matrix.🤖 Generated with Claude Code