How to use the index of selected data from one figure, to plot something in another figure? - visualization

I have a scatter plot on one figure. I'd like to be able to select possibly multiple data points on the mentioned scatter plot, and plot a (possibly) multi-line timeseries chart on the other figure, based on the indexes of the selected data.
Pseudo code:
data = { x: [1,2,3], y: [1,2,3], time_series: [[1,2,3],[4,5,6],[7,8,9]] }
figure1 = scatter_plot(x, y, select_enabled=True)
figure2 = multi_line_timeseries(figure1.indexes_of_selected_points)
show([figure1, figure2])
So if the [1,1] data point (index 0) is selected on figure 1, then the [1,2,3] timeseries (index 0) is plotted on figure 2. If multiple points are selected, then multiple timeseries are plotted.
A restraint is that the HoloViews library can't be used, due to it not supporting my platform.
How can this be achieved?

Note: I have opted to not support simultaneous multiple timeseries plotting, though that would be a trivial extension of this.
To use selected data point's index to determine what is to be plotted in another figure, you need to:
put the relevant data (i.e. x,y,timeseries in the example) on one or multiple ColumnDataSources;
I put the data to select and data that will be updated on different cds's, because I fear it might create a callback loop, though I've not tested this.
create a ColumnDataSource which will act as source for the second figure that plots the timeseries;
enable a selection tool, for example TapTool ('tap');
add a CustomJS callback to the ColumnDataSource that holds the selectable data points;
parametrize that callback with the ColumnDataSource that holds the timeseries data;
have the callback access indeces of selected data points;
have the callback make required changes to the second figure's ColumnDataSource;
call cds_of_2nd_figure.change.emit() before returning from the callback.
Code to illustrate:
cds = ColumnDataSource(data=dict(x=x,y=y,timeseries=timeseries))
cds2 = ColumnDataSource(x_to_plot=[],u_to_plot=[])
def selection_callback(d=cds,d2=cds2):
last_selected_ix = cb_obj.selected.indices[0]
timeserie = d.data['timeseries'][last_selected_ix]
x_to_plot = timeserie['x']
y_to_plot = timeserie['y']
d2.data['x_to_plot'] = x_to_plot
d2.data['y_to_plot'] = y_to_plot
d2.changes.emit()
# turn above function to js
selection_callback = CustomJS.from_py_func( selection_callback )
cds.callback = selection_callback
When some figure selects data from cds, the timeseries[ix] timeserie will be plotted on the figure/s that plot cds2, where ix is the index of the last selected data point from cds.
Relevant resource that has all the relevant information:
https://docs.bokeh.org/en/latest/docs/user_guide/interaction/callbacks.html#customjs-for-tools

Related

Matlab draw line with plot and dynamic values

I am trying to draw line with plot in matlab app designer. But i dont know how to create line. I am just redrawing point on new coordinates.
This is my code (functions runs each second) (format of plot is plot(PlotUI,X,Y))
function function2(app)
app.timeCounter = app.timeCounter + 1;
plot(app.UIAxes,app.timeCounter,app.newValDblPublic);
end
I will be thankful for any help.
At the moment you are just plotting the current set of values, if you want to plot the historic values too, you need to keep them in an array and plot the entire array.
%When the GUI is first created, start with one value in the array
app.time_values = [0];
app.y_values = [0];
%Inside your function
function function2(app)
app.time_values(end+1) = app.time_values(end)+1; % Add a new value to the array, 1 greater than the last value
app.y_values(end+1) = app.newValDblPublic;
plot(app.UIAxes,app.time_values,app.y_values);
end

Sorting dicom images in Matlab

