Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
e16658c
Extract PointerManager from legacy plugins struct in imviewer.App
ehennestad Mar 27, 2026
d5b4204
Centralise plugin key dispatch and remove legacy plugins aggregation …
ehennestad Mar 27, 2026
efe1b23
Move composite settings aggregation into imviewer.App.editSettings
ehennestad Mar 27, 2026
a054f0e
Add HasOptionsManager mixin and migrate DffExplorer to it (Phase 4)
ehennestad Mar 27, 2026
60963d3
Shrink AppPlugin base contract (Phase 5)
ehennestad Mar 27, 2026
07f7561
Clean up ImviewerPlugin (Phase 6)
ehennestad Mar 27, 2026
e8ab434
Introduce ModalMethodPreviewPlugin; strip modal lifecycle from AppPlu…
ehennestad Mar 27, 2026
74dd931
Use options vocabulary consistently in ModalMethodPreviewPlugin
ehennestad Mar 27, 2026
d18d7a7
Refactor RoiClassifier as bridge plugin
ehennestad Mar 27, 2026
bb865bb
Refactor plugin options away from settings base
ehennestad Mar 27, 2026
d56f738
Clean up plugin base contract
ehennestad Mar 27, 2026
5c18128
Minor syntax fix
ehennestad Mar 28, 2026
b651ba3
Update DataMethod.m
ehennestad Mar 28, 2026
3d9bcb7
Update ModalMethodPreviewController.m
ehennestad Mar 28, 2026
6ded699
Update NoRMCorre.m
ehennestad Mar 28, 2026
0561761
fix: accept plugin arguments in RoiClassifier
ehennestad May 1, 2026
c9e1b60
Fix misc bugs
ehennestad May 1, 2026
0468fac
Update code/graphics/+applify/+mixin/ModalMethodPreviewController.m
ehennestad May 1, 2026
57bf399
Update GitHub badges [skip ci]
github-actions[bot] May 6, 2026
c1353d1
Merge branch 'dev' into dev-refactor-app-plugin
ehennestad May 8, 2026
4d5d826
Merge branch 'dev' into dev-refactor-app-plugin
ehennestad May 8, 2026
15e5f57
fix: move plugin options to options mixin
ehennestad May 9, 2026
5d6d219
Cleanup constructor args and docstrings for app plugin classes
ehennestad May 9, 2026
7f7e059
Add syntax examples in class level docstring
ehennestad May 9, 2026
49f13d1
Update GitHub badges [skip ci]
github-actions[bot] May 9, 2026
8ec7b23
Merge branch 'dev' into dev-refactor-app-plugin
ehennestad May 9, 2026
d22acfd
refactor: clarify modal preview plugin lifecycle
ehennestad May 9, 2026
0d88f6c
refactor: address code review feedback on plugin mixins
ehennestad May 9, 2026
1b687f3
Add unittests
ehennestad May 9, 2026
8dbdaab
Update App.m
ehennestad May 9, 2026
4069af8
Update RoiManager.m
ehennestad May 9, 2026
0e68fc5
Fix structeditor bug with nested structs containing config fields
ehennestad May 9, 2026
8a4148c
Update App.m
ehennestad May 9, 2026
d267a61
Visual improvements when running motion correction in imviewer
ehennestad May 9, 2026
529fd6e
fix: restore waitfor for option plugins
ehennestad May 9, 2026
ba473a9
Add confirmation on task complete
ehennestad May 9, 2026
7b86ac1
Merge branch 'dev' into dev-refactor-app-plugin
ehennestad May 9, 2026
9869e8b
Update GitHub badges [skip ci]
github-actions[bot] May 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/badges/code_issues.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion .github/badges/tests.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions code/+nansen/+manage/OptionsManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ function setOptions(obj, optionsName, options)
end
end

function hOptionsEditor = openOptionsEditor(obj, optionsName, optsStruct)
function hOptionsEditor = openOptionsEditor(obj, optionsName, optsStruct, varargin)
%openOptionsEditor Open options editor for current options.

if nargin < 2 || isempty(optionsName)
Expand All @@ -419,7 +419,8 @@ function setOptions(obj, optionsName, options)
hOptionsEditor = structeditor(optsStruct, ...
'OptionsManager', obj, ...
'Title', titleStr, ...
'Prompt', promptStr );
'Prompt', promptStr, ...
varargin{:} );

hOptionsEditor.changeOptionsSelectionDropdownValue(optionsName);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
% nansen.plugin.imviewer.NoRMCorre as subclasses?

properties (Abstract)
settings
Options
ImviewerObj
end

Expand All @@ -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
Expand All @@ -33,25 +33,25 @@ 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);
for i = 1:numel(defaultFields)
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
Expand All @@ -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'
Expand All @@ -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 );
Expand All @@ -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

Expand All @@ -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

Expand All @@ -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]);
Expand All @@ -156,17 +156,17 @@ 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

% % if mod( size(imArray,1), 2 ) ~= 0
% %
% % 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);
Expand Down
4 changes: 2 additions & 2 deletions code/+nansen/+processing/DataMethod.m
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
67 changes: 31 additions & 36 deletions code/apps/+imviewer/+plugin/@RoiClassifier/RoiClassifier.m
Original file line number Diff line number Diff line change
@@ -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)
function obj = RoiClassifier(imviewerApp, varargin)
%openRoiClassifier Open roiClassifier on request from imviewer

obj@applify.mixin.AppPlugin(imviewerApp)
obj@applify.mixin.AppBridgePlugin(imviewerApp, varargin{:})

% Find roimanager handle
success=false;
Expand Down Expand Up @@ -92,43 +83,47 @@
% Initialize roi classifier
hClsf = roiclassifier.App(roiGroup, 'tileUnits', 'scaled');
obj.ClassifierApp = hClsf;
obj.ClassifierDestroyedListener = addlistener(hClsf, ...
'ObjectBeingDestroyed', @(s,e) obj.onClassifierAppDestroyed());

success = true;
end
end

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
13 changes: 0 additions & 13 deletions code/apps/+imviewer/+plugin/@RoiClassifier/getDefaultSettings.m

This file was deleted.

20 changes: 9 additions & 11 deletions code/apps/+imviewer/+plugin/@RoiManager/RoiManager.m
Original file line number Diff line number Diff line change
@@ -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:
Expand Down Expand Up @@ -131,6 +131,7 @@
function obj = RoiManager(varargin)

obj@imviewer.ImviewerPlugin(varargin{:})
obj@applify.mixin.UserSettings()

% % [nvPairs, varargin] = utility.getnvpairs(varargin);
% %
Expand Down Expand Up @@ -671,12 +672,14 @@ function runAutoSegmentation(obj)

end

function openManualRoiClassifier(obj)
function openRoiClassifier(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
end % /function openRoiClassifier

function extractSignals(obj) % Todo: Use imageStackProcessors and external methods!

Expand Down Expand Up @@ -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'};

Expand Down Expand Up @@ -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');
Expand Down
Loading