diff --git a/urchin_compare/+Dev/VersionInfo.m b/urchin_compare/+Dev/VersionInfo.m new file mode 100644 index 0000000000000000000000000000000000000000..2002fa5fbb50dbde8291e29c1913bdaa6285cbb8 --- /dev/null +++ b/urchin_compare/+Dev/VersionInfo.m @@ -0,0 +1,10 @@ +% versionInfo = VersionInfo() +% Return the version info structure +% +% Note: This file is autogenerated by build script DO NOT MODIFY!! + +function versionInfo = VersionInfo() + jsonVer = '{"name":"urchin_compare","majorVersion":2,"minorVersion":0,"branchName":"master","buildNumber":"2020.12.07.07","buildMachine":"bioimage28","commitHash":[" : 15410c6b7251a6890057e04366431f0286b90d70"," : 07f725fe7b1c500a2743e016d119a7b0f059561e"]}'; + + versionInfo = Utils.ParseJSON(jsonVer); +end diff --git a/urchin_compare/.gitignore b/urchin_compare/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..4051d2a75c4484099154eca55f55295928e90b7c --- /dev/null +++ b/urchin_compare/.gitignore @@ -0,0 +1,6 @@ +# Ignore matlab data files +*.mat + +# Ignore some image formats +*.jpg +*.nd2 \ No newline at end of file diff --git a/urchin_compare/compile_urchin.m b/urchin_compare/compile_urchin.m new file mode 100644 index 0000000000000000000000000000000000000000..b1e40b68891aa442a13bc2b0bac0d7ff35cdb062 --- /dev/null +++ b/urchin_compare/compile_urchin.m @@ -0,0 +1,46 @@ +function compile_urchin() + %% General compiler setup: Deals with version updates and pulls external dependencies + initStruct = Dev.InitCompiler('urchin_compare','2.0'); + if ( isempty(initStruct) ) + % User exited the build due to error or uncommited dependencies. + return; + end + + compileMATLAB('urchin_compare','urchin_compare','', initStruct.javaList, initStruct.toolboxList); +end + +function outputFile = compileMATLAB(projectName, mainscript, outputdir, extrasList, toolboxList) + compileTime = tic(); + + outputFile = [projectName '.exe']; + + if ( ~exist('extrasList','var') ) + extrasList = {}; + end + + extraCommand = ''; + if ( ~isempty(extrasList) ) + extraElems = cellfun(@(x)([' -a ' x]),extrasList, 'UniformOutput',0); + extraCommand = [extraElems{:}]; + end + + if ( ~exist('toolboxList','var') ) + toolboxList = {}; + end + + toolboxAddCommand = ''; + if ( ~isempty(toolboxList) ) + toolboxElems = cellfun(@(x)([' -p "' x '"']), toolboxList, 'UniformOutput',0); + toolboxAddCommand = ['-N' toolboxElems{:}]; + end + + fprintf('\nMATLAB Compiling: %s...\n', outputFile); + result = system(['mcc -v -R -startmsg -m ' mainscript '.m -o ' projectName ' ' toolboxAddCommand extraCommand]); + if ( result ~= 0 ) + error([projectName ': MATLAB compile failed.']); + end + + system(['copy ' projectName '.exe ' fullfile(outputdir,'.')]); + + fprintf('Done (%f sec)\n', toc(compileTime)); +end diff --git a/urchin_compare/helpdlg.fig b/urchin_compare/helpdlg.fig new file mode 100644 index 0000000000000000000000000000000000000000..e48d4f55d39dda373ec42b2bf8b1e20511c2e014 Binary files /dev/null and b/urchin_compare/helpdlg.fig differ diff --git a/urchin_compare/helpdlg.m b/urchin_compare/helpdlg.m new file mode 100644 index 0000000000000000000000000000000000000000..c58ee283ab96fea102b203b2f4ab03ec27159adc --- /dev/null +++ b/urchin_compare/helpdlg.m @@ -0,0 +1,86 @@ +function varargout = helpdlg(varargin) +% HELPDLG MATLAB code for helpdlg.fig +% HELPDLG, by itself, creates a new HELPDLG or raises the existing +% singleton*. +% +% H = HELPDLG returns the handle to a new HELPDLG or the handle to +% the existing singleton*. +% +% HELPDLG('CALLBACK',hObject,eventData,handles,...) calls the local +% function named CALLBACK in HELPDLG.M with the given input arguments. +% +% HELPDLG('Property','Value',...) creates a new HELPDLG or raises the +% existing singleton*. Starting from the left, property value pairs are +% applied to the GUI before helpdlg_OpeningFcn gets called. An +% unrecognized property name or invalid value makes property application +% stop. All inputs are passed to helpdlg_OpeningFcn via varargin. +% +% *See GUI Options on GUIDE's Tools menu. Choose "GUI allows only one +% instance to run (singleton)". +% +% See also: GUIDE, GUIDATA, GUIHANDLES + +% Edit the above text to modify the response to help helpdlg + +% Last Modified by GUIDE v2.5 18-Jun-2019 03:23:18 + +% Begin initialization code - DO NOT EDIT +gui_Singleton = 1; +gui_State = struct('gui_Name', mfilename, ... + 'gui_Singleton', gui_Singleton, ... + 'gui_OpeningFcn', @helpdlg_OpeningFcn, ... + 'gui_OutputFcn', @helpdlg_OutputFcn, ... + 'gui_LayoutFcn', [] , ... + 'gui_Callback', []); +if nargin && ischar(varargin{1}) + gui_State.gui_Callback = str2func(varargin{1}); +end + +if nargout + [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:}); +else + gui_mainfcn(gui_State, varargin{:}); +end +% End initialization code - DO NOT EDIT + + +% --- Executes just before helpdlg is made visible. +function helpdlg_OpeningFcn(hObject, eventdata, handles, varargin) +% This function has no output args, see OutputFcn. +% hObject handle to figure +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) +% varargin command line arguments to helpdlg (see VARARGIN) + +% Choose default command line output for helpdlg +handles.output = hObject; + +% Update handles structure +guidata(hObject, handles); + +% UIWAIT makes helpdlg wait for user response (see UIRESUME) +% uiwait(handles.help_fig); + + +% --- Outputs from this function are returned to the command line. +function varargout = helpdlg_OutputFcn(hObject, eventdata, handles) +% varargout cell array for returning output args (see VARARGOUT); +% hObject handle to figure +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Get default command line output from handles structure +varargout{1} = handles.output; + + +% --- Executes on key press with focus on help_fig and none of its controls. +function help_fig_KeyPressFcn(hObject, eventdata, handles) +% hObject handle to help_fig (see GCBO) +% eventdata structure with the following fields (see MATLAB.UI.FIGURE) +% Key: name of the key that was pressed, in lower case +% Character: character interpretation of the key(s) that was pressed +% Modifier: name(s) of the modifier key(s) (i.e., control, shift) pressed +% handles structure with handles and user data (see GUIDATA) +if ( strcmpi(eventdata.Key,'escape') ) + close(hObject); +end diff --git a/urchin_compare/settings.json b/urchin_compare/settings.json new file mode 100644 index 0000000000000000000000000000000000000000..0a04ea2ea1046977c8663c03147abeecae8dbb84 --- /dev/null +++ b/urchin_compare/settings.json @@ -0,0 +1,8 @@ +{ + "imagePath" : "H:\\Smadar\\2020-10-Phallodin_Myo", + "dataPath" : "H:\\Smadar\\2020-10-Phallodin_Myo", + "channelColors" : [ + [0, 1, 0], + [1, 0, 0] + ] +} diff --git a/urchin_compare/toolsdlg.fig b/urchin_compare/toolsdlg.fig new file mode 100644 index 0000000000000000000000000000000000000000..be3cc86a14f3b050d531c93ae21c3d150b0c3cd6 Binary files /dev/null and b/urchin_compare/toolsdlg.fig differ diff --git a/urchin_compare/toolsdlg.m b/urchin_compare/toolsdlg.m new file mode 100644 index 0000000000000000000000000000000000000000..4c2151738a95f23da57048d96dd62efdccade42c --- /dev/null +++ b/urchin_compare/toolsdlg.m @@ -0,0 +1,537 @@ +function varargout = toolsdlg(varargin) +% TOOLSDLG MATLAB code for tools.fig +% TOOLSDLG, by itself, creates a new TOOLSDLG or raises the existing +% singleton*. +% +% H = TOOLSDLG returns the handle to a new TOOLSDLG or the handle to +% the existing singleton*. +% +% TOOLSDLG('CALLBACK',hObject,eventData,handles,...) calls the local +% function named CALLBACK in TOOLSDLG.M with the given input arguments. +% +% TOOLSDLG('Property','Value',...) creates a new TOOLSDLG or raises the +% existing singleton*. Starting from the left, property value pairs are +% applied to the GUI before toolsdlg_OpeningFcn gets called. An +% unrecognized property name or invalid value makes property application +% stop. All inputs are passed to toolsdlg_OpeningFcn via varargin. +% +% *See GUI Options on GUIDE's Tools menu. Choose "GUI allows only one +% instance to run (singleton)". +% +% See also: GUIDE, GUIDATA, GUIHANDLES + +% Edit the above text to modify the response to help toolsdlg + +% Last Modified by GUIDE v2.5 01-Dec-2020 18:35:44 + +% Begin initialization code - DO NOT EDIT +gui_Singleton = 1; +gui_State = struct('gui_Name', mfilename, ... + 'gui_Singleton', gui_Singleton, ... + 'gui_OpeningFcn', @toolsdlg_OpeningFcn, ... + 'gui_OutputFcn', @toolsdlg_OutputFcn, ... + 'gui_LayoutFcn', [] , ... + 'gui_Callback', []); +if nargin && ischar(varargin{1}) + gui_State.gui_Callback = str2func(varargin{1}); +end + +if nargout + [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:}); +else + gui_mainfcn(gui_State, varargin{:}); +end +% End initialization code - DO NOT EDIT + + +% --- Executes just before toolsdlg is made visible. +function toolsdlg_OpeningFcn(hObject, eventdata, handles, varargin) +% This function has no output args, see OutputFcn. +% hObject handle to figure +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) +% varargin command line arguments to toolsdlg (see VARARGIN) + +% Choose default command line output for toolsdlg +handles.output = hObject; + +% Update handles structure +guidata(hObject, handles); + +% UIWAIT makes toolsdlg wait for user response (see UIRESUME) +% uiwait(handles.tools_fig); + + +% --- Outputs from this function are returned to the command line. +function varargout = toolsdlg_OutputFcn(hObject, eventdata, handles) +% varargout cell array for returning output args (see VARARGOUT); +% hObject handle to figure +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Get default command line output from handles structure +varargout{1} = handles.output; + +% Helpers for updating editbox/sliders +function v = clamp(v, minv,maxv) + v = max(minv, min(v, maxv)); + +% --- Executes on slider movement. +function update_editbox(hTxt,hSlider) + val = get(hSlider, 'Value'); + if ( ~isempty(hTxt) ) + set(hTxt, 'String',num2str(val)); + end + +% --- Executes on slider movement. +function update_slider(hTxt,hSlider) + strval = get(hTxt, 'String'); + val = str2double(strval); + if ( ~isempty(hSlider) ) + val = clamp(val, get(hSlider,'Min'),get(hSlider,'Max')); + set(hSlider, 'Value',val); + end + +% --- Executes on slider movement. +function contrast_ch2_Callback(hObject, eventdata, handles) +% hObject handle to contrast_ch2 (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hints: get(hObject,'Value') returns position of slider +% get(hObject,'Min') and get(hObject,'Max') to determine range of slider +global UIInfo +hTxt = findobj('Tag','contrast_ch2_edit'); +update_editbox(hTxt,hObject); +UIInfo.updateFcn(); + + +% --- Executes during object creation, after setting all properties. +function contrast_ch2_CreateFcn(hObject, eventdata, handles) +% hObject handle to contrast_ch2 (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles empty - handles not created until after all CreateFcns called + +% Hint: slider controls usually have a light gray background. +if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor',[.9 .9 .9]); +end + + +% --- Executes on slider movement. +function bright_ch2_Callback(hObject, eventdata, handles) +% hObject handle to bright_ch2 (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hints: get(hObject,'Value') returns position of slider +% get(hObject,'Min') and get(hObject,'Max') to determine range of slider +global UIInfo +hTxt = findobj('Tag','bright_ch2_edit'); +update_editbox(hTxt,hObject); +UIInfo.updateFcn(); + + +% --- Executes during object creation, after setting all properties. +function bright_ch2_CreateFcn(hObject, eventdata, handles) +% hObject handle to bright_ch2 (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles empty - handles not created until after all CreateFcns called + +% Hint: slider controls usually have a light gray background. +if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor',[.9 .9 .9]); +end + + +% --- Executes on button press in color_ch2. +function color_ch2_Callback(hObject, eventdata, handles) +% hObject handle to color_ch2 (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) +global UIInfo +newC = uisetcolor(get(hObject, 'BackgroundColor')); +set(hObject, 'BackgroundColor', newC); +UIInfo.updateFcn(); + + +% --- Executes on button press in color_ch1. +function color_ch1_Callback(hObject, eventdata, handles) +% hObject handle to color_ch1 (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) +global UIInfo +newC = uisetcolor(get(hObject, 'BackgroundColor')); +set(hObject, 'BackgroundColor', newC); +UIInfo.updateFcn(); + + +% --- Executes on slider movement. +function bright_ch1_Callback(hObject, eventdata, handles) +% hObject handle to bright_ch1 (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hints: get(hObject,'Value') returns position of slider +% get(hObject,'Min') and get(hObject,'Max') to determine range of slider +global UIInfo +hTxt = findobj('Tag','bright_ch1_edit'); +update_editbox(hTxt,hObject); +UIInfo.updateFcn(); + + +% --- Executes during object creation, after setting all properties. +function bright_ch1_CreateFcn(hObject, eventdata, handles) +% hObject handle to bright_ch1 (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles empty - handles not created until after all CreateFcns called + +% Hint: slider controls usually have a light gray background. +if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor',[.9 .9 .9]); +end + + +% --- Executes on slider movement. +function contrast_ch1_Callback(hObject, eventdata, handles) +% hObject handle to contrast_ch1 (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hints: get(hObject,'Value') returns position of slider +% get(hObject,'Min') and get(hObject,'Max') to determine range of slider +global UIInfo +hTxt = findobj('Tag','contrast_ch1_edit'); +update_editbox(hTxt,hObject); +UIInfo.updateFcn(); + + +% --- Executes during object creation, after setting all properties. +function contrast_ch1_CreateFcn(hObject, eventdata, handles) +% hObject handle to contrast_ch1 (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles empty - handles not created until after all CreateFcns called + +% Hint: slider controls usually have a light gray background. +if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor',[.9 .9 .9]); +end + + +% --- Executes on button press in show_regions. +function show_regions_Callback(hObject, eventdata, handles) +% hObject handle to show_regions (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hint: get(hObject,'Value') returns toggle state of show_regions +global UIInfo +UIInfo.updateFcn(); + + +% --- Executes on button press in show_ch1px. +function show_ch1px_Callback(hObject, eventdata, handles) +% hObject handle to show_ch1px (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hint: get(hObject,'Value') returns toggle state of show_ch1px +global UIInfo +UIInfo.updateFcn(); + + +% --- Executes on button press in show_ch2px. +function show_ch2px_Callback(hObject, eventdata, handles) +% hObject handle to show_ch2px (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hint: get(hObject,'Value') returns toggle state of show_ch2px +global UIInfo +UIInfo.updateFcn(); + + +% --- Executes on slider movement. +function ch1_thresh_Callback(hObject, eventdata, handles) +% hObject handle to ch1_thresh (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hints: get(hObject,'Value') returns position of slider +% get(hObject,'Min') and get(hObject,'Max') to determine range of slider +global UIInfo +hTxt = findobj('Tag','ch1_thresh_edit'); +update_editbox(hTxt,hObject); +UIInfo.updateThresh(); + + +% --- Executes during object creation, after setting all properties. +function ch1_thresh_CreateFcn(hObject, eventdata, handles) +% hObject handle to ch1_thresh (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles empty - handles not created until after all CreateFcns called + +% Hint: slider controls usually have a light gray background. +if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor',[.9 .9 .9]); +end + + +% --- Executes on slider movement. +function ch2_thresh_Callback(hObject, eventdata, handles) +% hObject handle to ch2_thresh (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hints: get(hObject,'Value') returns position of slider +% get(hObject,'Min') and get(hObject,'Max') to determine range of slider +global UIInfo +hTxt = findobj('Tag','ch2_thresh_edit'); +update_editbox(hTxt,hObject); +UIInfo.updateThresh(); + + +% --- Executes during object creation, after setting all properties. +function ch2_thresh_CreateFcn(hObject, eventdata, handles) +% hObject handle to ch2_thresh (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles empty - handles not created until after all CreateFcns called + +% Hint: slider controls usually have a light gray background. +if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor',[.9 .9 .9]); +end + + +% --- Executes when selected cell(s) is changed in cells_table. +function cells_table_CellSelectionCallback(hObject, eventdata, handles) +% hObject handle to cells_table (see GCBO) +% eventdata structure with the following fields (see MATLAB.UI.CONTROL.TABLE) +% Indices: row and column indices of the cell(s) currently selecteds +% handles structure with handles and user data (see GUIDATA) +global UIInfo + +regionID = -1; +if ( ~isempty(eventdata.Indices) ) + Data = get(hObject, 'Data'); + regionID = Data{eventdata.Indices(1),1}; +end + +UIInfo.updateRegionSelect(regionID); + + +% --- Executes when entered data in editable cell(s) in cells_table. +function cells_table_CellEditCallback(hObject, eventdata, handles) +% hObject handle to cells_table (see GCBO) +% eventdata structure with the following fields (see MATLAB.UI.CONTROL.TABLE) +% Indices: row and column indices of the cell(s) edited +% PreviousData: previous data for the cell(s) edited +% EditData: string(s) entered by the user +% NewData: EditData or its converted form set on the Data property. Empty if Data was not changed +% Error: error string when failed to convert EditData to appropriate value for Data +% handles structure with handles and user data (see GUIDATA) +global UIInfo + +Data = get(hObject, 'Data'); + +editData = eventdata.EditData; +if ( ischar(editData) ) + %% Support empty if space-bar is used + editData = strtrim(editData); +end + +if ( isempty(editData) ) + %% Allow user to set empty data + Data{eventdata.Indices(1),eventdata.Indices(2)} = []; + set(hObject, 'Data',Data); +end +% % User modifiable count variable +% cellID = Data{eventdata.Indices(1),1}; +% newCount = Data{eventdata.Indices(1),eventdata.Indices(2)}; + +% UIInfo.updateCellCount(cellID,newCount); + + +% --- Executes when user attempts to close tools_fig. +function tools_fig_CloseRequestFcn(hObject, eventdata, handles) +% hObject handle to tools_fig (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hint: delete(hObject) closes the figure +global UIInfo +set(hObject, 'Visible','off'); +UIInfo.updateMenu(); + + + +function ch1_thresh_edit_Callback(hObject, eventdata, handles) +% hObject handle to ch1_thresh_edit (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hints: get(hObject,'String') returns contents of ch1_thresh_edit as text +% str2double(get(hObject,'String')) returns contents of ch1_thresh_edit as a double +global UIInfo +hSlider = findobj('Tag','ch1_thresh'); +update_slider(hObject,hSlider); +UIInfo.updateThresh(); + + +% --- Executes during object creation, after setting all properties. +function ch1_thresh_edit_CreateFcn(hObject, eventdata, handles) +% hObject handle to ch1_thresh_edit (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles empty - handles not created until after all CreateFcns called + +% Hint: edit controls usually have a white background on Windows. +% See ISPC and COMPUTER. +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end +hSlider = findobj('Tag','ch1_thresh'); +update_editbox(hObject,hSlider); + + + +function ch2_thresh_edit_Callback(hObject, eventdata, handles) +% hObject handle to ch2_thresh_edit (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hints: get(hObject,'String') returns contents of ch2_thresh_edit as text +% str2double(get(hObject,'String')) returns contents of ch2_thresh_edit as a double +global UIInfo +hSlider = findobj('Tag','ch2_thresh'); +update_slider(hObject,hSlider); +UIInfo.updateThresh(); + + +% --- Executes during object creation, after setting all properties. +function ch2_thresh_edit_CreateFcn(hObject, eventdata, handles) +% hObject handle to ch2_thresh_edit (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles empty - handles not created until after all CreateFcns called + +% Hint: edit controls usually have a white background on Windows. +% See ISPC and COMPUTER. +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end +hSlider = findobj('Tag','ch2_thresh'); +update_editbox(hObject,hSlider); + + + +function bright_ch2_edit_Callback(hObject, eventdata, handles) +% hObject handle to bright_ch2_edit (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hints: get(hObject,'String') returns contents of bright_ch2_edit as text +% str2double(get(hObject,'String')) returns contents of bright_ch2_edit as a double +global UIInfo +hSlider = findobj('Tag','bright_ch2'); +update_slider(hObject,hSlider); +UIInfo.updateFcn(); + + +% --- Executes during object creation, after setting all properties. +function bright_ch2_edit_CreateFcn(hObject, eventdata, handles) +% hObject handle to bright_ch2_edit (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles empty - handles not created until after all CreateFcns called + +% Hint: edit controls usually have a white background on Windows. +% See ISPC and COMPUTER. +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end +hSlider = findobj('Tag','bright_ch2'); +update_editbox(hObject,hSlider); + + + +function contrast_ch2_edit_Callback(hObject, eventdata, handles) +% hObject handle to contrast_ch2_edit (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hints: get(hObject,'String') returns contents of contrast_ch2_edit as text +% str2double(get(hObject,'String')) returns contents of contrast_ch2_edit as a double +global UIInfo +hSlider = findobj('Tag','contrast_ch2'); +update_slider(hObject,hSlider); +UIInfo.updateFcn(); + + +% --- Executes during object creation, after setting all properties. +function contrast_ch2_edit_CreateFcn(hObject, eventdata, handles) +% hObject handle to contrast_ch2_edit (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles empty - handles not created until after all CreateFcns called + +% Hint: edit controls usually have a white background on Windows. +% See ISPC and COMPUTER. +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end +hSlider = findobj('Tag','contrast_ch2'); +update_editbox(hObject,hSlider); + + +function bright_ch1_edit_Callback(hObject, eventdata, handles) +% hObject handle to bright_ch1_edit (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hints: get(hObject,'String') returns contents of bright_ch1_edit as text +% str2double(get(hObject,'String')) returns contents of bright_ch1_edit as a double +global UIInfo +hSlider = findobj('Tag','bright_ch1'); +update_slider(hObject,hSlider); +UIInfo.updateFcn(); + + +% --- Executes during object creation, after setting all properties. +function bright_ch1_edit_CreateFcn(hObject, eventdata, handles) +% hObject handle to bright_ch1_edit (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles empty - handles not created until after all CreateFcns called + +% Hint: edit controls usually have a white background on Windows. +% See ISPC and COMPUTER. +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end +hSlider = findobj('Tag','bright_ch1'); +update_editbox(hObject,hSlider); + + + +function contrast_ch1_edit_Callback(hObject, eventdata, handles) +% hObject handle to contrast_ch1_edit (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hints: get(hObject,'String') returns contents of contrast_ch1_edit as text +% str2double(get(hObject,'String')) returns contents of contrast_ch1_edit as a double +global UIInfo +hSlider = findobj('Tag','contrast_ch1'); +update_slider(hObject,hSlider); +UIInfo.updateFcn(); + + +% --- Executes during object creation, after setting all properties. +function contrast_ch1_edit_CreateFcn(hObject, eventdata, handles) +% hObject handle to contrast_ch1_edit (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles empty - handles not created until after all CreateFcns called + +% Hint: edit controls usually have a white background on Windows. +% See ISPC and COMPUTER. +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end +hSlider = findobj('Tag','contrast_ch1'); +update_editbox(hObject,hSlider); diff --git a/urchin_compare/urchin_compare.m b/urchin_compare/urchin_compare.m new file mode 100644 index 0000000000000000000000000000000000000000..d38f4ab2c931b789f9c9feb6f29194fe9949141f --- /dev/null +++ b/urchin_compare/urchin_compare.m @@ -0,0 +1,1232 @@ +function urchin_compare() + init_globals(); + load_config(); + + ui_initialize_main(); +end + +function init_globals() + global CONSTANTS UIInfo RegionInfo imC bwCh + + imC = []; + bwCh = []; + + RegionInfo = []; + CONSTANTS = []; + + UIInfo.mainFig = figure(); + UIInfo.toolFig = []; + + reset_ui_fields(); +end + +function reset_ui_fields() + global UIInfo + + UIInfo.pad = zeros(1,2); + UIInfo.padDelta = zeros(1,2); + + UIInfo.mousePt = zeros(1,2); + UIInfo.zoom = 0; + UIInfo.selectedRegion = -1; + + UIInfo.edited = false; + UIInfo.panning = false; + + clf(UIInfo.mainFig); +end + +function imPath = get_image_path(rootDir) + imPath = ''; + + [filename,pathname] = uigetfile(fullfile(rootDir, '*.*'), 'Open image file'); + if ( filename == 0 ) + return; + end + + imPath = fullfile(pathname,filename); +end + +function [im,imD] = fixup_im_channels(im,imD) + chNames = {}; + if ( isfield(imD,'ChannelNames') ) + chNames = imD.ChannelNames; + end + + if ( ~isempty(chNames) ) + keepNames = {'Alexa 488'; 'Cy3.5'}; + sidx = zeros(length(chNames),1); + for i=1:length(keepNames) + bkn = startsWith(chNames, keepNames{i}); + sidx(bkn) = i; + end + + srtCh = zeros(length(keepNames),1); + for i=1:length(keepNames) + srtCh(i) = find(sidx==i,1,'first'); + end + + imD.ChannelNames = chNames(srtCh); + else + srtCh = [1;2]; + end + + imD.NumberOfChannels = 2; + im = im(:,:,:,srtCh,:); +end + +function [im,imD,imPath] = load_image(imPath) + im = []; + + [pathname,filename,fext] = fileparts(imPath); + imD = MicroscopeData.Original.ReadMetadata(pathname,[filename fext]); + if ( isempty(imD) ) + return; + end + + im = MicroscopeData.Original.ReadImages(pathname,[filename fext], [], 'zList',[1]); + if ( isempty(im) ) + return; + end + + [im,imD] = fixup_im_channels(im,imD); +end + +%% ---- Size compat-helper +function sz_dims = size_dims(A, dims) + sz_dims = arrayfun(@(x)(size(A,x)), dims); +end + +%% ---- Config handling ---- +function load_config() + global Config + + Config = struct('imagePath',{''},... + 'dataPath',{''},... + 'channelColors',{[0,1,0; 1,0,0]}); + + % Check for a valid settings file + if ( ~exist('settings.json','file') ) + return; + end + + jsonData = fileread('settings.json'); + settings = jsondecode(jsonData); + + if ( isempty(settings) ) + return; + end + + fields = fieldnames(settings); + for i=1:length(fields) + Config.(fields{i}) = settings.(fields{i}); + end +end + +function save_config() + global Config + + jsonData = Utils.CreateJSON(Config); + fid = fopen('settings.json','wt'); + if ( fid >= 0 ) + fprintf(fid, '%s\n', jsonData); + fclose(fid); + end +end + +function set_datapath(datapath) + global Config + + pathname = fileparts(datapath); + Config.dataPath = pathname; +end + +function set_imagepath(imagepath) + global Config + + pathname = fileparts(imagepath); + Config.imagePath = pathname; +end + + +%% ---- Draw Functions ---- +function draw_image() + global imC UIInfo + + if ( isempty(imC) ) + return; + end + + imR = im_composite_channels(imC, UIInfo.Cmap,UIInfo.Tfn); + + render_image(UIInfo.mainFig, imR); + draw_regions(); +end + +function xor_cmap = color_xor(cmap) + xor_cmap = sum(cmap,1); + while ( any(xor_cmap > 1.0) ) + bgo = (xor_cmap > 1.0); + xor_cmap(bgo) = xor_cmap(bgo) - 1.0; + end +end + +function draw_regions() + global UIInfo RegionInfo + + curAx = get(UIInfo.mainFig, 'CurrentAxes'); + + if ( UIInfo.bShowRegions ) + for i=1:length(RegionInfo) + if ( isempty(RegionInfo(i).id) ) + continue; + end + + [~,uridx] = sortrows(RegionInfo(i).pts); + + drawStyle = '-'; + if ( UIInfo.selectedRegion == i ) + drawStyle = ':'; + end + plot(RegionInfo(i).pts(:,1),RegionInfo(i).pts(:,2), 'w',... + 'LineWidth',2,... + 'LineStyle',drawStyle,... + 'Parent',curAx,... + 'ButtonDownFcn',@(src,evt)(set_region_select(i))); + + text(curAx,... + RegionInfo(i).pts(uridx(end),1)+1,... + RegionInfo(i).pts(uridx(end),2)+1,... + num2str(RegionInfo(i).id),... + 'Color','w',... + 'VerticalAlignment','top'); + end + end + + % Figure out a distinctive overlap color (if all channels on) + bShowCh = [UIInfo.bShowChPx, any(UIInfo.bShowChPx)]; + % NOTE/HACK: color_xor runs across all channels passed in so it's important to start with [0,0,0] for overlap + pxCmap = [UIInfo.Cmap; 0 0 0]; + pxCmap(3,:) = color_xor(pxCmap(bShowCh,:)); + for c=1:3 + if ( ~bShowCh(c) ) + continue; + end + + for i=1:length(RegionInfo) + if ( isempty(RegionInfo(i).id) ) + continue; + end + + if ( isempty(RegionInfo(i).chPts{c}) ) + continue; + end + + plot(RegionInfo(i).chPts{c}(:,1),RegionInfo(i).chPts{c}(:,2), '.',... + 'Color',pxCmap(c,:),... + 'Parent',curAx,... + 'ButtonDownFcn',@(src,evt)(set_region_select(i))); + end + end + + drawnow(); +end + +function render_image(hFig, imRender) + global UIInfo + + %% Compute padding info + imSize = size(imRender); + imPad = pad_image(imRender, UIInfo.pad); + + imLims = [1-UIInfo.pad(2) imSize(2)+UIInfo.pad(2); + 1-UIInfo.pad(1) imSize(1)+UIInfo.pad(1)]; + + %% Setup current image axis and get limits + xl = []; + yl = []; + + curAx = get(hFig, 'CurrentAxes'); + if ( isempty(curAx) ) + curAx = axes('Parent', hFig); + set(hFig, 'CurrentAxes',curAx); + else + %% Get current axis limit sets + xl = xlim(curAx); + yl = ylim(curAx); + + im_xy = (imLims(:,2)-imLims(:,1)); + prevIm_xy = im_xy - 2*[UIInfo.padDelta(2);UIInfo.padDelta(1)]; + + view_xy = [xl(2)-xl(1); yl(2)-yl(1)]; + + ssrd_xy = 0.5 * view_xy .* (im_xy - prevIm_xy) ./ prevIm_xy; + + xl = xl + [-ssrd_xy(1), ssrd_xy(1)]; + yl = yl + [-ssrd_xy(2), ssrd_xy(2)]; + end + + % Reset pad delta + UIInfo.padDelta = zeros(1,2); + + %% render image + hold(curAx, 'off'); + hIm = imagesc(imLims(1,:),imLims(2,:),imPad, 'Parent',curAx, [0 1]); + set(hIm, 'ButtonDownFcn', @click_down_event); + + %% Axis positions + set(curAx, 'Position',[0 0 1 1]); + + %% Setup axis defaults + axis(curAx, 'off'); + axis(curAx, 'fill'); + axis(curAx, 'ij'); + zoom(hFig, 'reset'); + + %% Reset limits + if ( ~isempty(xl) ) + xlim(curAx, xl); + ylim(curAx, yl); + end + + %% Hold for further drawing + hold(curAx, 'all'); +end + + +%% ---- Handle aspect-ratio padding +function imPad = pad_image(im, pad) + imSize = size(im); + imPad = zeros(imSize + 2*[pad, 0]); + + imX = pad(2) + (1:imSize(2)); + imY = pad(1) + (1:imSize(1)); + + imPad(imY,imX,:) = im; +end + +function [pad,finalAR] = compute_pad(hFig, im) + imSize = size(im); + pos = get(hFig, 'Position'); + + fAR = pos(3) / pos(4); + imAR = imSize(2) / imSize(1); + + pad = zeros(1,2); + if ( imAR <= fAR ) + pad(2) = ceil((fAR * imSize(1) - imSize(2)) / 2); + else + pad(1) = ceil((imSize(2) / fAR - imSize(1)) / 2); + end + + finalAR = (imSize(2) + 2*pad(2)) / (imSize(1) + 2*pad(1)); +end + +%% ---- Image rendering functions +function imRender = im_composite_channels(imC, cmap,tfn) + imSize = size_dims(imC, 1:4); + + %% Create normalized images + imNrm = zeros([imSize(1:2), imSize(4)]); + for c=1:imSize(4) + imNrm(:,:,c) = mat2gray(imC(:,:,1,c)); + end + + %% Transform channels + imT = im_apply_tfn(imNrm, tfn); + + % Use a default alpha and normalize at the end + % NOTE: Alpha = 0.1 makes all channels pretty transparent + alpha = 0.1; + %% Composite colors + imRender = zeros([imSize(1:2), 3]); + imTrn = ones([imSize(1:2), 1]); + for c=imSize(4):-1:1 + oppacity = alpha * imT(:,:,c); + + imPremul = oppacity .* repmat(reshape(cmap(c,:),[1,1,3]), [imSize(1:2), 1]); + imRender = (1-oppacity).*imRender + imPremul; + imTrn = imTrn .* (1-oppacity); + end + + imRender = clamp(imRender ./ alpha, 0.0,1.0); +end + +function imT = im_apply_tfn(im,tfn) + alpha = tfn(:,1).'; + beta = tfn(:,2).'; + % gamma = tfn(:,3).'; + + imR = reshape(im, [size(im,1)*size(im,2), size(im,3)]); + imTl = clamp(alpha .* (clamp(imR + beta,0.0,1.0) - 0.5) + 0.5, 0.0, 1.0); +% imTl = clamp(clamp(alpha .* ((imR.^gamma) - 0.5),-0.5,0.5) + 0.5 + beta, 0.0, 1.0); + + imT = reshape(imTl, size(im)); +end + +function outV = clamp(inV, minV, maxV) + outV = min(max(inV, minV), maxV); +end + + +%% ---- UI Init Functions ---- +function ui_initialize_main() + global UIInfo + + set(UIInfo.mainFig,... + 'SizeChangedFcn', @resize_event,... + 'WindowScrollWheelFcn', @scroll_event,... + 'WindowButtonMotionFcn',@move_event,... + 'KeyPressFcn', @key_event,... + 'CloseRequestFcn', @close_event,... + 'Menu', 'none',... + 'ToolBar', 'none',... + 'BusyAction', 'cancel',... + 'Interruptible', 'off',... + 'NumberTitle', 'off',... + 'Name', 'Sea Urchin Calcein Counter'); + + ui_create_menubar(UIInfo.mainFig); +end + +function ui_create_menubar(hFig) + global UIInfo + + fileMenu = uimenu(... + 'Parent', hFig,... + 'Label', 'File',... + 'HandleVisibility', 'callback'); + + toolsMenu = uimenu(... + 'Parent', hFig,... + 'Label', 'Tools',... + 'HandleVisibility', 'callback'); + + helpMenu = uimenu(... + 'Parent', hFig,... + 'Label', 'Help',... + 'HandleVisibility', 'callback',... + 'Callback', @(src,evt)(helpdlg())); + + uimenu(... + 'Parent', fileMenu,... + 'Label', 'Open Image',... + 'HandleVisibility', 'callback', ... + 'Callback', @open_image_event,... + 'Accelerator', 'o'); + + uimenu(... + 'Parent', fileMenu,... + 'Label', 'Save Data',... + 'HandleVisibility', 'callback', ... + 'Callback', @save_data_event,... + 'Accelerator', 's',... + 'Separator', 'on'); + + uimenu(... + 'Parent', fileMenu,... + 'Label', 'Load Data',... + 'HandleVisibility', 'callback',... + 'Callback', @open_data_event,... + 'Accelerator', 'l'); + + showTools = uimenu(... + 'Parent', toolsMenu,... + 'Label', 'Show Tools',... + 'HandleVisibility', 'callback',... + 'Callback', @(src,evt)(ui_toggle_tools()),... + 'Accelerator', 't',... + 'Enable', 'off',... + 'Checked', 'off'); + + UIInfo.handles.showTools = showTools; +end + +function ui_update_title() + global CONSTANTS UIInfo + + datasetName = CONSTANTS.imageData.DatasetName; + + title = datasetName; + if ( is_edited() ) + title = [title ' *']; + end + + set(UIInfo.mainFig, 'NumberTitle','off'); + set(UIInfo.mainFig, 'Name',title); +end + +function ui_create_tooldlg() + global UIInfo + + if ( ~check_valid_handle(UIInfo.toolFig) ) + UIInfo.toolFig = toolsdlg(); + UIInfo.updateMenu = @ui_update_toolsmenu; + UIInfo.updateFcn = @ui_update_fcn; + UIInfo.updateThresh = @ui_update_thresh_fcn; + UIInfo.updateRegionSelect = @set_region_select; + % UIInfo.updateCellCount = @ui_update_cellcount; + end + + ui_setup_toolhandles(); + ui_set_channel_names(); + + ui_update_region_table(); + ui_init_chthresh(); + + ui_update_from_info(); + ui_update_toolsmenu(); +end + +function ui_toggle_tools() + global UIInfo + + if ( check_valid_handle(UIInfo.toolFig) ) + visible = get(UIInfo.toolFig, 'Visible'); + if ( strcmpi(visible,'on') ) + set(UIInfo.toolFig, 'Visible','off'); + else + set(UIInfo.toolFig, 'Visible','on'); + end + else + ui_create_tooldlg(); + end + + ui_update_toolsmenu(); +end + +function ui_update_toolsmenu() + global UIInfo CONSTANTS + + strEn = 'off'; + if ( ~isempty(CONSTANTS) ) + strEn = 'on'; + end + + if ( check_valid_handle(UIInfo.toolFig) ) + strCh = get(UIInfo.toolFig, 'Visible'); + end + + set(UIInfo.handles.showTools, 'Checked',strCh); + set(UIInfo.handles.showTools, 'Enable',strEn); +end + +function bValid = check_valid_handle(chkHandle) + bValid = false; + if ( isempty(chkHandle) ) + return; + end + + if ( ~isvalid(chkHandle) ) + return; + end + + bValid = true; +end + + +%% ---- Tools UI Helpers ---- +function ui_update_fcn() + ui_update_tool_values(); + draw_image(); +end + +function ui_update_tool_values() + ui_load_colors(); + ui_load_display_settings(); + ui_compute_transferfcn(); +end + +function ui_update_thresh_fcn() + ui_update_chthresh(); + + edit_op(@update_region_chbw); + ui_update_region_table(); + draw_image(); +end + +function ui_setup_toolhandles() + global CONSTANTS UIInfo + + UIInfo.tools = []; + UIInfo.tools.hShowCh = findobj(UIInfo.toolFig, 'Tag','show_regions'); + + minChan = min(2,CONSTANTS.imageData.NumberOfChannels); + + UIInfo.tools.hChColor = zeros(1,minChan); + UIInfo.tools.hChBright = zeros(1,minChan); + UIInfo.tools.hChContrast = zeros(1,minChan); + + for c=1:minChan + UIInfo.tools.hChColor(c) = findobj(UIInfo.toolFig, 'Tag',['color_ch' num2str(c)]); + UIInfo.tools.hChBright(c) = findobj(UIInfo.toolFig, 'Tag',['bright_ch' num2str(c)]); + UIInfo.tools.hChContrast(c) = findobj(UIInfo.toolFig, 'Tag',['contrast_ch' num2str(c)]); + + UIInfo.tools.hShowChPx(c) = findobj(UIInfo.toolFig, 'Tag',['show_ch' num2str(c) 'px']); + UIInfo.tools.hChThresh(c) = findobj(UIInfo.toolFig, 'Tag', ['ch' num2str(c) '_thresh']); + end + + UIInfo.tools.hRegionTable = findobj(UIInfo.toolFig, 'Tag','cells_table'); +end + +function ui_set_channel_names() + global CONSTANTS UIInfo + + for c=1:2 + hChPanel = findobj(UIInfo.toolFig, 'Tag',['uipanel_ch' num2str(c)]); + if ( ~isempty(hChPanel) && ~isempty(CONSTANTS.imageData.ChannelNames{c}) ) + set(hChPanel, 'Title',CONSTANTS.imageData.ChannelNames{c}); + end + end +end + +function ui_init_chthresh() + ui_update_chthresh(); + update_bw_ch(); +end + +function ui_set_channel_colors() + global UIInfo Config CONSTANTS + + if ( isfield(CONSTANTS.imageData,'ChannelColors') && ~isempty(CONSTANTS.imageData.ChannelColors) ) + UIInfo.Cmap = CONSTANTS.imageData.ChannelColors; + else + UIInfo.Cmap = Config.channelColors; + end +end + +function ui_reset_display_settings() + global UIInfo + + UIInfo.bShowRegions = true; + UIInfo.bShowChPx = [true, true]; + + UIInfo.ThrScale = [1.0, 1.0]; + + UIInfo.Tfn(1,:) = [1,0,1]; + UIInfo.Tfn(2,:) = [1,0,1]; + + ui_set_channel_colors(); +end + +function ui_update_from_info() + global UIInfo + + %% Setup display settings + set(UIInfo.tools.hShowCh, 'Value',UIInfo.bShowRegions); + for c=1:length(UIInfo.tools.hShowChPx) + set(UIInfo.tools.hShowChPx(c), 'Value',UIInfo.bShowChPx(c)); + end + + %% Setup colors + for c=1:size(UIInfo.Cmap,1) + set(UIInfo.tools.hChColor(c), 'BackgroundColor',UIInfo.Cmap(c,:)); + end + + %% Setup transfer functions + for c=1:size(UIInfo.Tfn,1) + alpha = UIInfo.Tfn(c,1); + beta = UIInfo.Tfn(c,2); + + vBr = beta; + vCon = 1-2.^(-alpha); + + set(UIInfo.tools.hChBright(c), 'Value',vBr); + set(UIInfo.tools.hChContrast(c), 'Value',vCon); + end + + %% Setup threshold + for c=1:length(UIInfo.tools.hChThresh) + tSc = UIInfo.ThrScale(c); + vCt = 1-2.^(-tSc); + set(UIInfo.tools.hChThresh(c), 'Value',vCt); + end +end + +function ui_load_display_settings() + global UIInfo + + UIInfo.bShowRegions = (get(UIInfo.tools.hShowCh, 'Value') > 0.0); + for c=1:length(UIInfo.tools.hShowChPx) + UIInfo.bShowChPx(c) = (get(UIInfo.tools.hShowChPx(c), 'Value') > 0.0); + end +end + +function ui_load_colors() + global UIInfo Config + cmap = [1,0,0; 0,1,0]; + + for c=1:length(UIInfo.tools.hChColor) + cmap(c,:) = get(UIInfo.tools.hChColor(c), 'BackgroundColor'); + end + + UIInfo.Cmap = cmap; + Config.channelColors = cmap; +end + +function ui_compute_transferfcn() + global UIInfo + gamma = 1.2; + tfn = [1,0,gamma;1,0,gamma]; + + for c=1:length(UIInfo.tools.hChBright) + vBr = get(UIInfo.tools.hChBright(c), 'Value'); + vCon = get(UIInfo.tools.hChContrast(c), 'Value'); + + alpha = -log2(1-vCon); + beta = vBr; + + tfn(c,:) = [alpha,beta,1]; + end + + UIInfo.Tfn = tfn; +end + +function ui_update_chthresh() + global UIInfo + + thrScale = zeros(1,2); + for c=1:length(UIInfo.tools.hChThresh) + tVal = get(UIInfo.tools.hChThresh(c), 'Value'); + thrScale(c) = -log2(1-tVal); + end + + UIInfo.ThrScale = thrScale; +end + + +%% ---- Partial region (drawing) handling ---- +function update_cell_edge(point_xy) + global PartialCell + + updateLine = PartialCell.lines(end); + set(updateLine, 'XData', [PartialCell.pts(end,1) point_xy(1)]); + set(updateLine, 'YData', [PartialCell.pts(end,2) point_xy(2)]); +end + +function start_cell(curAx, point_xy) + global PartialCell + + PartialCell = []; + PartialCell.pts = point_xy; + + newLine = line([PartialCell.pts(end,1) point_xy(1)], [PartialCell.pts(end,2) point_xy(2)], 'Parent',curAx, 'color','w'); + PartialCell.lines = newLine; +end + +function chk_next_edge(curAx, point_xy) + global PartialCell + + if ( isempty(PartialCell) ) + return; + end + + if ( sum((PartialCell.pts(end,:) - point_xy).^2,1) < 0.6 ) + return; + end + + next_cell_edge(curAx, point_xy); +end + +function next_cell_edge(curAx, point_xy) + global PartialCell + + if ( isempty(PartialCell) ) + return; + end + + PartialCell.pts = [PartialCell.pts; point_xy]; + + newLine = line([PartialCell.pts(end,1) point_xy(1)], [PartialCell.pts(end,2) point_xy(2)], 'Parent',curAx, 'color','w'); + PartialCell.lines = [PartialCell.lines; newLine]; +end + +function delete_partial_cell() + global PartialCell + + if ( ~isempty(PartialCell) ) + for i=1:length(PartialCell.lines) + delete(PartialCell.lines(i)); + end + end + + PartialCell = []; +end + + +%% ---- Region handling ---- +function ui_update_region_table() + global RegionInfo UIInfo + + data = {}; + if ( isempty(RegionInfo) ) + set(UIInfo.tools.hRegionTable, 'Data',data); + else + bValidRegions = arrayfun(@(x)(~isempty(x.id)), RegionInfo); + validRegions = RegionInfo(bValidRegions); + + % Channel counts 1x3 array: [ch1Count,ch2Count,overlapCount] + % NOTE: The channel 1/2 counts are only the disjoint non-overlapping pixels so we add 3 to both + chCounts = arrayfun(@(x)([x.chCount(1) x.chCount(2) 0] + x.chCount(3)), validRegions, 'UniformOutput',false); + chCounts = vertcat(chCounts{:}); + + data = cell(length(validRegions),5); + [data{:,1}] = deal(validRegions.id); + [data{:,2}] = deal(validRegions.area); + data(:,3) = num2cell(chCounts(:,1)); + data(:,4) = num2cell(chCounts(:,2)); + data(:,5) = num2cell(chCounts(:,3)); + + set(UIInfo.tools.hRegionTable, 'Data',data); + end +end + +function update_region_chpoints(regionIDs) + global RegionInfo bwCh + + %% Get above-threshold points for each region (handle overlap as well) + pts = []; + chansz = zeros(1,3); + bOverlap = all(bwCh,3); + % These are disjoint (non-overlapping points) + for ch=1:size(bwCh,3) + [r,c] = find(bwCh(:,:,ch) & ~bOverlap); + + pts = [pts; c,r]; + chansz(ch) = size(r,1); + end + % Handle overlap points + [ro,co] = find(bOverlap); + pts = [pts; co,ro]; + chansz(3) = size(ro,1); + % Starts for channel/overlap points + startidx = [0,cumsum(chansz)]; + % Make a bool map for point sets + bChPx = false(sum(chansz),3); + for i=1:3 + bChPx(startidx(i)+(1:chansz(i)),i) = true; + end + + %% Compute counts and channel points for all valid regions in region list + for i=1:length(regionIDs) + idx = regionIDs(i); + if ( isempty(RegionInfo(idx).id) ) + continue; + end + + bInR = inpolygon(pts(:,1),pts(:,2), RegionInfo(idx).pts(:,1),RegionInfo(idx).pts(:,2)); + RegionInfo(idx).chCount = zeros(1,3); + for ch=1:3 + bch = bInR & bChPx(:,ch); + chPts = pts(bch,:); + + RegionInfo(idx).chPts{ch} = chPts; + RegionInfo(idx).chCount(ch) = size(chPts,1); + end + end +end + +function update_region_chbw() + global RegionInfo + + update_bw_ch(); + update_region_chpoints(1:length(RegionInfo)); +end + +function update_bw_ch() + global UIInfo imC bwCh + + bwCh = false(size_dims(imC,[1,2,4])); + for c=1:size(imC,4) + imN = mat2gray(imC(:,:,1,c)); + mt = multithresh(imN(:),2); + thresh = UIInfo.ThrScale(c) * mt(2); + + bwCh(:,:,c) = (imN >= thresh); + end +end + +function create_region(partialRegion) + global RegionInfo + + if ( isempty(partialRegion) ) + return; + end + + newID = 1; + if ( ~isempty(RegionInfo) ) + newID = size(RegionInfo,1) + 1; + end + + newRegion = make_empty_cell(); + newRegion.id = newID; + newRegion.pts = [partialRegion.pts; partialRegion.pts(1,:)]; + newRegion.area = polyarea(newRegion.pts(:,1),newRegion.pts(:,2)); + + RegionInfo = [RegionInfo; newRegion]; + update_region_chpoints(newID); + + ui_update_region_table(); +end + +function delete_region(cellID) + global RegionInfo + + RegionInfo(cellID) = make_empty_cell(); +end + +function cellStruct = make_empty_cell() + cellStruct = struct('id',{[]}, 'pts',{[]}, 'area',{[]}, 'chPts',{[]},'chCount',{[]}, 'count',{[]}); +end + +function regionID = check_region_select(point_xy) + global RegionInfo + + bValidRegions = arrayfun(@(x)(~isempty(x.id)), RegionInfo); + validRegions = RegionInfo(bValidRegions); + + regionID = -1; + for i=1:length(validRegions) + [bIn,bOn] = inpolygon(point_xy(1),point_xy(2), validRegions(i).pts(:,1),validRegions(i).pts(:,2)); + if ( any(bIn | bOn) ) + regionID = validRegions(i).id; + return + end + end +end + +%% ---- Editor support ---- +function edit_op(editfcn, varargin) + editfcn(varargin{:}); + set_edited(true); +end + +function bEdited = is_edited() + global UIInfo + bEdited = UIInfo.edited; +end + +function set_edited(bEdited) + global UIInfo + + UIInfo.edited = bEdited; + ui_update_title(); +end + + +%% ---- Menu Events ---- +function open_image_event(hSrc,evt) + global CONSTANTS Config imC + + chk_const = []; + if ( ~check_continue_unsaved() ) + return; + end + + imPath = get_image_path(Config.imagePath); + if ( isempty(imPath) ) + return; + end + + [imC,chk_const.imageData] = load_image(imPath); + if ( isempty(imC) ) + msgbox('Unable to read image data from file', 'Error reading image', 'warn'); + return; + end + + chk_const.imagePath = imPath; + chk_const.dataPath = ''; + + CONSTANTS = chk_const; + + set_imagepath(imPath); + set_datapath(imPath); + + clear_data(); + reset_ui_fields(); + + ui_update_title(); + ui_reset_display_settings(); + ui_create_tooldlg(); + + resize_padding(); + draw_image(); +end + +function open_data_event(hSrc,evt) + global CONSTANTS Config + + if ( ~check_continue_unsaved() ) + return; + end + + [filename,pathname] = uigetfile(fullfile(Config.dataPath,'*.mat')); + if ( filename == 0 ) + return; + end + + datapath = fullfile(pathname,filename); + + open_data(datapath) + CONSTANTS.dataPath = datapath; + + ui_create_tooldlg(); + draw_image(); +end + +function save_data_event(hSrc,evt) + global CONSTANTS Config + + if ( isempty(CONSTANTS) ) + return; + end + + if ( isempty(CONSTANTS.dataPath) ) + [filename,pathname] = uiputfile(fullfile(Config.dataPath,'*.mat')); + if ( filename == 0 ) + return; + end + + CONSTANTS.dataPath = fullfile(pathname,filename); + end + + save_data(CONSTANTS.dataPath); +end + + +function bContinue = check_continue_unsaved() + bContinue = true; + if ( is_edited() ) + bContinue = unsaved_verify(); + end +end + +function bContinue = unsaved_verify() + userSel = questdlg('You have unsaved data, are you sure you wish to continue without saving?','Unsaved Data','Continue','Cancel','Cancel'); + bContinue = ~strcmpi(userSel,'Cancel'); +end + +%% ---- Data handling ---- +function clear_data() + global RegionInfo + + RegionInfo = []; +end + +function open_data(datapath) + global CONSTANTS RegionInfo UIInfo imC + + s = load(datapath); + CONSTANTS = s.CONSTANTS; + RegionInfo = s.RegionInfo; + + imC = s.CONSTANTS.im; + + %% Update from ui after load + fields = fieldnames(s.displayInfo); + for i=1:length(fields) + UIInfo.(fields{i}) = s.displayInfo.(fields{i}); + end + + set_edited(false); + set_datapath(datapath); +end + +function save_data(datapath) + global CONSTANTS RegionInfo UIInfo imC + + CONSTANTS.im = imC; + displayInfo = struct('Tfn',{UIInfo.Tfn}, 'Cmap',{UIInfo.Cmap},... + 'ThrScale',{UIInfo.ThrScale},... + 'bShowRegions',{UIInfo.bShowRegions},... + 'bShowChPx',{UIInfo.bShowChPx}); + + save(datapath, 'CONSTANTS','RegionInfo','displayInfo'); + + set_edited(false); + set_datapath(datapath); +end + + +%% ---- UI Events ---- +function click_down_event(hSrc,evt) + global UIInfo PartialCell + curAx = get(UIInfo.mainFig, 'CurrentAxes'); + currentPoint = get(curAx, 'CurrentPoint'); + point_xy = currentPoint(1,1:2); + + selectType = get(UIInfo.mainFig, 'SelectionType'); + cellID = check_region_select(point_xy); + + set_region_select(cellID); + + if ( isempty(PartialCell) && strcmpi(selectType,'normal') && (cellID < 0) ) + start_cell(curAx, point_xy); + set(UIInfo.mainFig, 'WindowButtonUpFcn',@click_up_event); + elseif ( strcmpi(selectType,'alt') ) + UIInfo.panning = true; + set(UIInfo.mainFig, 'WindowButtonUpFcn',@click_up_event); + end +end + +function click_up_event(hSrc,evt) + global UIInfo PartialCell + + UIInfo.panning = false; + set(UIInfo.mainFig, 'WindowButtonUpFcn',''); + + if ( ~isempty(PartialCell) ) + %% Don't create a cell if it's only a local click + if ( size(PartialCell.pts,1) > 2 ) + edit_op(@create_region, PartialCell); + end + + delete_partial_cell(); + draw_image(); + end +end + +function set_region_select(regionID) + global UIInfo + + if ( UIInfo.selectedRegion ~= regionID ) + UIInfo.selectedRegion = regionID; + draw_image(); + end +end + +function move_event(hSrc,evt) + global UIInfo PartialCell + + curAx = get(hSrc, 'CurrentAxes'); + if ( UIInfo.panning ) + stable_mouse_xlat(curAx); + end + + if ( ~isempty(curAx) ) + curPt = get(curAx, 'CurrentPoint'); + UIInfo.mousePt = curPt(1,1:2); + end + + if ( ~isempty(PartialCell) ) + update_cell_edge(UIInfo.mousePt); + chk_next_edge(curAx,UIInfo.mousePt); + end +end + +function key_event(hSrc,evt) + global UIInfo + + if ( strcmpi(evt.Key,'escape') ) + delete_partial_cell(); + set(UIInfo.mainFig, 'WindowButtonUpFcn',''); + elseif ( strcmpi(evt.Key,'backspace') || strcmpi(evt.Key,'delete') ) + if ( UIInfo.selectedRegion > 0 ) + edit_op(@delete_region, UIInfo.selectedRegion) + + UIInfo.selectedRegion = -1; + ui_update_region_table(); + draw_image(); + end + end +end + +function scroll_event(hSrc,evt) + global UIInfo + + zBase = 1.4; + + curAx = get(UIInfo.mainFig, 'CurrentAxes'); + if ( isempty(curAx) ) + return; + end + + currentPoint = get(curAx, 'CurrentPoint'); + point_xy = currentPoint(1,1:2); + + wheelDelta = -evt.VerticalScrollCount; + newZoom = clamp(wheelDelta + UIInfo.zoom, 0, 10); + zoomDelta = newZoom - UIInfo.zoom; + + if ( abs(zoomDelta) > 0 ) + xl = xlim(curAx); + yl = ylim(curAx); + + UIInfo.zoom = UIInfo.zoom + zoomDelta; + zoomFactor = zBase ^ zoomDelta; + + lims_xy = [xl.', yl.']; + zoomlims_xy = compute_smooth_zoom(zoomFactor, lims_xy, point_xy); + + xlim(curAx, zoomlims_xy(:,1)); + ylim(curAx, zoomlims_xy(:,2)); + elseif ( wheelDelta < 0 ) + UIInfo.zoom = 0; + zoom(curAx, 'out') + end +end + +function zm_lims_xy = compute_smooth_zoom(zoomFactor, lims_xy, point_xy) + %% Compute zoomed xy-limits such that point_xy is fixed (doesn't move) + % NOTE: lims_xy = [xmin ymin + % xmax ymax] + + loc_xy = point_xy - lims_xy(1,:); + vsz_xy = lims_xy(2,:) - lims_xy(1,:); + + delta_xy = loc_xy - (loc_xy / zoomFactor); + zoomsz_xy = vsz_xy / zoomFactor; + + zm_lims_xy = [lims_xy(1,:); lims_xy(1,:) + zoomsz_xy] + delta_xy; +end + +function stable_mouse_xlat(curAx) + global UIInfo + + xl = xlim(curAx); + yl = ylim(curAx); + + currentPoint = get(curAx, 'CurrentPoint'); + point_xy = currentPoint(1,1:2); + + delta_xy = UIInfo.mousePt - point_xy; + xl = xl + delta_xy(1); + yl = yl + delta_xy(2); + + xlim(curAx, xl); + ylim(curAx, yl); +end + +function resize_padding() + global UIInfo imC + + if ( isempty(imC) ) + return; + end + + padding = compute_pad(UIInfo.mainFig, imC); + + UIInfo.padDelta = padding - UIInfo.pad; + UIInfo.pad = padding; +end + +function resize_event(hFig,evt) + resize_padding(); + draw_image(); +end + +function close_event(hFig,evt) + global UIInfo + + if ( ~check_continue_unsaved() ) + return; + end + + save_config(); + + if ( check_valid_handle(UIInfo.toolFig) ) + delete(UIInfo.toolFig); + end + + delete(hFig); +end diff --git a/urchin_compare/version.json b/urchin_compare/version.json new file mode 100644 index 0000000000000000000000000000000000000000..ff8f7bf05c33c2d12446661bd1f05a5f61149bd4 --- /dev/null +++ b/urchin_compare/version.json @@ -0,0 +1,12 @@ +{ + "name" : "urchin_compare", + "majorVersion" : 2, + "minorVersion" : 0, + "branchName" : "master", + "buildNumber" : "2020.12.07.07", + "buildMachine" : "bioimage28", + "commitHash" : [ + " : 15410c6b7251a6890057e04366431f0286b90d70", + " : 07f725fe7b1c500a2743e016d119a7b0f059561e" + ] +}