Why is the isosurface value inverted with the colorbar and true value in go.Isosurface? - plotly.graph-objects

I have a 3D volume matrix with pressure values describing the acoustic field generated by an ultrasound transducer.
Goal: plot the data with isosurfaces value on hover.
Method: use the go.Isosurfaces from plotly.graph_objects library.
Problem: instead of showing the correct value of the isosurface on hover it shows the opposite in the colorbar. eg: ~in the centre of the volume the maximum pressure occurs but the isosurface value is low; at the limits of the volume the pressure is minimum but the isosurface shows max pressure. See figs below:
fig with isosurface at centre of the volume; fig with hover at the limits of the volume
Here is an excerpt of the code, where pmax_derated is the 3D matrix:
z1 = range(0, 51)
x1 = range(0, 29)
y1 = range(0, 45)
surf_count=50
isomin=np.min(pmax_derated)
isomax=np.max(pmax_derated)
X, Y, Z = np.meshgrid(x1, y1, z1)
values = np.transpose(pmax_derated,(1,2,0)) #orientation changes to match the coordinates
title_colorbar='Peak Pressure (Pa)'
fig = go.Figure(data=[
go.Isosurface(
x=X.flatten(),
y=Y.flatten(),
z=Z.flatten(),
value=values.flatten(order='C'),
isomin=isomin,
isomax=isomax,
surface_fill=0.3,
surface_count=2,
surface_pattern = "odd",
caps=dict(x_show=False, y_show=False, z_show=False),
slices_z=dict(show=True, locations=[21]),
slices_y=dict(show=True, locations=[24]),
slices_x=dict(show=True, locations=[11]),
colorscale='jet',
colorbar_title=title_colorbar,
name="Acoustic field",
contour = dict(show=True, width=5, color='aliceblue'),
)
])
fig.update_layout(scene = dict(
xaxis_title='height',
yaxis_title='width',
zaxis_title='depth',
#camera=camera
),
title_text=title)
fig.show()
I don't understand what is the problem, the data is correct, as it can be seen from the colorbar. I've looked for the properties of go.Isosurface but have not found an error, or a property that is ultimately wrong. Does anyone have any idea?
Thank you in advance!

Related

Plot image on dome