I am working with lung data sets in matlab, but I need to sort the slices correctly and show them.
I knew that can be done using the "instance number" parameter in Dicom header, but I did not manage to run the correct code.
How can I do that?
Here is my piece of code:
Dicom_directory = uigetdir();
sdir = strcat(Dicom_directory,'\*.dcm');
files = dir(sdir);
I = strcat(Dicom_directory, '\',files(i).name);
x = repmat(double(0), [512 512 1 ]);
x(:,:,1) = double(dicomread(I));
axes(handles.axes1);
imshow(x,[]);
First of all, to get the DICOM header, you need to use dicominfo which will return a struct containing each of the fields. If you want to use the InstanceNumber field to sort by, then you can do this in such a way.
%// Get all of the files
directory = uigetdir();
files = dir(fullfile(directory, '*.dcm'));
filenames = cellfun(#(x)fullfile(directory, x), {files.name}, 'uni', 0);
%// Ensure that they are actually DICOM files and remove the ones that aren't
notdicom = ~cellfun(#isdicom, filenames);
files(notdicom) = [];
%// Now load all the DICOM headers into an array of structs
infos = cellfun(#dicominfo, filenames);
%// Now sort these by the instance number
[~, inds] = sort([infos.InstanceNumber]);
infos = infos(inds);
%// Now you can loop through and display them
dcm = dicomread(infos(1));
him = imshow(dcm, []);
for k = 1:numel(infos)
set(him, 'CData', dicomread(infos(k)));
pause(0.1)
end
That being said, you have to be careful sorting DICOMs using the InstanceNumber. This is not a robust way of doing it because the "InstanceNumber" can refer to the same image acquired over time or different slices throughout a 3D volume. If you want one or the other, I would choose something more specific.
If you want to sort physical slices, I would recommend sorting by the SliceLocation field (if available). If sorting by time, you could use TriggerTime (if available).
Also you will need to consider that there could also potentially be multiple series in your folder so maybe consider using the SeriesNumber to differentiate these.

Displaying the slope of a line created by imline in a text box

This post is a continuation of the thread: "Matlab imline snapping" which was resolved. The below is the working code that snaps the imline object to a curve.
function calc_slope(handle,event)
axis_h = findobj(gcf,'Type','axes');
obj_h = get(axis_h,'Children');
obj_type = get(obj_h,'Type');
if ~iscell(obj_type),obj_type = cellstr(obj_type);end
for i=1:length(obj_type)
if strcmpi(obj_type{i},'line'),data = obj_h(i);end
end
xdata = get(data,'XData');
ydata = get(data,'YData');
on = get(handle,'State');
if strcmpi(on,'on') || strcmpi(on,'off'),
fcn_constr = #(pos) imline_snap(pos, [xdata(:) ydata(:)]);
xy = imline(axis_h, 'PositionConstraintFcn', fcn_constr);
addNewPositionCallback(xy,#(pos) disp_slope(pos));
end
function constr_pos = imline_snap(new_pos, positions)
[~, ind1] = min(sum(bsxfun(#minus, new_pos(1,:), positions).^2, 2));
[~, ind2] = min(sum(bsxfun(#minus, new_pos(2,:), positions).^2, 2));
constr_pos = [positions(ind1,:); positions(ind2,:)];
function disp_slope(pos)
delete(findobj(gca,'Type','text'));
text((pos(1)+pos(2))/2,(pos(3)+pos(4))/2,['\DeltaY/\DeltaX = ',num2str((pos(4)-pos(3))/(pos(2)-pos(1))),...
' [\DeltaX = ',num2str(pos(2)-pos(1)),', \DeltaY = ',num2str((pos(4)-pos(3))),']']);
Every time the toggle button on the toolbar of a figure is toggled (on and off), a new imline object is thrown in. There are many figures with different parameters so that the data has to be extracted from the figure. In a given figure, there can be multiple objects: imline objects, text, and/or line; thus, the first seven lines in the calc_slope function.
The imline objects snaps to the nearest data point of a curve and it's beautifully done by imline_snap function which is an answer by "Luis Mendo". Thank you so much. This has been a biggest headache.
The final problem is now to show the slope of an imline object in a text box (instead of the title or the floating box). It's attempted in the disp_slope function (and it's miserable).
I'm doing "delete(findobj(gca,'Type','text'));" only because without something like that, as the imline object is moved around, it will leave millions of text boxes. I only want to show one most current slope calculation.
There are multiple problems with "delete(findobj(gca,'Type','text'));". If I stop moving the line around, it will nicely show the last slope calculation. However, as soon as I throw in another imline object and move the new one around, the text box in the first imline object will get deleted and of course.
Another problem is that even if I delete the imline object, the associated text box will remain.
In summary,
I want to show the current calculated slope in a text box
I want the text box for each imline object to remain even if there are multiple imline objects.
Finally, I want the corresponding text box to disappear as well when a particular imline object is deleted.
Can this be done? Help please.
Thanks,
Eric
Don't create a new text object every time. Create one initially
ht = text(.45, .85, ''); %// modify coordinates to place it where you want
and then update its content ('String' property) when the imline changes. To do the updating, modify the imline_snap function to accept ht as a third input and add the following line at the end:
set(ht, 'String', ...
num2str((constr_pos(2,2)-constr_pos(1,2))/(constr_pos(2,1)-constr_pos(1,1))));
So the function becomes
function constr_pos = imline_snap(new_pos, positions, ht)
[~, ind1] = min(sum(bsxfun(#minus, new_pos(1,:), positions).^2, 2));
[~, ind2] = min(sum(bsxfun(#minus, new_pos(2,:), positions).^2, 2));
constr_pos = [positions(ind1,:); positions(ind2,:)];
set(ht, 'String', ...
num2str((constr_pos(2,2)-constr_pos(1,2))/(constr_pos(2,1)-constr_pos(1,1))));
Then, when defining fcn_contr, pass the reference ht to the text object:
fcn_constr = #(pos) imline_snap(pos, [xdata(:) ydata(:)], ht);
Here's an example, borrowing the curve from my previous answer:
h = plot(0:.01:1, (0:.01:1).^2); %// example curve. Get a handle to it
a = gca; %// handle to current axes
ht = text(.45, .85, ''); %// create text
xdata = get(h,'XData'); %// x values of points from the curve
ydata = get(h,'YData'); %// y values of points from the curve
fcn_constr = #(pos) imline_snap(pos, [xdata(:) ydata(:)], ht); %// particularize function
imline(a, 'PositionConstraintFcn', fcn_constr); %// create imline
You can also update the text position ('Position' property). Just change the last statement of imline_snap to include that. For example:
set(ht, 'String', ...
num2str((constr_pos(2,2)-constr_pos(1,2))/(constr_pos(2,1)-constr_pos(1,1))), ...
'Position', ...
mean(constr_pos) + [.03 -.03]); %// manually adjust offset if needed
The offset [.03 -.03] is intended to avoid the text overlapping with the line. You may need to change it. Also, it may help to create the text object with boldface. The line becomes
ht = text(.45, .85, '', 'Fontweight', 'bold'); %// create text, in boldface
Here's an example with text position updating:
To delete the associated text object when you delete the imline object you need and event listener. This is an object with three main properties: a cell array of source objects, an event, and a callback function. When the indicated event happens to one of the source objects the callback function of the event listener is executed.
To create an event listener for the imline object's deletion, use that object's addEventListener method and specify the event name and callback function. The callback function is specified by means of a function handle, and it should expect two inputs, which correspond to the source object and the event (this is how the callback function will know "why" it's being called). Even if those inputs won't actually be used, the function needs to be defined that way.
In this case, the event we want to listen to is ObjectBeingDestroyed, the source object is the imline object, and the callback function is delete(ht) (to delete the text object). So, the code in the above example becomes
h = plot(0:.01:1, (0:.01:1).^2); %// example curve. Get a handle to it
a = gca; %// handle to current axes
ht = text(.45, .85, '', 'Fontweight', 'bold'); %// create text, in boldface
xdata = get(h,'XData'); %// x values of points from the curve
ydata = get(h,'YData'); %// y values of points from the curve
fcn_constr = #(pos) imline_snap(pos, [xdata(:) ydata(:)], ht); %// particularize function
hi = imline(a, 'PositionConstraintFcn', fcn_constr); %// create imline and get a handle
addlistener(hi, 'ObjectBeingDestroyed', #(obj,event) delete(ht))
where only the last two lines are new.
Now whenever the imline object is deleted the action delete(ht) will be performed, thus deleting the text object.

Plotting arrays from a cell list of strings

Suppose I have different rows loaded from a .mat file (using load filename.mat) containing float numbers following the same naming convention, e.g:
file_3try = [ 2.4, 5.2, 7.8 ]
file_4try = [ 8.7, 2.5, 4,2 ]
file_5try = [ 11.2, 9.11 ]
to plot all of these in one plot using automation (I have many more rows than in the example above) I created a cell containing the names of the arrays by using:
name{l} = sprintf('%s%02i%s','file_',num,'try');
inside a for loop with num the numbers in the names and l a counter starting from 1.
But when I try to plot the arrays using for example:
plot(name{1})
I get the error:
Error using plot
Invalid first data argument
Is there a way to solve this, or am I going about this wrong?
There is something built in to solve this
data = load ( 'filename' ); % load data and store in struct
fnames = fieldnames ( data );
for ii=1:length(fnames)
plot ( axHandle, data.(fnames{ii}) );
end
axHandle is a handle to the axes you want to plot on. Its not required but it is good practice to use it. If its not provided then the plot command will plot on the current axes, e.g. gca.
So as mentioned already you need to use eval.
Assuming the file_**X**try rows are different lengths then you could just place all of them in a cell rather than creating a cell of the variable names. So instead of assigning to separate variables the way you are doing you could assign to a cell, so:
file_try{i} = [.....];
You can then cycle through file_try and plot each entry:
for i = 1:length(file_try)
plot(file_try{i});
end
If the rows are not different lengths then stick them in a matrix and plot it.

Plot to highlight selected data

I'd like to create something similar to this. The differences are: my table only has one column, which is the variable I changed to get my data. I have the table set up and working, and most of what I have works, but I only seem to be able to highlight one thing at once. This is what I have:
% Set up plots etc
for a = 1:length(data);
xAxis = data{a,1}(:,X);
yAxis = data{a,1}(:,Y);
plot(graph,xAxis,yAxis);
% Visible data
hilite = plot(graph,xAxis,yAxis,'o-','LineWidth',2,'HandleVisibility','off','Visible','off');
% Invisible data, to be highlighted later
hold all
end
hold off
% Data table callback function
function dataTable_Callback(hObject, eventdata)
set(hilite,'Visible','off')
names = get(hObject,'Data');
select = eventdata.Indices(:,1);
for d = 1:length(select)
group = select(d);
xAxis = [];
% makes sure that previously selected data is removed
yAxis = [];
xAxis(:,d) = data{group,1}(:,X);
% if I remove the semicolon and let MATLAB print this data, I always get
the correct data in the correct columns, adding and subtracting them when
I select them
yAxis(:,d) = data{group,1}(:,Y);
set(hilite,'Visible','on','XData',xAxis(:,d),'YData',yAxis(:,d))
legend(hilite, num2str(names{select,1}(1,1)));
end
end
I've been playing with this for a few hours now, and I really can't work out what my issue is. Any help is obviously much appreciated! :-)