diff --git a/.github/badges/code_issues.svg b/.github/badges/code_issues.svg
index 4112a8cb..a1bb67f3 100644
--- a/.github/badges/code_issues.svg
+++ b/.github/badges/code_issues.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/code/+nansen/+processing/@MotionCorrectionPreview/MotionCorrectionPreview.m b/code/+nansen/+processing/@MotionCorrectionPreview/MotionCorrectionPreview.m
index 81dde168..43f53fc5 100644
--- a/code/+nansen/+processing/@MotionCorrectionPreview/MotionCorrectionPreview.m
+++ b/code/+nansen/+processing/@MotionCorrectionPreview/MotionCorrectionPreview.m
@@ -7,7 +7,7 @@
% nansen.plugin.imviewer.NoRMCorre as subclasses?
properties (Abstract)
- settings
+ Options
ImviewerObj
end
@@ -21,8 +21,8 @@
methods (Access = protected)
- function onSettingsChanged(obj, name, value)
- %onSettingsChanged Update value in settings if value changes.
+ function onOptionsChanged(obj, name, value)
+ %onOptionsChanged Update value in options if value changes.
% Deal with specific fields
switch name
@@ -33,9 +33,9 @@ function onSettingsChanged(obj, name, value)
msgbox('This is not implemented yet, constant bidirectional correction will be used')
end
case 'OutputFormat'
- oldFilename = obj.settings.Export.FileName;
+ oldFilename = obj.Options.Export.FileName;
newFilename = obj.buildFilenameWithExtension(oldFilename);
- obj.settings.Export.FileName = newFilename;
+ obj.Options.Export.FileName = newFilename;
end
defaultFields = fieldnames(obj.DefaultOptions);
@@ -43,15 +43,15 @@ function onSettingsChanged(obj, name, value)
subFields = fieldnames( obj.DefaultOptions.(defaultFields{i}) );
if any(strcmp(subFields, name))
- obj.settings.(defaultFields{i}).(name) = value;
+ obj.Options.(defaultFields{i}).(name) = value;
end
end
end
- function assertPreviewSettingsValid(obj)
+ function assertPreviewOptionsValid(obj)
% Check if saveResult or showResults is selected
- if ~obj.settings.Preview.saveResults && ~obj.settings.Preview.showResults
+ if ~obj.Options.Preview.saveResults && ~obj.Options.Preview.showResults
msg = 'Aborted, because neither "Save Results" nor "Show Results" are selected';
obj.ImviewerObj.displayMessage(msg);
return
@@ -63,7 +63,7 @@ function assertPreviewSettingsValid(obj)
% Strip current filename of all extensions.
fileName = strsplit(fileName, '.'); % For file with multiple extentsions, i.e .ome.tif
- switch obj.settings.Export.OutputFormat
+ switch obj.Options.Export.OutputFormat
case 'Binary'
fileName = sprintf('%s.raw', fileName{1});
case 'Tiff'
@@ -75,12 +75,12 @@ function assertPreviewSettingsValid(obj)
function dataSet = prepareTargetDataset(obj)
- folderPath = obj.settings.Export.SaveDirectory;
+ folderPath = obj.Options.Export.SaveDirectory;
%folderPath = fileparts( obj.ImviewerObj.ImageStack.FileName );
%folderPath = fullfile(folderPath, 'motion_correction_flowreg');
if ~isfolder(folderPath); mkdir(folderPath); end
- [~, datasetID] = fileparts(obj.settings.Export.FileName);
+ [~, datasetID] = fileparts(obj.Options.Export.FileName);
dataSet = nansen.dataio.dataset.SingleFolderDataSet(folderPath, ...
'DataSetID', datasetID );
@@ -89,7 +89,7 @@ function assertPreviewSettingsValid(obj)
'Data', obj.ImviewerObj.ImageStack)
dataSet.addVariable('TwoPhotonSeries_Corrected', ...
- 'FilePath', obj.settings.Export.FileName, ...
+ 'FilePath', obj.Options.Export.FileName, ...
'Subfolder', 'motion_corrected');
end
@@ -111,7 +111,7 @@ function assertPreviewSettingsValid(obj)
rootDir = obj.DataIoModel.getTargetFolder();
saveDir = fullfile(rootDir, 'image_registration');
else
- rootDir = fileparts( obj.settings.Export.SaveDirectory );
+ rootDir = fileparts( obj.Options.Export.SaveDirectory );
saveDir = rootDir;
end
@@ -126,15 +126,15 @@ function assertPreviewSettingsValid(obj)
end
function imArray = loadSelectedFrameSet(obj)
- %loadSelectedFrameSet Load images for frame interval in settings
+ %loadSelectedFrameSet Load images for frame interval in options
import nansen.wrapper.normcorre.utility.apply_bidirectional_offset
imArray = [];
- % Get frame interval from settings
- firstFrame = obj.settings.Preview.firstFrame;
- lastFrame = (firstFrame-1) + obj.settings.Preview.numFrames;
+ % Get frame interval from options
+ firstFrame = obj.Options.Preview.firstFrame;
+ lastFrame = (firstFrame-1) + obj.Options.Preview.numFrames;
% Make sure we dont grab more than is available.
firstFrame = max([1, firstFrame]);
@@ -156,9 +156,9 @@ function assertPreviewSettingsValid(obj)
imArray = obj.ImviewerObj.ImageStack.getFrameSet(firstFrame:lastFrame);
imArray = squeeze(imArray);
- if obj.settings.Preprocessing.NumFlybackLines ~= 0
+ if obj.Options.Preprocessing.NumFlybackLines ~= 0
IND = repmat({':'}, 1, ndims(imArray));
- IND{1} = obj.settings.Preprocessing.NumFlybackLines : size(imArray, 1);
+ IND{1} = obj.Options.Preprocessing.NumFlybackLines : size(imArray, 1);
imArray = imArray(IND{:});
end
@@ -166,7 +166,7 @@ function assertPreviewSettingsValid(obj)
% %
% % end
- if ~strcmp( obj.settings.Preprocessing.BidirectionalCorrection, 'None')
+ if ~strcmp( obj.Options.Preprocessing.BidirectionalCorrection, 'None')
if ndims(imArray) == 4
imArrayMean = squeeze( mean(imArray, 3) );
colShift = correct_bidirectional_offset(imArrayMean, size(imArray,4), 10);
diff --git a/code/+nansen/+processing/DataMethod.m b/code/+nansen/+processing/DataMethod.m
index ad1d3066..15c726b0 100644
--- a/code/+nansen/+processing/DataMethod.m
+++ b/code/+nansen/+processing/DataMethod.m
@@ -133,8 +133,8 @@
% Abort if h is invalid (improper exit)
if ~isvalid(hPreviewApp); wasSuccess = false; return; end
- obj.Parameters = hPreviewApp.settings;
- obj.Options = hPreviewApp.settings;
+ obj.Parameters = hPreviewApp.Options;
+ obj.Options = hPreviewApp.Options;
wasSuccess = ~hPreviewApp.wasAborted;
delete(hPreviewApp)
diff --git a/code/apps/+imviewer/+plugin/@RoiClassifier/RoiClassifier.m b/code/apps/+imviewer/+plugin/@RoiClassifier/RoiClassifier.m
index d49bbe24..9ba06fd0 100644
--- a/code/apps/+imviewer/+plugin/@RoiClassifier/RoiClassifier.m
+++ b/code/apps/+imviewer/+plugin/@RoiClassifier/RoiClassifier.m
@@ -1,32 +1,23 @@
-classdef RoiClassifier < applify.mixin.AppPlugin
-
- properties (Constant, Hidden = true) % Inherited from applify.mixin.UserSettings via AppPlugin
- USE_DEFAULT_SETTINGS = false
- DEFAULT_SETTINGS = imviewer.plugin.RoiClassifier.getDefaultSettings() % Todo... This is classifier settings.I guess these should be settings relevant for connecting the two apps...
- end
-
+classdef RoiClassifier < applify.mixin.AppBridgePlugin
+
properties (Constant) % Inherited from uim.applify.AppPlugin
Name = 'Roiclassifier'
end
-
- properties
- PrimaryAppName = 'Imviewer';
- end
-
+
properties
ClassifierApp
end
-
- methods (Static)
- S = getDefaultSettings()
+
+ properties (Access = private)
+ ClassifierDestroyedListener event.listener
end
-
+
methods
function obj = RoiClassifier(imviewerApp)
%openRoiClassifier Open roiClassifier on request from imviewer
- obj@applify.mixin.AppPlugin(imviewerApp)
+ obj@applify.mixin.AppBridgePlugin(imviewerApp)
% Find roimanager handle
success=false;
@@ -92,6 +83,8 @@
% Initialize roi classifier
hClsf = roiclassifier.App(roiGroup, 'tileUnits', 'scaled');
obj.ClassifierApp = hClsf;
+ obj.ClassifierDestroyedListener = addlistener(hClsf, ...
+ 'ObjectBeingDestroyed', @(s,e) obj.onClassifierAppDestroyed());
success = true;
end
@@ -99,36 +92,38 @@
if ~success
imviewerApp.displayMessage('Error: No rois are present')
+ delete(obj)
end
end
function delete(obj)
-
+ if ~isempty(obj.ClassifierDestroyedListener) && isvalid(obj.ClassifierDestroyedListener)
+ delete(obj.ClassifierDestroyedListener)
+ end
+ if ~isempty(obj.ClassifierApp) && isvalid(obj.ClassifierApp)
+ delete(obj.ClassifierApp)
+ end
end
end
methods
function setFilePath(obj, filePath)
- obj.ClassifierApp.dataFilePath = filePath;
- end
- end
-
- methods (Access = protected)
-
- function onSettingsChanged(obj, name, value)
-
- end
-
- function onPluginActivated(obj)
- % fprintf('roiclassifier plugin activated...')
-
+ if ~isempty(obj.ClassifierApp) && isvalid(obj.ClassifierApp)
+ obj.ClassifierApp.dataFilePath = filePath;
+ end
end
end
-
- methods (Static)
- function icon = getPluginIcon()
-
+
+ methods (Access = private)
+
+ function onClassifierAppDestroyed(obj)
+ obj.ClassifierDestroyedListener = event.listener.empty;
+ obj.ClassifierApp = [];
+ if isvalid(obj)
+ delete(obj)
+ end
end
+
end
end
diff --git a/code/apps/+imviewer/+plugin/@RoiClassifier/getDefaultSettings.m b/code/apps/+imviewer/+plugin/@RoiClassifier/getDefaultSettings.m
deleted file mode 100644
index b7c3a35a..00000000
--- a/code/apps/+imviewer/+plugin/@RoiClassifier/getDefaultSettings.m
+++ /dev/null
@@ -1,13 +0,0 @@
-function S = getDefaultSettings()
-
- S = struct(...
- 'GridSize_', {{'3x5', '4x6', '5x8', '6x10', '7x12', '8x12', '9x14', '10x16'}}, ...
- 'GridSize', '8x12', ...
- 'ImageSize_', {{'30x30', '50x50', '75x75', '100x100', '128x128', '256x256'}}, ...
- 'ImageSize', '100x100', ...
- 'TileAlpha_', struct('type', 'slider', 'args', {{'Min', 0, 'Max', 1}}), ...
- 'TileAlpha', 0.5, ...
- 'DeleteRejectedTilesOnRefresh', false, ...
- 'IgnoreClassifiedTileOnShiftClick', true);
-
-end
diff --git a/code/apps/+imviewer/+plugin/@RoiManager/RoiManager.m b/code/apps/+imviewer/+plugin/@RoiManager/RoiManager.m
index 0be5528e..ed4b2020 100755
--- a/code/apps/+imviewer/+plugin/@RoiManager/RoiManager.m
+++ b/code/apps/+imviewer/+plugin/@RoiManager/RoiManager.m
@@ -1,4 +1,4 @@
-classdef RoiManager < imviewer.ImviewerPlugin & roimanager.RoiGroupFileIoAppMixin
+classdef RoiManager < imviewer.ImviewerPlugin & applify.mixin.UserSettings & roimanager.RoiGroupFileIoAppMixin
%imviewer.plugin.RoiManager Open the roimanager tool as a plugin in imviewer
% Todo:
@@ -131,6 +131,7 @@
function obj = RoiManager(varargin)
obj@imviewer.ImviewerPlugin(varargin{:})
+ obj@applify.mixin.UserSettings()
% % [nvPairs, varargin] = utility.getnvpairs(varargin);
% %
@@ -673,8 +674,10 @@ function runAutoSegmentation(obj)
function openManualRoiClassifier(obj)
% todo....
- hClassifier = imviewer.plugin.RoiClassifier(obj.ImviewerObj);
- hClassifier.setFilePath(obj.roiFilePath);
+ hClassifier = obj.ImviewerObj.openPlugin('RoiClassifier');
+ if ~isempty(hClassifier) && isvalid(hClassifier)
+ hClassifier.setFilePath(obj.roiFilePath);
+ end
end % /function openManualRoiClassifier
@@ -1231,8 +1234,7 @@ function initializePointerTools(obj)
hAxes = obj.PrimaryApp.Axes;
hMap = obj.roiDisplay;
- isMatch = contains({hViewer.plugins.pluginName}, 'pointerManager');
- obj.PointerManager = hViewer.plugins(isMatch).pluginHandle;
+ obj.PointerManager = hViewer.PointerManager;
pointerNames = {'selectObject', 'polyDraw', 'circleSelect', 'autoDetect', 'freehandDraw'};
@@ -1497,10 +1499,6 @@ function assertValidChannelIndex(obj, value, message)
S = getDefaultSettings()
- function icon = getPluginIcon()
-
- end
-
function pathStr = getIconPath()
% Get system dependent absolute path for icons.
pathStr = roimanager.localpath('toolbar_icons');
diff --git a/code/apps/+imviewer/@App/App.m b/code/apps/+imviewer/@App/App.m
index c990d1e1..9a629e87 100755
--- a/code/apps/+imviewer/@App/App.m
+++ b/code/apps/+imviewer/@App/App.m
@@ -122,7 +122,10 @@
LinkedApps % Todo: Make superclass for timeseries viewer. Property: currentSampleNum & Methods for syncing samplenumber...
uiwidgets = struct() % Todo: Inherit from modular app?
- plugins = struct('pluginName', {}, 'pluginHandle', {}); % Todo: migrate to AppWithPlugin superclass
+end
+
+properties (Access = {?imviewer.App, ?imviewer.ImviewerPlugin}) % App-owned interaction interface
+ PointerManager % uim.interface.pointerManager — owns zoom/pan pointer tools
end
properties (Access = private) % Private components (todo: clean up)
@@ -502,10 +505,7 @@ function initializeViewer(obj)
%obj.displayMessage('Initializing...')
% Initialize the pointer interface.
- pif = uim.interface.pointerManager(obj.Figure, obj.uiaxes.imdisplay, {'zoomIn', 'zoomOut', 'pan'});
- pif.pointers.pan.buttonMotionCallback = @obj.moveImage;
- obj.plugins(end+1).pluginName = 'pointerManager';
- obj.plugins(end).pluginHandle = pif;
+ obj.initialisePointerManager();
% A bit random to do this here, but for now it only influences the
% pointerManager zoom tools
@@ -1442,19 +1442,15 @@ function addImageToolbar(obj)
hToolbar.addButton('Icon', obj.ICONS.zoomOut, 'Mode', 'togglebutton', 'Tag', 'zoomOut', 'Tooltip', 'Zoom Out (w)', buttonProps{:})
hToolbar.addButton('Icon', obj.ICONS.hand4, 'Mode', 'togglebutton', 'Tag', 'pan', 'Tooltip', 'Pan (y)', buttonProps{:})
- % Get handle for pointerManager interface
- isMatch = contains({obj.plugins.pluginName}, 'pointerManager');
- pifHandle = obj.plugins(isMatch).pluginHandle;
-
% Add listeners for toggling of modes from the pointertools to the
% buttons. Also connect to buttonDown to toggling of the pointer
% tools.
pointerModes = {'zoomIn', 'zoomOut', 'pan'};
-
+
for i = 1:numel(pointerModes)
hBtn = hToolbar.getHandle(pointerModes{i});
- hBtn.Callback = @(s,e,h,str) togglePointerMode(pifHandle, pointerModes{i});
- hBtn.addToggleListener(pifHandle.pointers.(pointerModes{i}), 'ToggledPointerTool')
+ hBtn.Callback = @(s,e,h,str) togglePointerMode(obj.PointerManager, pointerModes{i});
+ hBtn.addToggleListener(obj.PointerManager.pointers.(pointerModes{i}), 'ToggledPointerTool')
end
% Add toolbar to the widgets property.
@@ -1488,7 +1484,7 @@ function addTaskbar(obj, showMessage)
hBtn = hTaskbar.addButton('Type', 'pushbutton', ...
'Tag', 'manualclassifier', 'Tooltip', 'Manual Classifier', ...
'Icon', obj.ICONS.manualClassifier, 'UseDefaultIcon', false);
- hBtn.ButtonDownFcn = @(s, e, h) imviewer.plugin.RoiClassifier(obj);
+ hBtn.ButtonDownFcn = @(s, e, h) obj.openPlugin('RoiClassifier');
% Add button for the RoiManager plugin
hBtn = hTaskbar.addButton('Type', 'pushbutton', ...
@@ -1774,34 +1770,29 @@ function reparent(obj, newParent, mode)
obj.Axes.UIContextMenu.Parent = hFig;
% TEMPORARY. Todo: should make separate method for this!
- % Get handle for pointerManager interface
- isMatch = contains({obj.plugins.pluginName}, 'pointerManager');
- pifHandle = obj.plugins(isMatch).pluginHandle;
- delete(pifHandle)
-
+ delete(obj.PointerManager)
+
obj.Figure.CloseRequestFcn = [];
delete(obj.Figure)
-
+
obj.Figure = hFig;
-
+
obj.uiaxes.imdisplay.UIContextMenu = uicontextmenu(obj.Figure);
obj.createImageMenu(obj.uiaxes.imdisplay.UIContextMenu);
-
+
obj.uiaxes.imdisplay.ButtonDownFcn = [];
- pifHandle = uim.interface.pointerManager(obj.Figure, obj.uiaxes.imdisplay, {'zoomIn', 'zoomOut', 'pan'});
- pifHandle.pointers.pan.buttonMotionCallback = @obj.moveImage;
- obj.plugins(isMatch).pluginHandle = pifHandle;
+ obj.initialisePointerManager();
+
% Add listeners for toggling of modes from the pointertools to the
% buttons. Also connect to buttonDown to toggling of the pointer
% tools.
-
hToolbar = obj.uiwidgets.Toolbar;
pointerModes = {'zoomIn', 'zoomOut', 'pan'};
-
+
for i = 1:numel(pointerModes)
hBtn = hToolbar.getHandle(pointerModes{i});
- hBtn.Callback = @(s,e,h,str) togglePointerMode(pifHandle, pointerModes{i});
- hBtn.addToggleListener(pifHandle.pointers.(pointerModes{i}), 'ToggledPointerTool')
+ hBtn.Callback = @(s,e,h,str) togglePointerMode(obj.PointerManager, pointerModes{i});
+ hBtn.addToggleListener(obj.PointerManager.pointers.(pointerModes{i}), 'ToggledPointerTool')
end
% From updateImageDisplay
@@ -4363,7 +4354,27 @@ function showHelp(obj, ~, ~)
% % Todo: Remove/resolve this
function editSettings(obj, ~, ~)
- obj.editSettings@applify.mixin.UserSettings()
+ % Collect settings from the app and any active plugins that have settings.
+ settingsProviders = [{obj}, num2cell(obj.Plugins)];
+ hasSettings = cellfun(@(h) isprop(h, 'settings'), settingsProviders);
+ settingsProviders = settingsProviders(hasSettings);
+
+ if numel(settingsProviders) > 1
+ titleStr = sprintf('Preferences for %s', class(obj));
+ settingsStructs = cellfun(@(h) h.settings, settingsProviders, 'uni', 0);
+ names = cellfun(@(h) class(h), settingsProviders, 'uni', 0);
+ callbacks = cellfun(@(h) @(name, value) h.changeSettings(name, value), settingsProviders, 'uni', 0);
+
+ settingsStructs = tools.editStruct(settingsStructs, nan, titleStr, ...
+ 'Callback', callbacks, 'Name', names);
+
+ for i = 1:numel(settingsProviders)
+ settingsProviders{i}.settings = settingsStructs{i};
+ settingsProviders{i}.saveSettings()
+ end
+ else
+ obj.editSettings@applify.mixin.UserSettings()
+ end
% Why? Is this if the cancel button is hit?
% obj.uiaxes.imdisplay.CLim = obj.settings.ImageDisplay.imageBrightnessLimits;
@@ -4722,8 +4733,19 @@ function stopDraw(obj)
end
end
+methods (Access = private) % PointerManager lifecycle
+
+ function initialisePointerManager(obj)
+ % initialisePointerManager - Create the pointer manager and assign it to obj.PointerManager
+ pointerManager = uim.interface.pointerManager(obj.Figure, obj.uiaxes.imdisplay, {'zoomIn', 'zoomOut', 'pan'});
+ pointerManager.pointers.pan.buttonMotionCallback = @obj.moveImage;
+ obj.PointerManager = pointerManager;
+ end
+
+end
+
methods (Access = private) % Housekeeping and internal
-
+
function turnOffModernAxesToolbar(obj, hAxes)
% Disable newer matlab axes interactivity...
@@ -4963,10 +4985,8 @@ function onSliderChanged(obj, ~, evtData, channelNumber)
end
function onMouseEnteredImage(obj, ~, ~)
- isMatch = contains({obj.plugins.pluginName}, 'pointerManager');
- if any(isMatch)
- pifHandle = obj.plugins(isMatch).pluginHandle;
- pifHandle.updatePointerSymbol()
+ if ~isempty(obj.PointerManager)
+ obj.PointerManager.updatePointerSymbol()
end
end
@@ -5099,7 +5119,7 @@ function onMouseMotion(obj, ~, ~)
obj.isDrag = true;
% Abort if mousemode is set
- if ~isempty(obj.plugins(1).pluginHandle.currentPointerTool)
+ if ~isempty(obj.PointerManager) && ~isempty(obj.PointerManager.currentPointerTool)
return
end
@@ -5220,23 +5240,16 @@ function onKeyPressed(obj, ~, event, forceKey)
if ~obj.isMouseInApp && ~forceKey; return; end
- % Todo: This only holds the pointermanager. Should make it into an
- % interface.., not a plugin.
- if ~isempty(obj.plugins)
- for i = 1:numel(obj.plugins)
- try
- wasCaptured = obj.plugins(i).pluginHandle.onKeyPress([], event);
- if wasCaptured; return; end
- catch ME
- fprintf( [ME.message, '\n'] )
- % something went wrong, but that's fine?
- end
+ % Route key press to the pointer manager first.
+ if ~isempty(obj.PointerManager)
+ try
+ wasCaptured = obj.PointerManager.onKeyPress([], event);
+ if wasCaptured; return; end
+ catch ME
+ fprintf( [ME.message, '\n'] )
end
end
-
- % Note: This should eventually take over for the if block above,
- % pointermanager is currently saved in the 'plugins' property, and
- % this method works on 'Plugins' property. Todo: consolidate
+
wasCaptured = obj.sendKeyEventToPlugins([], event);
if wasCaptured; return; end
@@ -5509,22 +5522,19 @@ function onKeyPressed(obj, ~, event, forceKey)
function onKeyReleased(obj, ~, event)
% onKeyReleased - Callback handler for key release events
- % Check if any plugins are active, and invoke their keyRelease
- % callback handler. If no plugins are active, or no plugins
- % captures the key, let this app handle the key event.
-
- if ~isempty(obj.plugins)
- for i = 1:numel(obj.plugins)
- try
- wasCaptured = obj.plugins(i).pluginHandle.onKeyRelease([], event);
- if wasCaptured; return; end
- catch ME
- fprintf( [ME.message, '\n'] )
- % something went wrong, but that's fine?
- end
+ % Route key release to the pointer manager first.
+ if ~isempty(obj.PointerManager)
+ try
+ wasCaptured = obj.PointerManager.onKeyRelease([], event);
+ if wasCaptured; return; end
+ catch ME
+ fprintf( [ME.message, '\n'] )
end
end
+ wasCaptured = obj.sendKeyReleaseEventToPlugins([], event);
+ if wasCaptured; return; end
+
switch event.Key
case 'shift'
obj.Figure.SelectionType = 'normal';
@@ -5851,20 +5861,13 @@ function onImageStackSet(obj)
end
function configureSpatialDownsampling(obj)
-
+ if isempty(obj.PointerManager); return; end
if isa(obj.ImageStack, 'nansen.stack.HighResolutionImage')
-
- ind = find(contains( {obj.plugins.pluginName}, 'pointerManager'));
- pif = obj.plugins(ind).pluginHandle;
-
- pif.pointers.zoomIn.zoomFinishedCallback = @(s,e) obj.onDisplayLimitsChanged;
- pif.pointers.zoomOut.zoomFinishedCallback = @(s,e) obj.onDisplayLimitsChanged;
-
+ obj.PointerManager.pointers.zoomIn.zoomFinishedCallback = @(s,e) obj.onDisplayLimitsChanged;
+ obj.PointerManager.pointers.zoomOut.zoomFinishedCallback = @(s,e) obj.onDisplayLimitsChanged;
else
- ind = find(contains( {obj.plugins.pluginName}, 'pointerManager'));
- pif = obj.plugins(ind).pluginHandle;
- pif.pointers.zoomIn.zoomFinishedCallback = [];
- pif.pointers.zoomOut.zoomFinishedCallback = [];
+ obj.PointerManager.pointers.zoomIn.zoomFinishedCallback = [];
+ obj.PointerManager.pointers.zoomOut.zoomFinishedCallback = [];
end
end
diff --git a/code/apps/+imviewer/@ImviewerPlugin/ImviewerPlugin.m b/code/apps/+imviewer/@ImviewerPlugin/ImviewerPlugin.m
index 62b286bc..ae8eaf14 100644
--- a/code/apps/+imviewer/@ImviewerPlugin/ImviewerPlugin.m
+++ b/code/apps/+imviewer/@ImviewerPlugin/ImviewerPlugin.m
@@ -1,12 +1,8 @@
classdef ImviewerPlugin < applify.mixin.AppPlugin
-%imviewer.AppPlugin Superclass for plugins to the imviewer app
+%imviewer.ImviewerPlugin App-scoped plugin base for imviewer
% Abstract class providing properties and methods that gives plugin
% functionality for imviewer.
-
- properties
- PrimaryAppName = 'imviewer' % Name of primary app
- end
properties (Dependent)
ImviewerObj % Alias for PrimaryApp % Rename to ImviewerApp
@@ -14,11 +10,9 @@
properties (GetAccess = protected, SetAccess = private)
Axes % Axes for plotting into
- %PointerManager
end
properties (Dependent, SetAccess = private)
- NumFrames
NumChannels
NumPlanes
end
@@ -32,23 +26,10 @@
methods % Constructor
function obj = ImviewerPlugin(varargin)
-
% Make sure the given handle is an instance of imviewer.App
[h, varargin] = imviewer.ImviewerPlugin.checkForImviewerInArgList(varargin);
obj@applify.mixin.AppPlugin(h, varargin{:})
-
- if isempty(h); return; end
-
- % Assign property values.
- obj.PrimaryApp = h;
- obj.Axes = h.Axes;
-
- obj.onImviewerSet()
-
- obj.assignDataIoModel() % todo: superclass? Should belong to a
- % data method class, not a plugin.
- % So a plugin that runs a method should inherit the imviewer
- % plugin and the datamethod...
+ % onImviewerSet is called via onPluginActivated during activatePlugin above.
end
end
@@ -65,14 +46,6 @@
methods
- function assignDataIoModel(obj)
- return % Under construction. Todo: Move to another class
- if isempty(obj.DataIoModel)
- folderPath = fileparts( obj.ImviewerObj.ImageStack.FileName );
- obj.DataIoModel = nansen.dataio.DataIoModel(folderPath);
- end
- end
-
function imviewerObj = get.ImviewerObj(obj)
imviewerObj = obj.PrimaryApp;
end
diff --git a/code/graphics/+applify/+mixin/AppBridgePlugin.m b/code/graphics/+applify/+mixin/AppBridgePlugin.m
new file mode 100644
index 00000000..eb5bbd20
--- /dev/null
+++ b/code/graphics/+applify/+mixin/AppBridgePlugin.m
@@ -0,0 +1,7 @@
+classdef AppBridgePlugin < applify.mixin.AppPlugin
+%AppBridgePlugin Base for plugins that bridge to another app/controller
+%
+% Bridge plugins coordinate another app or controller, but do not own a
+% method-options workflow and do not need any extra base behavior beyond
+% the generic AppPlugin contract.
+end
diff --git a/code/graphics/+applify/+mixin/AppPlugin.m b/code/graphics/+applify/+mixin/AppPlugin.m
index 604f2dd1..38c20409 100644
--- a/code/graphics/+applify/+mixin/AppPlugin.m
+++ b/code/graphics/+applify/+mixin/AppPlugin.m
@@ -1,4 +1,4 @@
-classdef AppPlugin < applify.mixin.UserSettings & matlab.mixin.Heterogeneous & uiw.mixin.AssignPVPairs
+classdef AppPlugin < matlab.mixin.Heterogeneous & uiw.mixin.AssignPVPairs
%AppPlugin Abstract superclass for an app plugin
%
% Syntax:
@@ -18,313 +18,163 @@
% property, value pairs to be set on construction
%
- % Not quite sure yet what to add here.
+ % Plugins can:
+ % - implement mouse/keyboard callbacks invoked by the host app
+ % - add items to the app menu
+ % - hold method options via OptionsManager (algorithm parameters)
%
- % Provide properties and methods for other classes to act as plugins to
- % apps.
- %
- % On construction of the plugin, it is added to the apps plugin
- % list. If a plugin of the same type is already in the list, the
- % handle of that is returned instead of creating a new one...
- %
- % Plugins can implement mouse/keyboard callbacks that are called
- % whenever the apps corresponding callback is invoked
- %
- % The plugin gets access to some of the parent class properties
- % and methods.
- %
- % The plugin can add items to the apps menu.
- %
- % App takes plugin's settings into account.
-
- % Todo:
- % [ ] Inherit from nansen.mixin.HasOptions instead of
- % applify.mixin.UserSettings?
- % The original idea was that a plugin has some additional
- % settings that should be combined with the apps own settings
- % when the plugin is active. However, all the plugins that are
- % implemented is a method/algorithm with parameters, and these
- % are managed using the Options/OptionsManager instead...
-
+ % matlab.mixin.Heterogeneous allows AppWithPlugin.Plugins to hold an
+ % array of mixed AppPlugin subclass instances.
+
properties (Abstract, Constant)
Name
end
-
- properties (Abstract)
- PrimaryAppName % What is this used for exactly? Maybe remove?
- end
-
+
properties (SetAccess = protected)
PartialConstruction = false % Do a partial construction, i.e skip the opening of options editor on construction
end
-
+
properties
- RunMethodOnFinish = true % Should we run method when settings/options are "saved"?
- DestroyOnFinish = true % Destroy plugin when control panel is closed
- Modal = true % Is figure modal or not
- DataIoModel % Store a data i/o model object if it is provided.
- OptionsManager % Store optionsmanager handle if plugin is provided with an optionsmanager on construction
+ DataIoModel % Store a data i/o model object if it is provided.
+ OptionsManager % Store optionsmanager handle if plugin is provided with an optionsmanager on construction
end
-
+
properties
- PrimaryApp % App which is primary "owner" of the plugin. find better propname?
+ PrimaryApp % App which is primary "owner" of the plugin.
MenuItem struct % Struct for storing menu handles
- Icon
end
-
+
properties (Access = protected)
IsActivated = false;
end
-
- methods (Abstract, Static) % Should it be a property or part of settings?
- %getPluginIcon()
- end
-
-% % methods (Abstract, Access = protected) % todo: Is this abstract??
-% % onPluginActivated % Todo: find better name..
-% % end
methods % Constructor
-
+
function obj = AppPlugin(hApp, varargin)
-
+
[options, varargin] = applify.mixin.AppPlugin.optionsCheck(varargin);
-
+
if nargin > 2
obj.parseVarargin(varargin{1:end})
end
-
+
if ~nargin || isempty(hApp); return; end
-
+
obj.validateAppHandle(hApp)
-
- % Check if plugin is already open/active
+
+ % If this plugin is already active, return the existing instance
+ % rather than creating a duplicate.
if hApp.isPluginActive(obj)
+ warning('applify:AppPlugin:alreadyActive', ...
+ 'Plugin "%s" is already active. Returning existing instance.', obj.Name)
obj = hApp.getPluginHandle(obj.Name);
+ return
end
-
+
% Assign options from input if provided
if nargin >= 2 && ~isempty(options)
obj.assignOptions(options)
else
obj.assignDefaultOptions()
end
-
- if ~hApp.isPluginActive(obj)
- obj.activatePlugin(hApp)
- end
-
+
+ obj.activatePlugin(hApp);
+
if ~nargout; clear obj; end
end
-
- function delete(obj)
+ function delete(obj)
% Delete menu items
if ~isempty(obj.MenuItem)
structfun(@delete, obj.MenuItem)
end
end
end
-
+
methods (Access = public)
-
- function openControlPanel(obj)
- obj.editSettings()
- end
-
- function run(obj)
+
+ function run(obj) %#ok
% Subclasses may override
end
end
-
+
% Methods for mouse and keyboard interactive callbacks
- methods (Access = {?applify.mixin.AppPlugin, ?applify.AppWithPlugin} )
-
- function tf = keyPressHandler(obj, src, evt) % Subclass can override
- % todo: rename to onKeyPressed
- tf = false; % Key press event was not captured by plugin
+ methods (Access = {?applify.mixin.AppPlugin, ?applify.AppWithPlugin})
+ function tf = keyPressHandler(obj, src, evt) %#ok Subclass can override
+ tf = false;
end
-
- function tf = keyReleasedHandler(obj, src, evt) % Subclass can override
- tf = false; % Key released event was not captured by plugin
+
+ function tf = keyReleasedHandler(obj, src, evt) %#ok Subclass can override
+ tf = false;
end
- %tf = mousePressHandler(src, evt) % Subclass can override
-
end
-
- methods % Access?
-
+
+ methods
+
function activatePlugin(obj, appHandle)
obj.PrimaryApp = appHandle;
obj.PrimaryApp.addPlugin(obj)
-
obj.onPluginActivated()
obj.IsActivated = true;
end
-
- function sEditor = openSettingsEditor(obj)
- %openSettingsEditor Open ui dialog for editing plugin options.
-
- titleStr = sprintf('Options Editor (%s)', obj.Name);
-
- if ~isempty(obj.OptionsManager)
- sEditor = obj.OptionsManager.openOptionsEditor();
- sEditor.Title = titleStr;
- sEditor.Callback = @obj.onSettingsChanged;
- else
- sEditor = structeditor(obj.settings, 'Title', titleStr, ...
- 'Callback', @obj.onSettingsChanged );
- end
-
- obj.relocatePrimaryApp(sEditor) % To prevent figures covering each other
- obj.hSettingsEditor = sEditor;
-
- addlistener(obj, 'ObjectBeingDestroyed', @(s,e)delete(sEditor));
-
- end
-
- function editSettings(obj)
- %editSettings Open and wait for user to edit settings.
-
- sEditor = obj.openSettingsEditor();
-
- if obj.Modal
- sEditor.waitfor()
- obj.onSettingsEditorResumed()
- else
- % Todo: implement above in callback.
- addlistener(sEditor, 'AppDestroyed', ...
- @(s, e) obj.onSettingsEditorResumed);
- end
- end
-
- function place(obj, varargin)
- obj.hSettingsEditor.place(varargin{:})
- end
end
-
+
methods (Access = protected)
-
+
function onPluginActivated(obj)
%onPluginActivated Run subroutines when plugin is activated.
obj.createSubMenu()
end
-
+
function parseVarargin(obj, varargin)
%parseVarargin Parser for varargin that are passed on construction
-
- % Look for flag of whether to open plugin's options panel on
- % construction
+ % Look for flag of whether to open plugin's options panel on construction
if ischar(varargin{1}) && isequal(varargin{1}, '-p')
obj.PartialConstruction = true;
varargin(1) = [];
end
-
obj.assignPVPairs(varargin{:})
end
-
+
function assignOptions(obj, options)
%assignOptions Assign non default options for plugin
%
- % obj.assignOptions(options) assigns non-default settings to
- % this object. options can be a struct or an OptionsManager
- % object
-
+ % options can be a struct or an OptionsManager object
if isa(options, 'struct')
- obj.settings = options;
+ obj.assignOptionsStruct(options)
elseif isa(options, 'nansen.manage.OptionsManager')
obj.OptionsManager = options;
- obj.settings = obj.OptionsManager.Options;
+ obj.assignOptionsStruct(obj.OptionsManager.Options)
end
end
-
- function assignDefaultOptions(obj)
- %assignDefaultOptions Assign default options
- % Subclasses may override.
- end
-
- function createSubMenu(obj)
- % Subclasses may override
- end
-
- function onSettingsEditorResumed(obj)
- %onSettingsEditorResumed Callback for when settings editor is resumed
-
- % Todo: What if obj is invalid
-
- % Abort if sEditor is invalid (improper exit)
- if ~isvalid(obj.hSettingsEditor)
- obj.hSettingsEditor = [];
- return;
- end
- if ~obj.hSettingsEditor.wasCanceled
- obj.settings_ = obj.hSettingsEditor.dataEdit;
- end
-
- obj.wasAborted = obj.hSettingsEditor.wasCanceled;
- delete(obj.hSettingsEditor)
- obj.hSettingsEditor = [];
- obj.onSettingsEditorClosed()
-
- if ~obj.wasAborted && obj.RunMethodOnFinish
- obj.run();
- end
-
- if obj.DestroyOnFinish
- delete(obj)
- end
+ function assignDefaultOptions(obj) %#ok
+ %assignDefaultOptions Assign default options. Subclasses may override.
end
-
- function onSettingsEditorClosed(obj)
+
+ function createSubMenu(obj) %#ok
% Subclasses may override
end
-
- function relocatePrimaryApp(obj, hPlugin, direction)
- %relocatePrimaryApp Relocate windows to prevent coverup.
-
- % TODO: What if plugin figure is in same window.. i.e roimanager
- % TODO: Improve so that windows are not moved off-screen
-
- if nargin < 3
- direction = 'horizontal';
- end
-
- figPos(1,:) = getpixelposition(hPlugin.Figure);
- figPos(2,:) = getpixelposition(obj.PrimaryApp.Figure);
- hFig = [hPlugin.Figure, obj.PrimaryApp.Figure];
-
- switch direction
- case 'horizontal'
- figPos_ = figPos(:, [1,3]); dim = 1;
- case 'vertical'
- figPos_ = figPos(:, [2,4]); dim = 2;
- end
-
- screenPos = uim.utility.getCurrentScreenSize( obj.PrimaryApp.Figure );
-
- [~, idx] = sort(figPos_(:, 1));
- figPos = figPos(idx, :);
- hFig = hFig(idx);
-
- [x, ~] = uim.utility.layout.subdividePosition(min(figPos(:,1)), ...
- screenPos(dim+2), figPos_(:,2), 20);
-
- for i = 1:2
- hFig(i).Position(dim) = x(i);
+
+ function assignOptionsStruct(obj, options)
+ if isprop(obj, 'Options')
+ obj.Options = options;
+ else
+ error('AppPlugin:MissingOptionsTarget', ...
+ 'Plugin "%s" does not expose Options storage.', obj.Name)
end
end
+
end
-
+
methods (Access = private)
-
+
function validateAppHandle(obj, hApp)
%validateAppHandle Check validity of app handle
-
if ~isa(hApp, 'applify.AppWithPlugin')
error('Can not add plugin "%s" to app of type %s', ...
obj.Name, class(hApp))
@@ -333,15 +183,12 @@ function validateAppHandle(obj, hApp)
end
methods (Static)
-
+
function [opts, cellOfArgs] = optionsCheck(cellOfArgs)
-
opts = [];
-
if numel(cellOfArgs) >= 1
containsOpts = isa(cellOfArgs{1}, 'struct') || ...
isa(cellOfArgs{1}, 'nansen.manage.OptionsManager');
-
if containsOpts
opts = cellOfArgs{1}; cellOfArgs(1) = [];
end
diff --git a/code/graphics/+applify/+mixin/HasOptionsManager.m b/code/graphics/+applify/+mixin/HasOptionsManager.m
new file mode 100644
index 00000000..daf408c3
--- /dev/null
+++ b/code/graphics/+applify/+mixin/HasOptionsManager.m
@@ -0,0 +1,83 @@
+classdef HasOptionsManager < handle
+%HasOptionsManager Mixin that gives a class an instance-level OptionsManager
+%
+% Provides a clean separation between user settings (persistent
+% preferences, saved to file via UserSettings) and method options
+% (algorithm parameters, managed via OptionsManager).
+%
+% Intended for use by AppPlugin subclasses that represent an algorithm
+% with parameters. Mix this in alongside AppPlugin to get a dedicated
+% options path that does not go through obj.settings.
+%
+% USAGE
+% classdef MyPlugin < applify.mixin.AppPlugin & applify.mixin.HasOptionsManager
+%
+% % In assignDefaultOptions:
+% function assignDefaultOptions(obj)
+% obj.OptionsManager = nansen.manage.OptionsManager('myFunction');
+% end
+%
+% % Elsewhere: read options
+% opts = obj.Options;
+%
+% % Open editor and wait
+% [opts, wasAborted] = obj.editOptions();
+
+ properties
+ OptionsManager nansen.manage.OptionsManager
+ end
+
+ properties (Dependent)
+ Options
+ end
+
+ properties (Access = private)
+ Options_
+ end
+
+ methods
+
+ function set.Options(obj, options)
+ obj.Options_ = options;
+ end
+
+ function options = get.Options(obj)
+ if ~isempty(obj.Options_)
+ options = obj.Options_;
+ elseif ~isempty(obj.OptionsManager)
+ options = obj.OptionsManager.Options;
+ else
+ options = struct.empty;
+ end
+ end
+
+ end
+
+ methods (Access = public)
+
+ function [options, wasAborted] = editOptions(obj)
+ %editOptions Open the options editor and wait for user input
+ [~, options, wasAborted] = obj.OptionsManager.editOptions();
+ if ~wasAborted
+ obj.Options_ = options;
+ obj.onOptionsChanged();
+ end
+ if ~nargout
+ clear options wasAborted
+ elseif nargout == 1
+ clear wasAborted
+ end
+ end
+
+ end
+
+ methods (Access = protected)
+
+ function onOptionsChanged(obj) %#ok
+ %onOptionsChanged Called after options are changed via editOptions
+ % Subclasses may override to react to option changes immediately.
+ end
+
+ end
+
+end
diff --git a/code/graphics/+applify/+mixin/ModalMethodPreviewController.m b/code/graphics/+applify/+mixin/ModalMethodPreviewController.m
new file mode 100644
index 00000000..6c9dcc6a
--- /dev/null
+++ b/code/graphics/+applify/+mixin/ModalMethodPreviewController.m
@@ -0,0 +1,172 @@
+classdef ModalMethodPreviewController < handle
+%ModalMethodPreviewController Behavioral mixin for modal edit/preview/run workflows
+%
+% Provides the lifecycle for plugins that:
+% 1. Open a modal options editor (edit)
+% 2. Show a live preview while the editor is open (preview)
+% 3. Run the method when the editor is confirmed (run)
+% 4. Destroy themselves when finished (destroy)
+%
+% Subclasses should override:
+% - assignDefaultOptions - set the default method options
+% - openControlPanel — set up preview state, then call editOptions
+% - run — execute the method
+% - onOptionsChanged - react to live option changes in the editor
+% - onOptionsEditorClosed — clean up preview state when editor closes
+%
+% This class is a behavioral mixin, not the primary plugin base class.
+% Concrete plugins should combine it with a single AppPlugin-derived
+% superclass such as imviewer.ImviewerPlugin.
+
+ properties
+ RunMethodOnFinish (1,1) logical = true % Run method when editor is confirmed
+ DestroyOnFinish (1,1) logical = true % Destroy plugin after run
+ Modal (1,1) logical = true % Block execution while editor is open
+ end
+
+ properties (Dependent)
+ Options
+ end
+
+ properties (Access = protected)
+ Options_ struct = struct.empty
+ hOptionsEditor
+ end
+ properties
+ wasAborted = false
+ end
+
+ methods (Access = public)
+
+ function openControlPanel(obj)
+ %openControlPanel Open the options editor. Subclasses may override
+ % to set up preview state before calling editOptions.
+ obj.editOptions()
+ end
+
+ function editOptions(obj)
+ %editOptions Open the options editor and wait (if modal).
+ optionsEditor = obj.openOptionsEditor();
+ if obj.Modal
+ optionsEditor.waitfor()
+ obj.onOptionsEditorResumed()
+ else
+ addlistener(optionsEditor, 'AppDestroyed', ...
+ @(s, e) obj.onOptionsEditorResumed);
+ end
+ end
+
+ function optionsEditor = openOptionsEditor(obj)
+ %openOptionsEditor Open the ui dialog for editing method options.
+ titleStr = sprintf('Options Editor (%s)', obj.Name);
+ if ~isempty(obj.OptionsManager)
+ optionsEditor = obj.OptionsManager.openOptionsEditor([], obj.getCurrentOptions());
+ optionsEditor.Title = titleStr;
+ optionsEditor.Callback = @obj.onOptionsChanged;
+ else
+ optionsEditor = structeditor(obj.getCurrentOptions(), ...
+ 'Title', titleStr, 'Callback', @obj.onOptionsChanged);
+ end
+ obj.relocatePrimaryApp(optionsEditor)
+ obj.hOptionsEditor = optionsEditor;
+ addlistener(obj, 'ObjectBeingDestroyed', @(s,e) delete(optionsEditor));
+ end
+
+ function place(obj, varargin)
+ obj.hOptionsEditor.place(varargin{:})
+ end
+
+ end
+
+ methods (Access = protected)
+
+ function onOptionsEditorResumed(obj)
+ %onOptionsEditorResumed Called when the editor closes or is confirmed.
+ if ~isvalid(obj.hOptionsEditor)
+ obj.hOptionsEditor = [];
+ return
+ end
+ if ~obj.hOptionsEditor.wasCanceled
+ obj.setCurrentOptions(obj.hOptionsEditor.dataEdit);
+ end
+ obj.wasAborted = obj.hOptionsEditor.wasCanceled;
+ delete(obj.hOptionsEditor)
+ obj.hOptionsEditor = [];
+ obj.onOptionsEditorClosed()
+ if ~obj.wasAborted && obj.RunMethodOnFinish
+ obj.run()
+ end
+ if obj.DestroyOnFinish
+ delete(obj)
+ end
+ end
+
+ function onOptionsEditorClosed(obj) %#ok
+ %onOptionsEditorClosed Called just before run/destroy. Subclasses
+ % override to clean up preview state (e.g. delete grid overlays).
+ end
+
+ function onOptionsChanged(obj, name, value)
+ %onOptionsChanged Called when the options editor changes a value.
+ obj.updateOptionValue(name, value)
+ end
+
+ function relocatePrimaryApp(obj, hPlugin, direction)
+ %relocatePrimaryApp Shift app and plugin windows so they do not overlap.
+ if nargin < 3; direction = 'horizontal'; end
+ figPos(1,:) = getpixelposition(hPlugin.Figure);
+ figPos(2,:) = getpixelposition(obj.PrimaryApp.Figure);
+ hFig = [hPlugin.Figure, obj.PrimaryApp.Figure];
+ switch direction
+ case 'horizontal'; figPos_ = figPos(:, [1,3]); dim = 1;
+ case 'vertical'; figPos_ = figPos(:, [2,4]); dim = 2;
+ end
+ screenPos = uim.utility.getCurrentScreenSize(obj.PrimaryApp.Figure);
+ [~, idx] = sort(figPos_(:, 1));
+ figPos = figPos(idx, :);
+ hFig = hFig(idx);
+ [x, ~] = uim.utility.layout.subdividePosition(min(figPos(:,1)), ...
+ screenPos(dim+2), figPos_(:,2), 20);
+ for i = 1:2
+ hFig(i).Position(dim) = x(i);
+ end
+ end
+
+ function options = getCurrentOptions(obj)
+ %getCurrentOptions Return the current method options.
+ options = obj.Options;
+ end
+
+ function setCurrentOptions(obj, options)
+ %setCurrentOptions Assign the current method options.
+ obj.Options = options;
+ end
+
+ function updateOptionValue(obj, name, value)
+ superFields = fieldnames(obj.Options);
+
+ for i = 1:numel(superFields)
+ thisField = superFields{i};
+ if isfield(obj.Options.(thisField), name)
+ obj.Options.(thisField).(name) = value;
+ end
+ end
+ end
+ end
+
+ methods
+ function set.Options(obj, options)
+ obj.Options_ = options;
+ end
+
+ function options = get.Options(obj)
+ if ~isempty(obj.Options_)
+ options = obj.Options_;
+ elseif ~isempty(obj.OptionsManager)
+ options = obj.OptionsManager.Options;
+ else
+ options = struct.empty;
+ end
+ end
+ end
+end
diff --git a/code/graphics/+applify/+mixin/UserSettings.m b/code/graphics/+applify/+mixin/UserSettings.m
index 48716363..6102e09d 100644
--- a/code/graphics/+applify/+mixin/UserSettings.m
+++ b/code/graphics/+applify/+mixin/UserSettings.m
@@ -192,65 +192,27 @@ function editSettings(obj)
%editSettings Open gui for editing fields of settings.
titleStr = sprintf('Preferences for %s', class(obj));
- doDefault = true; % backward compatibility...
-
+
if ~isempty(obj.hSettingsEditor)
figure(obj.hSettingsEditor.Figure)
return
end
-
- % Todo: If obj has plugins, grab all settings from plugin
- % classes as well.
- if isprop(obj, 'plugins')
-
- hObjects = [{obj}, {obj.plugins.pluginHandle}];
- hasSettings = cellfun(@(h) isprop(h, 'settings'), hObjects);
-
- if sum(hasSettings) > 1
-
- hObjects = hObjects(hasSettings);
- settingsStruct = cellfun(@(h) h.settings, hObjects, 'uni', 0);
- names = cellfun(@(h) class(h), hObjects, 'uni', 0);
- callbacks = cellfun(@(h) @(name, value) h.changeSettings(name, value), hObjects, 'uni', 0);
-
- settingsStruct = tools.editStruct(settingsStruct, nan, titleStr, ...
- 'Callback', callbacks, 'Name', names);
-
- obj.settings = settingsStruct{1};
- obj.saveSettings()
- for i = 1:numel(hObjects)
- hObjects{i}.settings = settingsStruct{i};
- hObjects{i}.saveSettings()
- end
- doDefault = false;
- else
- doDefault = true;
- end
- end
-
- if doDefault
- try
- obj.hSettingsEditor = structeditor.App(obj.settings, ...
- 'Title', titleStr, 'Callback', @obj.onSettingsChanged);
-
- addlistener(obj.hSettingsEditor, 'AppDestroyed', ...
- @(s, e) obj.onSettingsEditorClosed);
- catch ME
-
- switch ME.identifier
- case 'MATLAB:class:InvalidSuperClass'
-
- if contains(ME.message, 'uiw.mixin.AssignPVPairs')
- msg = 'Settings window requires the Widgets Toolbox to be installed';
- errordlg(msg)
- error(msg)
- end
-
- otherwise
- rethrow(ME)
-
- end
+ try
+ obj.hSettingsEditor = structeditor.App(obj.settings, ...
+ 'Title', titleStr, 'Callback', @obj.onSettingsChanged);
+ addlistener(obj.hSettingsEditor, 'AppDestroyed', ...
+ @(s, e) obj.onSettingsEditorClosed);
+ catch ME
+ switch ME.identifier
+ case 'MATLAB:class:InvalidSuperClass'
+ if contains(ME.message, 'uiw.mixin.AssignPVPairs')
+ msg = 'Settings window requires the Widgets Toolbox to be installed';
+ errordlg(msg)
+ error(msg)
+ end
+ otherwise
+ rethrow(ME)
end
end
end
diff --git a/code/graphics/+applify/AppWithPlugin.m b/code/graphics/+applify/AppWithPlugin.m
index 373e7df6..61a8bab5 100644
--- a/code/graphics/+applify/AppWithPlugin.m
+++ b/code/graphics/+applify/AppWithPlugin.m
@@ -67,14 +67,14 @@ function deletePlugin(obj, pluginName)
end
function wasCaptured = sendKeyEventToPlugins(obj, ~, evt)
- %sendKeyEventToPlugins Send a key event to plugins
+ %sendKeyEventToPlugins Send a key press event to plugins
%
% The key event is sent to the plugins in the order they have
% been added. If a plugin captures the key event, this method
% returns before sending the event to the remaining plugins.
-
+
wasCaptured = false;
-
+
for i = 1:numel(obj.Plugins)
try
wasCaptured = obj.Plugins(i).keyPressHandler([], evt);
@@ -84,5 +84,24 @@ function deletePlugin(obj, pluginName)
end
end
end
+
+ function wasCaptured = sendKeyReleaseEventToPlugins(obj, ~, evt)
+ %sendKeyReleaseEventToPlugins Send a key release event to plugins
+ %
+ % The key release event is sent to the plugins in the order they
+ % have been added. If a plugin captures the event, this method
+ % returns before sending the event to the remaining plugins.
+
+ wasCaptured = false;
+
+ for i = 1:numel(obj.Plugins)
+ try
+ wasCaptured = obj.Plugins(i).keyReleasedHandler([], evt);
+ if wasCaptured; return; end
+ catch ME
+ warning( [ME.message, '\n'] )
+ end
+ end
+ end
end
end
diff --git a/code/wrappers/+nansen/+plugin/+imviewer/EXTRACT.m b/code/wrappers/+nansen/+plugin/+imviewer/EXTRACT.m
index e40aef0b..0d147f18 100644
--- a/code/wrappers/+nansen/+plugin/+imviewer/EXTRACT.m
+++ b/code/wrappers/+nansen/+plugin/+imviewer/EXTRACT.m
@@ -1,4 +1,4 @@
-classdef EXTRACT < imviewer.ImviewerPlugin
+classdef EXTRACT < imviewer.ImviewerPlugin & applify.mixin.ModalMethodPreviewController
%EXTRACT Imviewer plugin for EXTRACT method
%
% SYNTAX:
@@ -6,11 +6,6 @@
%
% extractPlugin = EXTRACT(imviewerObj, optionsManagerObj)
- properties (Constant, Hidden = true)
- USE_DEFAULT_SETTINGS = false % Ignore settings file
- DEFAULT_SETTINGS = [] % This class uses an optionsmanager
- end
-
properties (Constant)
Name = 'EXTRACT'
end
@@ -62,18 +57,11 @@ function delete(obj)
function openControlPanel(obj, mode)
obj.plotGrid()
- obj.editSettings()
- end
-
- function loadSettings(~)
- % This class does not have to load settings
- end
- function saveSettings(~)
- % This class does not have to save settings
+ obj.editOptions()
end
- function changeSetting(obj, name, value)
- obj.onSettingsChanged(name, value)
+ function changeOption(obj, name, value)
+ obj.onOptionsChanged(name, value)
end
function showTip(obj, message)
@@ -90,39 +78,39 @@ function onPluginActivated(obj)
end
- function onSettingsChanged(obj, name, value)
+ function onOptionsChanged(obj, name, value)
switch name
case {'num_partitions_x', 'num_partitions_y'}
- obj.settings.Main.(name) = value;
+ obj.Options.Main.(name) = value;
obj.plotGrid()
obj.checkGridSize()
case 'use_gpu'
- obj.settings.Main.(name) = value;
+ obj.Options.Main.(name) = value;
if value && ismac
obj.showTip('Note: GPU acceleration with Parallel Computing Toolbox is not supported on macOS versions 10.14 (Mojave) and above. Support for earlier macOS versions will be removed in a future MATLAB release.')
end
case 'avg_cell_radius'
- obj.settings.Main.(name) = value;
+ obj.Options.Main.(name) = value;
obj.plotCellTemplates(value)
case 'temporal_denoising'
- obj.settings.Preprocess.(name) = value;
+ obj.Options.Preprocess.(name) = value;
if value
obj.showTip('Note: This might increase processing time considerably for long movies')
end
case 'reestimate_S_if_downsampled'
- obj.settings.Downsample.(name) = value;
+ obj.Options.Downsample.(name) = value;
if value
obj.showTip('This is not recommended as precise shape of cell images are typically not essential, and processing will take longer')
end
case 'trace_output_option'
- obj.settings.Main.(name) = value;
+ obj.Options.Main.(name) = value;
if strcmp(value, 'raw')
obj.showTip('Please check EXTRACT''s FAQ before using this options')
@@ -131,12 +119,6 @@ function onSettingsChanged(obj, name, value)
end
end
- methods (Static) % Inherited...
- function getPluginIcon()
-
- end
- end
-
methods (Access = private)
function plotGrid(obj)
@@ -147,8 +129,8 @@ function plotGrid(obj)
end
% Plot grid lines
- numRows = obj.settings.Main.num_partitions_y;
- numCols = obj.settings.Main.num_partitions_x;
+ numRows = obj.Options.Main.num_partitions_y;
+ numCols = obj.Options.Main.num_partitions_x;
hLine = imviewer.plot.plotGridLines(obj.PrimaryApp, numRows, numCols);
@@ -206,8 +188,8 @@ function plotCellTemplates(obj, radius)
function checkGridSize(obj)
- numRows = obj.settings.Main.num_partitions_y;
- numCols = obj.settings.Main.num_partitions_x;
+ numRows = obj.Options.Main.num_partitions_y;
+ numCols = obj.Options.Main.num_partitions_x;
sizeX = obj.PrimaryApp.imWidth ./ numRows;
sizeY = obj.PrimaryApp.imHeight ./ numCols;
diff --git a/code/wrappers/+nansen/+plugin/+imviewer/FlowRegistration.m b/code/wrappers/+nansen/+plugin/+imviewer/FlowRegistration.m
index 0bbee4b7..b5ed486c 100644
--- a/code/wrappers/+nansen/+plugin/+imviewer/FlowRegistration.m
+++ b/code/wrappers/+nansen/+plugin/+imviewer/FlowRegistration.m
@@ -1,4 +1,4 @@
-classdef FlowRegistration < imviewer.ImviewerPlugin & nansen.processing.MotionCorrectionPreview
+classdef FlowRegistration < imviewer.ImviewerPlugin & applify.mixin.ModalMethodPreviewController & nansen.processing.MotionCorrectionPreview
%FlowRegistration Imviewer plugin for FlowRegistration method
%
% SYNTAX:
@@ -8,11 +8,6 @@
% Todo: Use methods of flowreg processor to run prealigning?
- properties (Constant, Hidden = true)
- USE_DEFAULT_SETTINGS = false % Ignore settings file
- DEFAULT_SETTINGS = struct.empty;
- end
-
properties (Constant)
Name = 'Flow Registration' % Name of plugin
end
@@ -50,20 +45,20 @@ function delete(obj)
methods
- function sEditor = openSettingsEditor(obj)
- %openSettingsEditor Open editor for method options.
+ function sEditor = openOptionsEditor(obj)
+ %openOptionsEditor Open editor for method options.
- % Update folder- and filename in settings.
+ % Update folder- and filename in options.
[folderPath, fileName] = fileparts( obj.ImviewerObj.ImageStack.FileName );
folderPath = fullfile(folderPath, obj.TargetFolderName);
% Prepare default filename
fileName = obj.buildFilenameWithExtension(fileName);
- obj.settings_.Export.SaveDirectory = folderPath;
- obj.settings_.Export.FileName = fileName;
+ obj.Options_.Export.SaveDirectory = folderPath;
+ obj.Options_.Export.FileName = fileName;
- sEditor = openSettingsEditor@imviewer.ImviewerPlugin(obj);
+ sEditor = openOptionsEditor@applify.mixin.ModalMethodPreviewController(obj);
% Need a better solution for this:
idx = strcmp(sEditor.Name, 'Export');
@@ -76,7 +71,7 @@ function delete(obj)
function openControlPanel(obj)
obj.initializeGaussianFilter()
- obj.editSettings()
+ obj.editOptions()
end
function run(obj)
@@ -86,10 +81,10 @@ function run(obj)
function runTestAlign(obj)
% Check if saveResult or showResults is selected
- obj.assertPreviewSettingsValid()
+ obj.assertPreviewOptionsValid()
% Prepare save directory
- if obj.settings.Preview.saveResults
+ if obj.Options.Preview.saveResults
[saveFolder, datePrefix] = obj.prepareSaveFolder();
if isempty(saveFolder); return; end
end
@@ -101,7 +96,7 @@ function runTestAlign(obj)
%stackSize = size(Y);
import nansen.wrapper.flowreg.*
- options = Options.convert(obj.settings);
+ options = Options.convert(obj.Options);
if ~isa(Y, 'single') || ~isa(Y, 'double')
Y = single(Y);
@@ -123,13 +118,13 @@ function runTestAlign(obj)
obj.ImviewerObj.clearMessage;
% Show results from test aligning:
- if obj.settings.Preview.showResults
+ if obj.Options.Preview.showResults
h = imviewer(M);
h.stackname = sprintf('%s - %s', obj.ImviewerObj.stackname, 'Flowreg Test Correction');
end
% Save results from test aligning:
- if obj.settings.Preview.saveResults
+ if obj.Options.Preview.saveResults
getSavepath = @(name) fullfile(saveFolder, ...
sprintf('%s_%s', datePrefix, name ) );
@@ -146,7 +141,7 @@ function runAlign(obj)
dataSet = obj.prepareTargetDataset();
nansen.wrapper.flowreg.Processor(obj.ImviewerObj.ImageStack, ...
- obj.settings, 'DataIoModel', dataSet)
+ obj.Options, 'DataIoModel', dataSet)
end
end
@@ -157,8 +152,8 @@ function createSubMenu(obj)
% Nothing here yet
end
- function onSettingsEditorClosed(obj)
- %onSettingsEditorClosed "Callback" for when settings editor exits
+ function onOptionsEditorClosed(obj)
+ %onOptionsEditorClosed "Callback" for when options editor exits
obj.resetGaussianFilter()
end
@@ -166,7 +161,7 @@ function assignDefaultOptions(obj)
functionName = 'nansen.wrapper.flowreg.Processor';
obj.OptionsManager = nansen.manage.OptionsManager(functionName);
- obj.settings = obj.OptionsManager.getOptions;
+ obj.Options = obj.OptionsManager.getOptions;
end
end
@@ -174,7 +169,7 @@ function assignDefaultOptions(obj)
function initializeGaussianFilter(obj)
obj.ImviewerObj.imageDisplayMode.filter = 'gauss3d';
- obj.ImviewerObj.imageDisplayMode.filterParam = struct('sigma', obj.settings.General.sigmaX);
+ obj.ImviewerObj.imageDisplayMode.filterParam = struct('sigma', obj.Options.General.sigmaX);
obj.ImviewerObj.updateImage();
obj.ImviewerObj.updateImageDisplay();
end
@@ -287,39 +282,39 @@ function updateResults(obj)
% Todo: Combine these into one method
- function onSettingsChanged(obj, name, value)
+ function onOptionsChanged(obj, name, value)
- % Call superclass method to deal with settings that are
- % general motion correction settings.
- onSettingsChanged@nansen.processing.MotionCorrectionPreview(obj, name, value)
+ % Call superclass method to deal with options that are
+ % general motion correction options.
+ onOptionsChanged@nansen.processing.MotionCorrectionPreview(obj, name, value)
switch name
case 'symmetricKernel'
- obj.settings.General.symmetricKernel = value;
+ obj.Options.General.symmetricKernel = value;
case {'sigmaX', 'sigmaY'}
- if obj.settings.General.symmetricKernel
- obj.settings.General.sigmaX = value;
- obj.settings.General.sigmaY = value;
+ if obj.Options.General.symmetricKernel
+ obj.Options.General.sigmaX = value;
+ obj.Options.General.sigmaY = value;
else
- obj.settings.General.(name) = value;
+ obj.Options.General.(name) = value;
end
- obj.settings.Model.sigma = [obj.settings.General.sigmaY, obj.settings.General.sigmaX, obj.settings.General.sigmaZ];
- obj.ImviewerObj.imageDisplayMode.filterParam = struct('sigma', obj.settings.Model.sigma);
+ obj.Options.Model.sigma = [obj.Options.General.sigmaY, obj.Options.General.sigmaX, obj.Options.General.sigmaZ];
+ obj.ImviewerObj.imageDisplayMode.filterParam = struct('sigma', obj.Options.Model.sigma);
obj.ImviewerObj.updateImage();
obj.ImviewerObj.updateImageDisplay();
case 'sigmaZ'
- obj.settings.sigma = [obj.settings.General.sigmaX, obj.settings.General.sigmaY, obj.settings.General.sigmaZ];
- obj.ImviewerObj.imageDisplayMode.filterParam = struct('sigma', obj.settings.Model.sigma);
+ obj.Options.sigma = [obj.Options.General.sigmaX, obj.Options.General.sigmaY, obj.Options.General.sigmaZ];
+ obj.ImviewerObj.imageDisplayMode.filterParam = struct('sigma', obj.Options.Model.sigma);
obj.ImviewerObj.updateImage();
obj.ImviewerObj.updateImageDisplay();
case 'FileName'
- obj.settings.Export.FileName = value;
- %obj.settings_.Export.FileName = value;
+ obj.Options.Export.FileName = value;
+ %obj.Options_.Export.FileName = value;
end
end
end
diff --git a/code/wrappers/+nansen/+plugin/+imviewer/FluFinder.m b/code/wrappers/+nansen/+plugin/+imviewer/FluFinder.m
index a865991a..b1a926e6 100644
--- a/code/wrappers/+nansen/+plugin/+imviewer/FluFinder.m
+++ b/code/wrappers/+nansen/+plugin/+imviewer/FluFinder.m
@@ -1,4 +1,4 @@
-classdef FluFinder < imviewer.ImviewerPlugin
+classdef FluFinder < imviewer.ImviewerPlugin & applify.mixin.ModalMethodPreviewController
%FluFinder Imviewer plugin for FluFinder autosegmentation method
%
% SYNTAX:
@@ -6,11 +6,6 @@
%
% flufinderPlugin = FluFinder(imviewerObj, optionsManagerObj)
- properties (Constant, Hidden = true)
- USE_DEFAULT_SETTINGS = false % Ignore settings file
- DEFAULT_SETTINGS = [] % This class uses an optionsmanager
- end
-
properties (Constant)
Name = 'FluFinder'
end
@@ -71,18 +66,11 @@ function delete(obj)
methods
function openControlPanel(obj, mode)
- obj.editSettings()
- end
-
- function loadSettings(~)
- % This class does not have to load settings
- end
- function saveSettings(~)
- % This class does not have to save settings
+ obj.editOptions()
end
- function changeSetting(obj, name, value)
- obj.onSettingsChanged(name, value)
+ function changeOption(obj, name, value)
+ obj.onOptionsChanged(name, value)
end
function showTip(obj, message)
@@ -101,7 +89,7 @@ function addPreviewOptions(obj)
S.Show = 'Preprocessed';
S.Show_ = {'Preprocessed', 'Binarized'};
- obj.settings_.Preview = S;
+ obj.Options_.Preview = S;
end
@@ -109,51 +97,45 @@ function onPluginActivated(obj)
end
- function onSettingsChanged(obj, name, value)
+ function onOptionsChanged(obj, name, value)
switch name
case 'RoiDiameter'
- obj.settings.General.RoiDiameter = value;
+ obj.Options.General.RoiDiameter = value;
obj.plotCellTemplates(value/2)
case 'Show'
- obj.settings.Preview.Show = value;
+ obj.Options.Preview.Show = value;
obj.changeImageToDisplay();
case 'PrctileForBinarization'
- obj.settings.Preprocessing.PrctileForBinarization = value;
+ obj.Options.Preprocessing.PrctileForBinarization = value;
- if strcmp(obj.settings.Preview.Show, 'Binarized')
+ if strcmp(obj.Options.Preview.Show, 'Binarized')
obj.updateImviewerDisplay()
end
case 'PrctileForBaseline'
- obj.settings.Preprocessing.PrctileForBaseline = value;
+ obj.Options.Preprocessing.PrctileForBaseline = value;
obj.updateBackgroundImage()
obj.BackgroundOffset = 0;
- if strcmp(obj.settings.Preview.Show, 'Static Background')
+ if strcmp(obj.Options.Preview.Show, 'Static Background')
obj.showImageInImviewer(obj.StaticBackground, 'Static Background')
- elseif strcmp(obj.settings.Preview.Show, 'Preprocessed')
+ elseif strcmp(obj.Options.Preview.Show, 'Preprocessed')
obj.updateImviewerDisplay()
end
case 'SmoothingSigma'
- obj.settings.Preprocessing.SmoothingSigma = value;
+ obj.Options.Preprocessing.SmoothingSigma = value;
obj.BackgroundOffset = 0;
- if strcmp(obj.settings.Preview.Show, 'Preprocessed')
+ if strcmp(obj.Options.Preview.Show, 'Preprocessed')
obj.updateImviewerDisplay()
end
end
end
end
- methods (Static) % Inherited...
- function getPluginIcon()
-
- end
- end
-
methods (Access = private)
function changeImageToDisplay(obj)
@@ -161,7 +143,7 @@ function changeImageToDisplay(obj)
hRoimanager = obj.PrimaryApp.getPluginHandle('Roimanager');
imArray = hRoimanager.prepareImagedata();
- switch obj.settings.Preview.Show
+ switch obj.Options.Preview.Show
case 'Preprocessed'
updateFcn = @obj.getPreprocessedImage;
@@ -186,7 +168,7 @@ function changeImageToDisplay(obj)
function imArray = getPreprocessedImageArray(obj, imArray)
import nansen.wrapper.abstract.OptionsAdapter
- opts = OptionsAdapter.ungroupOptions(obj.settings);
+ opts = OptionsAdapter.ungroupOptions(obj.Options);
if isempty(obj.CachePreprocessed)
imArray = flufinder.module.preprocessImages(imArray, opts);
@@ -199,7 +181,7 @@ function changeImageToDisplay(obj)
function imArray = getBinarizedImageArray(obj, imArray)
import nansen.wrapper.abstract.OptionsAdapter
- opts = OptionsAdapter.ungroupOptions(obj.settings);
+ opts = OptionsAdapter.ungroupOptions(obj.Options);
if isempty(obj.CacheBinarized)
imArray = obj.getPreprocessedImageArray(imArray);
@@ -212,7 +194,7 @@ function changeImageToDisplay(obj)
end
function image = getPreprocessedImage(obj, image)
- opts = obj.getUngroupedSettings();
+ opts = obj.getUngroupedOptions();
% Preprocess (subtract dynamic background)
optsNames = {'SpatialFilterType', 'SmoothingSigma'};
@@ -238,7 +220,7 @@ function changeImageToDisplay(obj)
function image = getBinarizedImage(obj, image)
- opts = obj.getUngroupedSettings();
+ opts = obj.getUngroupedOptions();
imageType = class(image);
image = obj.getPreprocessedImage(image);
@@ -262,7 +244,7 @@ function updateBackgroundImage(obj)
import flufinder.preprocess.computeStaticBackgroundImage
imageArray = obj.getImageArray();
- opts = obj.getUngroupedSettings();
+ opts = obj.getUngroupedOptions();
bgImage = computeStaticBackgroundImage(imageArray, opts);
@@ -333,9 +315,9 @@ function plotCellTemplates(obj, radius)
imArray = hRoimanager.prepareImagedata();
end
- function opts = getUngroupedSettings(obj)
+ function opts = getUngroupedOptions(obj)
import nansen.wrapper.abstract.OptionsAdapter
- opts = OptionsAdapter.ungroupOptions(obj.settings);
+ opts = OptionsAdapter.ungroupOptions(obj.Options);
end
end
end
diff --git a/code/wrappers/+nansen/+plugin/+imviewer/NoRMCorre.m b/code/wrappers/+nansen/+plugin/+imviewer/NoRMCorre.m
index 442b837b..99993509 100644
--- a/code/wrappers/+nansen/+plugin/+imviewer/NoRMCorre.m
+++ b/code/wrappers/+nansen/+plugin/+imviewer/NoRMCorre.m
@@ -1,4 +1,4 @@
-classdef NoRMCorre < imviewer.ImviewerPlugin & nansen.processing.MotionCorrectionPreview
+classdef NoRMCorre < imviewer.ImviewerPlugin & applify.mixin.ModalMethodPreviewController & nansen.processing.MotionCorrectionPreview
%NoRMCorre Imviewer plugin for NoRMCorre method
%
% SYNTAX:
@@ -9,22 +9,14 @@
% INHERITANCE:
% |- imviewer.ImviewerPlugin
% |- applify.mixin.AppPlugin
-% |- applify.mixin.UserSettings
% |- matlab.mixin.Heterogeneous
% |- uiw.mixin.AssignPVPairs
% TODO:
-% [v] Subclass from imviewer plugin class.
% [ ] migrate plugin to new instance if results open in new window
-% [v] Implement options based on OptionsManager & normcorre options.
% [ ] Should it have a DataIoModel property? Then its easy to plug in
% whatever model (i.e) a session model and save data consistently.
- properties (Constant, Hidden = true)
- USE_DEFAULT_SETTINGS = false % Ignore settings file
- DEFAULT_SETTINGS = [] % This class uses an optionsmanager
- end
-
properties (Constant) % Implementation of AppPlugin property
Name = 'NoRMCorre'
end
@@ -51,7 +43,7 @@
obj@imviewer.ImviewerPlugin(varargin{:})
- if ~ obj.PartialConstruction && isempty(obj.hSettingsEditor)
+ if ~ obj.PartialConstruction && isempty(obj.hOptionsEditor)
obj.openControlPanel()
end
@@ -72,12 +64,6 @@ function delete(obj)
end
end
- function loadSettings(~) % override to do nothing
- % This class does not have to load settings
- end
- function saveSettings(~) % override to do nothing
- % This class does not have to save settings
- end
end
methods (Access = protected) % Plugin derived methods
@@ -91,8 +77,8 @@ function createSubMenu(obj)
obj.MenuItem(1).PlotShifts.Callback = @obj.plotResults;
end
- function onSettingsEditorClosed(obj)
- %onSettingsEditorClosed "Callback" for when settings editor exits
+ function onOptionsEditorClosed(obj)
+ %onOptionsEditorClosed "Callback" for when options editor exits
delete(obj.hGridLines)
delete(obj.hGridOverlaps)
end
@@ -100,26 +86,26 @@ function onSettingsEditorClosed(obj)
function assignDefaultOptions(obj)
functionName = 'nansen.wrapper.normcorre.Processor';
obj.OptionsManager = nansen.manage.OptionsManager(functionName);
- obj.settings = obj.OptionsManager.getOptions;
+ obj.Options = obj.OptionsManager.getOptions;
end
end
methods % Methods for running normcorre motion correction
- function sEditor = openSettingsEditor(obj)
- %openSettingsEditor Open editor for method options.
+ function sEditor = openOptionsEditor(obj)
+ %openOptionsEditor Open editor for method options.
- % Update folder- and filename in settings.
+ % Update folder- and filename in options.
[folderPath, fileName] = fileparts( obj.ImviewerObj.ImageStack.FileName );
folderPath = fullfile(folderPath, obj.TargetFolderName);
% Prepare default filename
fileName = obj.buildFilenameWithExtension(fileName);
- obj.settings_.Export.SaveDirectory = folderPath;
- obj.settings_.Export.FileName = fileName;
+ obj.Options_.Export.SaveDirectory = folderPath;
+ obj.Options_.Export.FileName = fileName;
- sEditor = openSettingsEditor@imviewer.ImviewerPlugin(obj);
+ sEditor = openOptionsEditor@applify.mixin.ModalMethodPreviewController(obj);
% Need a better solution for this:
idx = strcmp(sEditor.Name, 'Export');
@@ -132,7 +118,7 @@ function assignDefaultOptions(obj)
function openControlPanel(obj)
obj.plotGrid()
- obj.editSettings()
+ obj.editOptions()
end
function run(obj)
@@ -146,10 +132,10 @@ function runTestAlign(obj)
% Run a motion correction processor on frames instead?
% Check if saveResult or showResults is selected
- obj.assertPreviewSettingsValid()
+ obj.assertPreviewOptionsValid()
% Prepare save directory
- if obj.settings.Preview.saveResults
+ if obj.Options.Preview.saveResults
[saveFolder, datePrefix] = obj.prepareSaveFolder();
if isempty(saveFolder); return; end
end
@@ -161,7 +147,7 @@ function runTestAlign(obj)
stackSize = size(Y);
import nansen.wrapper.normcorre.*
- ncOptions = Options.convert(obj.settings, stackSize);
+ ncOptions = Options.convert(obj.Options, stackSize);
if ~isa(Y, 'single') || ~isa(Y, 'double')
Y = single(Y);
@@ -172,7 +158,7 @@ function runTestAlign(obj)
obj.ImviewerObj.displayMessage('Running NoRMCorre...')
warning('off', 'MATLAB:mir_warning_maybe_uninitialized_temporary')
- [M, ncShifts, ref] = normcorre_batch(Y, ncOptions);
+ [M, ncShifts, ~] = normcorre_batch(Y, ncOptions);
warning('on', 'MATLAB:mir_warning_maybe_uninitialized_temporary')
obj.TestResults(end+1).Shifts = ncShifts;
@@ -185,13 +171,13 @@ function runTestAlign(obj)
obj.ImviewerObj.clearMessage;
% Show results from test aligning:
- if obj.settings.Preview.showResults
+ if obj.Options.Preview.showResults
h = imviewer(M);
h.stackname = sprintf('%s - %s', obj.ImviewerObj.stackname, 'NoRMCorre Test Correction');
end
% Save results from test aligning:
- if obj.settings.Preview.saveResults
+ if obj.Options.Preview.saveResults
getSavepath = @(name) fullfile(saveFolder, ...
sprintf('%s_%s', datePrefix, name ) );
@@ -209,35 +195,35 @@ function runAlign(obj)
dataSet = obj.prepareTargetDataset();
nansen.wrapper.normcorre.Processor(obj.ImviewerObj.ImageStack,...
- obj.settings, 'DataIoModel', dataSet)
+ obj.Options, 'DataIoModel', dataSet)
end
end
methods (Access = protected)
- function onSettingsChanged(obj, name, value)
+ function onOptionsChanged(obj, name, value)
- % Call superclass method to deal with settings that are
- % general motion correction settings.
- onSettingsChanged@nansen.processing.MotionCorrectionPreview(obj, name, value)
+ % Call superclass method to deal with options that are
+ % general motion correction options.
+ onOptionsChanged@nansen.processing.MotionCorrectionPreview(obj, name, value)
- patchesFields = fieldnames(obj.settings.Configuration);
- templateFields = fieldnames(obj.settings.Template);
+ patchesFields = fieldnames(obj.Options.Configuration);
+ templateFields = fieldnames(obj.Options.Template);
switch name
% Note: this needs to go before the patchesfield!
case {'numRows', 'numCols', 'patchOverlap'}
- obj.settings.Configuration.(name) = value;
+ obj.Options.Configuration.(name) = value;
obj.plotGrid()
case patchesFields
- obj.settings.Configuration.(name) = value;
+ obj.Options.Configuration.(name) = value;
case templateFields
- obj.settings.Template.(name) = value;
+ obj.Options.Template.(name) = value;
case {'firstFrame', 'numFrames', 'saveResults', 'showResults'}
- obj.settings.Preview.(name) = value;
+ obj.Options.Preview.(name) = value;
case 'runAlign'
obj.runAlign()
@@ -252,8 +238,8 @@ function plotGrid(obj)
xLim = [1,obj.ImviewerObj.imWidth];
yLim = [1,obj.ImviewerObj.imHeight];
- numRows = obj.settings.Configuration.numRows;
- numCols = obj.settings.Configuration.numCols;
+ numRows = obj.Options.Configuration.numRows;
+ numCols = obj.Options.Configuration.numCols;
xPoints = linspace(xLim(1),xLim(2), numCols+1);
yPoints = linspace(yLim(1),yLim(2), numRows+1);
@@ -277,8 +263,8 @@ function plotGrid(obj)
set(obj.hGridLines, 'HitTest', 'off', 'Tag', 'NorRMCorre Gridlines');
xDataVert = cat(1, xDataVert, xDataVert);
- xDataVert(1:2, :) = xDataVert(1:2, :) - obj.settings.Configuration.patchOverlap(2)/2;
- xDataVert(3:4, :) = xDataVert(3:4, :) + obj.settings.Configuration.patchOverlap(2)/2;
+ xDataVert(1:2, :) = xDataVert(1:2, :) - obj.Options.Configuration.patchOverlap(2)/2;
+ xDataVert(3:4, :) = xDataVert(3:4, :) + obj.Options.Configuration.patchOverlap(2)/2;
yDataVert = cat(1, yDataVert, flipud(yDataVert));
h2 = patch(obj.ImviewerObj.Axes, xDataVert, yDataVert, 'w');
@@ -286,8 +272,8 @@ function plotGrid(obj)
xDataHorz = cat(1, xDataHorz, flipud(xDataHorz));
yDataHorz = cat(1, yDataHorz, yDataHorz);
- yDataHorz(1:2, :) = yDataHorz(1:2, :) - obj.settings.Configuration.patchOverlap(1)/2;
- yDataHorz(3:4, :) = yDataHorz(3:4, :) + obj.settings.Configuration.patchOverlap(1)/2;
+ yDataHorz(1:2, :) = yDataHorz(1:2, :) - obj.Options.Configuration.patchOverlap(1)/2;
+ yDataHorz(3:4, :) = yDataHorz(3:4, :) + obj.Options.Configuration.patchOverlap(1)/2;
h3 = patch(obj.ImviewerObj.Axes, xDataHorz, yDataHorz, 'w');
set(h3, 'FaceAlpha', 0.15, 'HitTest', 'off')
diff --git a/code/wrappers/+nansen/+plugin/+signalviewer/CaimanDeconvolution.m b/code/wrappers/+nansen/+plugin/+signalviewer/CaimanDeconvolution.m
index 01f43e14..f215ae43 100644
--- a/code/wrappers/+nansen/+plugin/+signalviewer/CaimanDeconvolution.m
+++ b/code/wrappers/+nansen/+plugin/+signalviewer/CaimanDeconvolution.m
@@ -1,18 +1,9 @@
-classdef CaimanDeconvolution < applify.mixin.AppPlugin % signalviewer plugin
-
- properties (Constant, Hidden = true)
- USE_DEFAULT_SETTINGS = false % Ignore settings file
- DEFAULT_SETTINGS = [] % This class uses an optionsmanager
- end
+classdef CaimanDeconvolution < applify.mixin.AppPlugin & applify.mixin.HasOptionsManager % signalviewer plugin
properties (Constant) % Implementation of AppPlugin property
Name = 'CaImAn Deconvolution'
end
- properties
- PrimaryAppName = 'Roi Signal Explorer'
- end
-
properties (Access = protected)
RoiSignalArray
end
@@ -34,7 +25,7 @@
obj.Mode = 'UpdateAll';
- obj.editSettings()
+ obj.editOptions()
end
@@ -56,24 +47,20 @@ function createSubMenu(obj)
% Todo: Open? Close? Toggle?
obj.MenuItem(1).ExploreDff = uimenu(m, 'Text', 'Deconvolve...', 'Enable', 'off');
- obj.MenuItem(1).PlotShifts.Callback = @obj.editSettings;
+ obj.MenuItem(1).PlotShifts.Callback = @obj.editOptions;
end
function assignDefaultOptions(obj)
- %functionName = 'ophys.twophoton.process.deconvolution.Caiman';
functionName = 'nansen.twophoton.roisignals.deconvolveDff';
obj.OptionsManager = nansen.manage.OptionsManager(functionName);
- obj.settings = obj.OptionsManager.getOptions;
end
end
-
+
methods (Access = protected)
-
- function onSettingsChanged(obj, name, value)
-
- obj.settings_.(name) = value;
- obj.RoiSignalArray.DeconvolutionOptions = obj.settings;
+
+ function onOptionsChanged(obj)
+ obj.RoiSignalArray.DeconvolutionOptions = obj.Options;
obj.PrimaryApp.displayMessage('Updating Deconvolved Signal...')
@@ -104,7 +91,7 @@ function updateInternal(obj)
dff_ = dff(isVisible);
xData_ = xData(isVisible);
- [dec, den, ~] = deconvolveDff(dff_, obj.settings);%, options)
+ [dec, den, ~] = deconvolveDff(dff_, obj.Options);
if isempty(obj.hLineDeconvolved)
yyaxis(hAx, 'right')
diff --git a/code/wrappers/+nansen/+plugin/+signalviewer/DffExplorer.m b/code/wrappers/+nansen/+plugin/+signalviewer/DffExplorer.m
index 4ddd4a55..ba5f12c7 100644
--- a/code/wrappers/+nansen/+plugin/+signalviewer/DffExplorer.m
+++ b/code/wrappers/+nansen/+plugin/+signalviewer/DffExplorer.m
@@ -1,18 +1,9 @@
-classdef DffExplorer < applify.mixin.AppPlugin % signalviewer plugin
-
- properties (Constant, Hidden = true)
- USE_DEFAULT_SETTINGS = false % Ignore settings file
- DEFAULT_SETTINGS = [] % This class uses an optionsmanager
- end
+classdef DffExplorer < applify.mixin.AppPlugin & applify.mixin.HasOptionsManager % signalviewer plugin
properties (Constant) % Implementation of AppPlugin property
Name = 'DFF Explorer'
end
- properties
- PrimaryAppName = 'Roi Signal Explorer'
- end
-
properties (Access = protected)
RoiSignalArray
end
@@ -26,7 +17,7 @@
obj.PrimaryApp.Figure.Name = 'DFF Explorer';
obj.RoiSignalArray = obj.PrimaryApp.RoiSignalArray;
- obj.editSettings()
+ obj.editOptions()
end
@@ -48,28 +39,21 @@ function createSubMenu(obj)
% Todo: Open? Close? Toggle?
obj.MenuItem(1).ExploreDff = uimenu(m, 'Text', 'Explore DFF', 'Enable', 'off');
- obj.MenuItem(1).PlotShifts.Callback = @obj.editSettings;
+ obj.MenuItem(1).PlotShifts.Callback = @obj.editOptions;
end
function assignDefaultOptions(obj)
functionName = 'nansen.twophoton.roisignals.computeDff';
obj.OptionsManager = nansen.manage.OptionsManager(functionName);
- obj.settings = obj.OptionsManager.getOptions;
end
end
-
+
methods (Access = protected)
-
- function onSettingsChanged(obj, name, value)
-
- obj.settings_.(name) = value;
-
- obj.RoiSignalArray.DffOptions = obj.settings;
+
+ function onOptionsChanged(obj)
+ obj.RoiSignalArray.DffOptions = obj.Options;
obj.RoiSignalArray.resetSignals('all', {'dff'})
-
- %obj.updateSignalPlot(obj.DisplayedRoiIndices, 'replace', {'dff'}, true);
-
end
end
end