I am struggling to plot a round image on the surface of the dome in Matlab. Here is what I have. This is the png image:
And this is the dome:
Now, I need to project this image on the dome. I've written a code to place the image on the surface:
r = 10;
r2 = 9;
cdata = imread('circle_image.png');
props.EdgeColor = 'none';
figure();
n = 50;
[X,Y,Z] = sphere(n) ;
X1 = X * r;
Y1 = Y * r;
Z1 = Z * r;
for i = 1:n+1
for j = 1:n+1
if Z1(i,j) < r2
X1(i,j) = NaN;
Y1(i,j) = NaN;
Z1(i,j) = NaN;
end
end
end
my_dome = surf(X1,Y1,Z1,props) ;
alpha = 1;
set(kopula, 'FaceColor', 'texturemap', 'CData', cdata, 'FaceAlpha', alpha, 'EdgeColor', 'none');
axis equal
What I am getting looks like this:
It seems like the image is centred in the wrong place, or even in the wrong axis. How can I fix that?
Yes, the image is centered in the wrong place.
The texture mapping is applied on the whole surface, not just the "active" points (the one you didn't NaN). Basically, what you are getting is the image being spread onto the full sphere, and then when you crop the top of the sphere to get your dome, the image is cropped as well:
What you need to do, is actually remove all those points you converted to NaN, so they are not part of the surface at all and the texture mapping is aplied only on the top dome surface.
So replace your nested for loop with the following code:
idx_Raws2crop = Z1(:,1) < r2 ;
X1(idx_Raws2crop,:) = [] ;
Y1(idx_Raws2crop,:) = [] ;
Z1(idx_Raws2crop,:) = [] ;
Then continue with your code, you'll get:
Actually I also added the instruction:
cdata = flipud(cdata) ;
in order to have the THE CIRCLEwriting in the proper orientation (otherwise it appears upside down).
Edit:
To render the picture on the dome in the way you'd like according to your comment, I can see 2 options:
Options 1: Build upon what we have already
This will consist of:
Extend the [X,Y] domain on a square grid (so the picture is not distorted when texture mapped onto the surface).
Extend the picture itself (add some transparent margin), so the margin will cover the domain extension we introduced and the actual visible part of the picture will be nicely centered on the dome.
With the same X1, Y1 and Z1 than we obtained with the code above:
% Create a square grid large enough to cover the dome and a bit more:
[X2,Y2] = meshgrid(linspace(-5,5,100),linspace(-5,5,100)) ;
% reinterpolate Z1 over this new grid
Z2 = griddata(X1,Y1,Z1,X2,Y2) ;
Now your surface looks like this:
As I warned you, the image is now properly applied but the edge of the dome looks rather ugly. To ease that you could replace the NaN with the base value for your dome, this will transition a lot better between dome and flat domain:
Z2(isnan(Z2)) = 9 ;
Will now yield:
The next problem, as you can see, is that (as in your first question), the image is stretched onto the whole surface. So part of the writing is now on the flat part of the surface. To simply alleviate that, you can modify the picture (add a bit of transparent margin on every side), until the visible part of the image matches the dome size.
Options 2: Build your surface differently
This is actually easier and more straighforward. We will build a dome out of a square surface in the first place (no trimming and NaNing).
This code does not require any part of your previous code, it is self contained:
r = 10 ;
% Build a square grid
[X2,Y2] = meshgrid(linspace(-5,5,100),linspace(-5,5,100)) ;
% build Z2 according to the sphere equation.
Z2 = sqrt( r^2 - X2.^2 - Y2.^2) ;
figure();
my_dome = surf(X2,Y2,Z2,'EdgeColor', 'none', 'FaceColor', 'texturemap', 'CData', cdata, 'FaceAlpha', 1) ;
axis equal
Which yields a nicely centered texture map:
Note: The texture mapping works so well because your actual surface is still a square, only bent a bit to conform to a sphere. The texture mapping does not display the part of the surface where the picture is transparent, so it is invisible, but if you look at your surface without the texture mapping you'll understand what went on in the background:
hs=surf(X2,Y2,Z2) ; shading interp ; axis equal

Calculate 3D distance based on change in intensity

I have three sections (top, mid, bot) of grayscale images (3D). In each section, I have a point with coordinates (x,y) and intensity values [0-255]. The distance between each section is 20 pixels.
I created an illustration to show how those images were generated using a microscope:
Illustration
Illustration (side view): red line is the object of interest. Blue stars represents the dots which are visible in top, mid, bot section. The (x,y) coordinates of these dots are known. The length of the object remains the same but it can rotate in space - 'out of focus' (illustration shows a rotating line at time point 5). At time point 1, the red line is resting (in 2D image: 2 dots with a distance equal to the length of the object).
I want to estimate the x,y,z-coordinate of the end points (represents as stars) by using the changes in intensity, the knowledge about the length of the object and the information in the sections I have. Any help would be appreciated.
Here is an example of images:
Bot section
Mid section
Top section
My 3D PSF data:
https://drive.google.com/file/d/1qoyhWtLDD2fUy2zThYUgkYM3vMXxNh64/view?usp=sharing
Attempt so far:
enter image description here
I guess the correct approach would be to record three images with slightly different z-coordinates for your bot and your top frame, then do a 3D-deconvolution (using Richardson-Lucy or whatever algorithm).
However, a more simple approach would be as I have outlined in my comment. If you use the data for a publication, I strongly recommend to emphasize that this is just an estimation and to include the steps how you have done it.
I'd suggest the following procedure:
Since I do not have your PSF-data, I fake some by estimating the PSF as a 3D-Gaussiamn. Of course, this is a strong simplification, but you should be able to get the idea behind it.
First, fit a Gaussian to the PSF along z:
[xg, yg, zg] = meshgrid(-32:32, -32:32, -32:32);
rg = sqrt(xg.^2+yg.^2);
psf = exp(-(rg/8).^2) .* exp(-(zg/16).^2);
% add some noise to make it a bit more realistic
psf = psf + randn(size(psf)) * 0.05;
% view psf:
%
subplot(1,3,1);
s = slice(xg,yg,zg, psf, 0,0,[]);
title('faked PSF');
for i=1:2
s(i).EdgeColor = 'none';
end
% data along z through PSF's center
z = reshape(psf(33,33,:),[65,1]);
subplot(1,3,2);
plot(-32:32, z);
title('PSF along z');
% Fit the data
% Generate a function for a gaussian distibution plus some background
gauss_d = #(x0, sigma, bg, x)exp(-1*((x-x0)/(sigma)).^2)+bg;
ft = fit ((-32:32)', z, gauss_d, ...
'Start', [0 16 0] ... % You may find proper start points by looking at your data
);
subplot(1,3,3);
plot(-32:32, z, '.');
hold on;
plot(-32:.1:32, feval(ft, -32:.1:32), 'r-');
title('fit to z-profile');
The function that relates the intensity I to the z-coordinate is
gauss_d = #(x0, sigma, bg, x)exp(-1*((x-x0)/(sigma)).^2)+bg;
You can re-arrange this formula for x. Due to the square root, there are two possibilities:
% now make a function that returns the z-coordinate from the intensity
% value:
zfromI = #(I)ft.sigma * sqrt(-1*log(I-ft.bg))+ft.x0;
zfromI2= #(I)ft.sigma * -sqrt(-1*log(I-ft.bg))+ft.x0;
Note that the PSF I have faked is normalized to have one as its maximum value. If your PSF data is not normalized, you can divide the data by its maximum.
Now, you can use zfromI or zfromI2 to get the z-coordinate for your intensity. Again, I should be normalized, that is the fraction of the intensity to the intensity of your reference spot:
zfromI(.7)
ans =
9.5469
>> zfromI2(.7)
ans =
-9.4644
Note that due to the random noise I have added, your results might look slightly different.

How to crop out a detected object (circle) from image and store it?

The code below classifies objects based on their roundness using bwboundaries.
It estimates each object's area and perimeter and uses these results to form a simple metric indicating the roundness of an object with the following metric:
metric = 4*pi*area/perimeter^2
This metric is equal to 1 only for a circle and it is less than one for any other shape. But with this code, I'm using a threshold of 0.80 so that only objects with a metric value greater than 0.80 will be classified as round.
My question is when a given object classified as a round, how can I crop it out from the original image img (not I nor bw) and save it as a new image?
I think using the label matrix and boundary matrix would be enough to do so, but still don't know how to manipulate them.
img=imread('cap.png');
I = rgb2gray(img);
% Step 2: Threshold the Image
bw1 = imbinarize(I);
bw = imcomplement(bw1);
% Step 3: Remove the Noise
bw = bwareaopen(bw,30); % remove small objects
bw = imfill(bw,'holes');
% Step 4: Find the Boundaries
[B,L] = bwboundaries(bw,'noholes');
imshow(label2rgb(L,#jet,[.5 .5 .5]))
hold on
for k = 1:length(B)
boundary = B{k};
plot(boundary(:,2),boundary(:,1),'w','LineWidth',2)
end
% Step 5: Determine which Objects are Round
stats = regionprops(L,'Area','Centroid');
threshold = 0.80;
% loop over the boundaries
for k = 1:length(B)
% obtain (X,Y) boundary coordinates corresponding to label 'k'
boundary = B{k};
% compute a simple estimate of the object's perimeter
delta_sq = diff(boundary).^2;
perimeter = sum(sqrt(sum(delta_sq,2)));
% obtain the area calculation corresponding to label 'k'
area = stats(k).Area;
% compute the roundness metric
metric = 4*pi*area/perimeter^2;
% display the results
metric_string = sprintf('%2.2f',metric);
% Test if the current object classified as a round
if metric > threshold
% HERE, I want to crop the current object from the 'img'
% and save it as a new image
end
end
title(['Metrics closer to 1 indicate that ',...
'the object is approximately round'])
You can additionally add the BoundingBox attribute to regionprops which will effectively give you the limits of where the blob extends within a bounding box, and you can use those to crop your image and save it. It will have the form [x y width height] where x and y are the top left coordinates of the bounding box and width and height are of course the width and height. x would be the column coordinate and y would be the row coordinate. You can use imcrop to finally crop out the image.
img=imread('cap.png');
I = rgb2gray(img);
% Step 2: Threshold the Image
bw1 = imbinarize(I);
bw = imcomplement(bw1);
% Step 3: Remove the Noise
bw = bwareaopen(bw,30); % remove small objects
bw = imfill(bw,'holes');
% Step 4: Find the Boundaries
[B,L] = bwboundaries(bw,'noholes');
imshow(label2rgb(L,#jet,[.5 .5 .5]))
hold on
for k = 1:length(B)
boundary = B{k};
plot(boundary(:,2),boundary(:,1),'w','LineWidth',2)
end
% Step 5: Determine which Objects are Round
stats = regionprops(L,'Area','Centroid','BoundingBox'); % Change
threshold = 0.80;
% loop over the boundaries
for k = 1:length(B)
% obtain (X,Y) boundary coordinates corresponding to label 'k'
boundary = B{k};
% compute a simple estimate of the object's perimeter
delta_sq = diff(boundary).^2;
perimeter = sum(sqrt(sum(delta_sq,2)));
% obtain the area calculation corresponding to label 'k'
area = stats(k).Area;
% compute the roundness metric
metric = 4*pi*area/perimeter^2;
% display the results
metric_string = sprintf('%2.2f',metric);
% Test if the current object classified as a round
if metric > threshold
% HERE, I want to crop the current object from the 'img'
% and save it as a new image
% New - crop image
bb = stats(k).BoundingBox;
img_crop = imcrop(img, bb);
% New - Save the image
imwrite(img_crop, sprintf('crop%d.png', k));
end
end
title(['Metrics closer to 1 indicate that ',...
'the object is approximately round'])
Note that I use imwrite to save the crop to file and it's named based on what blob ID you're looking at. Therefore, if there are multiple blobs or round objects that satisfy the criteria, you will be saving them all.

How to get actual pixel indices from MATLAB function ginput?

I am using the MATLAB function ginput to label my image data for further process. Here is my code:
file_name = "test.jpg";
% Read the image
img = imread(file_name);
% Get the image dimension
imgInfo = imfinfo(file_name);
width = imgInfo.Width;
height = imgInfo.Height;
% Using ginput function to label the image
figure(1);
imshow(img);
hold on;
[x, y] = ginput(4); % Manually label 4 points
scatter(x, y, 100, 'ro', 'filled'); % Plot the marked points on img
hold off;
My Problem:
I found that the output x and yare not integers, so they are not representing the pixel indices.
Sometimes, these two conditions max(x) > width and max(y) > height are satisfied. It seems to suggest that the 4 points I marked using ginput are outside the image (but actually it is not).
I am aware of this issue is related to Image Coordinate System setting, but I am still not sure how to convert x and y obtained from ginput function to the actual pixel indices?
Thanks.
The code below shows a 2x2 image, enlarges the axes so we can see it, then turns on the axes ticks and labels. What this does is allow you to see the coordinate system used to render images in an axes object.
imshow([255,0;0,255])
set(gca,'position',[0.2,0.2,0.6,0.6])
axis on
The coordinates returned by ginput match these coordinates.
In short, what you need to do is simply round the coordinates returned by ginput to get indices into the image:
[x, y] = ginput(4); % Manually label 4 points
x = round(x);
y = round(y);

How can I draw a circle on an image in MATLAB?

I have an image in MATLAB:
im = rgb2gray(imread('some_image.jpg');
% normalize the image to be between 0 and 1
im = im/max(max(im));
And I've done some processing that resulted in a number of points that I want to highlight:
points = some_processing(im);
Where points is a matrix the same size as im with ones in the interesting points.
Now I want to draw a circle on the image in all the places where points is 1.
Is there any function in MATLAB that does this? The best I can come up with is:
[x_p, y_p] = find (points);
[x, y] = meshgrid(1:size(im,1), 1:size(im,2))
r = 5;
circles = zeros(size(im));
for k = 1:length(x_p)
circles = circles + (floor((x - x_p(k)).^2 + (y - y_p(k)).^2) == r);
end
% normalize circles
circles = circles/max(max(circles));
output = im + circles;
imshow(output)
This seems more than somewhat inelegant. Is there a way to draw circles similar to the line function?
You could use the normal PLOT command with a circular marker point:
[x_p,y_p] = find(points);
imshow(im); %# Display your image
hold on; %# Add subsequent plots to the image
plot(y_p,x_p,'o'); %# NOTE: x_p and y_p are switched (see note below)!
hold off; %# Any subsequent plotting will overwrite the image!
You can also adjust these other properties of the plot marker: MarkerEdgeColor, MarkerFaceColor, MarkerSize.
If you then want to save the new image with the markers plotted on it, you can look at this answer I gave to a question about maintaining image dimensions when saving images from figures.
NOTE: When plotting image data with IMSHOW (or IMAGE, etc.), the normal interpretation of rows and columns essentially becomes flipped. Normally the first dimension of data (i.e. rows) is thought of as the data that would lie on the x-axis, and is probably why you use x_p as the first set of values returned by the FIND function. However, IMSHOW displays the first dimension of the image data along the y-axis, so the first value returned by FIND ends up being the y-coordinate value in this case.
This file by Zhenhai Wang from Matlab Central's File Exchange does the trick.
%----------------------------------------------------------------
% H=CIRCLE(CENTER,RADIUS,NOP,STYLE)
% This routine draws a circle with center defined as
% a vector CENTER, radius as a scaler RADIS. NOP is
% the number of points on the circle. As to STYLE,
% use it the same way as you use the rountine PLOT.
% Since the handle of the object is returned, you
% use routine SET to get the best result.
%
% Usage Examples,
%
% circle([1,3],3,1000,':');
% circle([2,4],2,1000,'--');
%
% Zhenhai Wang <zhenhai#ieee.org>
% Version 1.00
% December, 2002
%----------------------------------------------------------------
Funny! There are 6 answers here, none give the obvious solution: the rectangle function.
From the documentation:
Draw a circle by setting the Curvature property to [1 1]. Draw the circle so that it fills the rectangular area between the points (2,4) and (4,6). The Position property defines the smallest rectangle that contains the circle.
pos = [2 4 2 2];
rectangle('Position',pos,'Curvature',[1 1])
axis equal
So in your case:
imshow(im)
hold on
[y, x] = find(points);
for ii=1:length(x)
pos = [x(ii),y(ii)];
pos = [pos-0.5,1,1];
rectangle('position',pos,'curvature',[1 1])
end
As opposed to the accepted answer, these circles will scale with the image, you can zoom in an they will always mark the whole pixel.
Hmm I had to re-switch them in this call:
k = convhull(x,y);
figure;
imshow(image); %# Display your image
hold on; %# Add subsequent plots to the image
plot(x,y,'o'); %# NOTE: x_p and y_p are switched (see note below)!
hold off; %# Any subsequent plotting will overwrite the image!
In reply to the comments:
x and y are created using the following code:
temp_hull = stats_single_object(k).ConvexHull;
for k2 = 1:length(temp_hull)
i = i+1;
[x(i,1)] = temp_hull(k2,1);
[y(i,1)] = temp_hull(k2,2);
end;
it might be that the ConvexHull is the other way around and therefore the plot is different. Or that I made a mistake and it should be
[x(i,1)] = temp_hull(k2,2);
[y(i,1)] = temp_hull(k2,1);
However the documentation is not clear about which colum = x OR y:
Quote: "Each row of the matrix contains the x- and y-coordinates of one vertex of the polygon. "
I read this as x is the first column and y is the second colum.
In newer versions of MATLAB (I have 2013b) the Computer Vision System Toolbox contains the vision.ShapeInserter System object which can be used to draw shapes on images. Here is an example of drawing yellow circles from the documentation:
yellow = uint8([255 255 0]); %// [R G B]; class of yellow must match class of I
shapeInserter = vision.ShapeInserter('Shape','Circles','BorderColor','Custom','CustomBorderColor',yellow);
I = imread('cameraman.tif');
circles = int32([30 30 20; 80 80 25]); %// [x1 y1 radius1;x2 y2 radius2]
RGB = repmat(I,[1,1,3]); %// convert I to an RGB image
J = step(shapeInserter, RGB, circles);
imshow(J);
With MATLAB and Image Processing Toolbox R2012a or newer, you can use the viscircles function to easily overlay circles over an image. Here is an example:
% Plot 5 circles at random locations
X = rand(5,1);
Y = rand(5,1);
% Keep the radius 0.1 for all of them
R = 0.1*ones(5,1);
% Make them blue
viscircles([X,Y],R,'EdgeColor','b');
Also, check out the imfindcircles function which implements the Hough circular transform. The online documentation for both functions (links above) have examples that show how to find circles in an image and how to display the detected circles over the image.
For example:
% Read the image into the workspace and display it.
A = imread('coins.png');
imshow(A)
% Find all the circles with radius r such that 15 ≤ r ≤ 30.
[centers, radii, metric] = imfindcircles(A,[15 30]);
% Retain the five strongest circles according to the metric values.
centersStrong5 = centers(1:5,:);
radiiStrong5 = radii(1:5);
metricStrong5 = metric(1:5);
% Draw the five strongest circle perimeters.
viscircles(centersStrong5, radiiStrong5,'EdgeColor','b');
Here's the method I think you need:
[x_p, y_p] = find (points);
% convert the subscripts to indicies, but transposed into a row vector
a = sub2ind(size(im), x_p, y_p)';
% assign all the values in the image that correspond to the points to a value of zero
im([a]) = 0;
% show the new image
imshow(im)

Resources