I'm trying to add thickness to a gyroid mesh and export this to STL. I can successfully plot the thickened gyroid mesh as shown below, but this is because I wrote a function to remove undesirable faces, namely, faces which have edges longer than a certain limit. I should also mention that all faces are triangular, so both the vertices and faces matrices are in N x 3 form.
Here's the code which checks for edge lengths and removes those which are too_long:
function [F] = remove_bad_faces(F,V,too_long)
%% Initiate various counters/storage matrices
bad_face_count = 0; % Number of bad faces
bad_faces = []; % Matrix of bad faces
bad_vertices = []; % Matrix of bad vertices
bad_face_indexes = []; % Matrix of bad face row indices. I know that the plural of index is not indexes.
%% Find all bad faces
for i=1:size(F,1)
face = F(i,:);
v1 = V(face(1),:);
v2 = V(face(2),:);
v3 = V(face(3),:);
if norm(v1-v2) >= too_long || norm(v2-v3) >= too_long || norm(v1-v3) >= too_long
bad_face_count = bad_face_count + 1;
bad_faces = [bad_faces; face];
bad_vertices = [bad_vertices;v1;v2;v3];
bad_face_indexes = [bad_face_indexes,i];
end
end
%% Remove bad faces
for j=1:size(bad_face_indexes,2)
index = bad_face_indexes(j);
F(index,:) = nan;
end
end
As you can see, I'm replacing "bad faces" (i.e. rows of the F matrix) with NaN, but this only works for visualisation. When I try to pass the resultant faces and vertices to an STL generating function (see here), I get the following error:
Index in position 2 is invalid. Array indices must be positive integers or logical values.
Error in stlwrite (line 76)
facets = reshape(facets(:,faces'), 3, 3, []);
Error in Gyroid_Mesh_Script (line 60)
stlwrite('gyroid_test.stl', F, V)
So clearly the NaN is the problem, and I've tried swapping it with [] and 0, but neither has worked. Any help is much appreciated, thanks in advance!
Edit:
When I use [] instead of NaN, I can export to STL, but it includes these "bad faces", shown below (zoomed-in view):
Thanks to #benJephunneh's response! That fixed everything. I'm posting the updated function which removes faces whose edges are too_long:
function [F] = remove_bad_faces(F,V,too_long)
%% Initiate various counters/storage matrices
bad_faces = []; % Matrix of bad faces
bad_vertices = []; % Matrix of bad vertices
bad_face_indexes = []; % Matrix of bad face row indices. I know that the plural of index is not indexes.
%% Find all bad faces
for i=1:size(F,1)
face = F(i,:);
v1 = V(face(1),:);
v2 = V(face(2),:);
v3 = V(face(3),:);
if norm(v1-v2) >= too_long || norm(v2-v3) >= too_long || norm(v1-v3) >= too_long
bad_faces = [bad_faces; face];
bad_vertices = [bad_vertices;v1;v2;v3];
bad_face_indexes = [bad_face_indexes,i];
end
end
bad_face_count = numel(bad_face_indexes);
%% Remove bad faces
F(bad_face_indexes,:) = [];
end
It also successfully exports to STL using stlwrite('gyroid_test.stl', F, V)
Related
bet{j,3} = react{j};
numBins = {};
edges = linspace(min(bet{j,3}), max(bet{j,3}), numBins(bet{j,3}));
[N, whichBin] = histc(bet{j,3}, edges);
binsize = NaN*zeros(size(bins));
for k = 1:numBins
bin = find(whichBin == k);
binMembers = bet{j,3}(bin);
if (~isempty(bin))
mu(k) = mean(y(bin));
end
end
error on
edges = linspace(min(bet{j,3}), max(bet{j,3}), numBins(bet{j,3})); that says it exceeds matrix dimensions
Any suggestions to what could be the problem, as well as suggestions if this is code might work for binning data (e.g., reaction time)?
Your line numBins = {}; creates an empty cell array. But in numBins(bet{j,3})); you are trying to access an element. As there is none it fails on index exceeds matrix dimension.
I have the following function that works perfectly, but I would like to apply vectorization to it...
for i = 1:size(centroids,1)
centroids(i, :) = mean(X(idx == i, :));
end
It checks if idx matches the current index and if it does, it calculates the mean value for all the X values that correspond to that index.
This is my attempt at vectorization, my solution does not work and I know why...
centroids = mean(X(idx == [1:size(centroids,1)], :));
The following idx == [1:size(centroids,1)] breaks the code. I have no idea how to check if idx equals to either of the numbers from 1 to size(centroids,1).
tl:dr
Get rid of the for loop through vectorization
One option is to use arrayfun;
nIdx = size(centroids,1);
centroids = arrayfun(#(ii) mean(X(idx==ii,:)),1:nIdx, 'UniformOutput', false);
centroids = vertcat(centroids{:})
Since the output of a single function call is not necessarily a scalar, the UniformOutput option has to be set to false. Thus, arrayfun returns a cell array and you need to vertcat it to get the desired double array.
you can split the matrix into cells and take the mean from each cell using cellfun (which applies a loop in its inner operation):
generate data:
dim = 10;
N = 400;
nc = 20;
idx = randi(nc,[N 1]);
X = rand(N,dim);
centroids = zeros(nc,dim);
mean using loop (the question's method)
for i = 1:size(centroids,1)
centroids(i, :) = mean(X(idx == i, :));
end
vectorizing:
% split X into cells by idx
A = accumarray(idx, (1:N)', [nc,1], #(i) {X(i,:)});
% mean of each cell
C = cell2mat(cellfun(#(x) mean(x,1),A,'UniformOutput',0));
maximum absolute error between the methods:
max(abs(C(:) - centroids(:))) % about 1e-16
Using the answer found here, I was able to draw a nice torus. I would like to draw two "circles" on this torus, the first of which is standard. The second should be somewhat random, perhaps like a ladder, ideally on the opposite side from the first. It might be kind of jagged, but should remain on the edges of the lattice and should connect back to itself. Here is what I have so far
am = 1.;
rm = 3.;
t = linspace(-pi,pi,16);
p = linspace(0.,2.*pi,16);
[t,p] = meshgrid(p,t);
x = (rm + am.*cos(p)).*cos(t);
y = (rm + am.*cos(p)).*sin(t);
z = am.*sin(p);
hsurf = surf(x,y,z);
axis equal;
set(hsurf, 'FaceColor','interp','FaceAlpha',0.5,'EdgeAlpha',0.25);
hold on
plot3((rm+am.*cos(p)).*cos(t(8)),(rm+am.*cos(p)).*sin(t(8)),am.*sin(p), 'b', 'LineWidth',2);
I have the standard loop, but can't get the jagged one. I have been trying to specify the other loop by specifying the vertices, but it doesn't seem to be working. Can I specify a curve on this mesh by specifying its vertices? If not, are there any suggestions? Thanks for any help you can provide.
EDIT: Sorry for being vague earlier. The picture below shows what I would like. Note that the right circle is given by the above code, whereas the left hand 'circle' is drawn by hand, and is what I would like to automate.
If you want the vertices of the (jagged or not) circle to match the points of the surface, you do not need to recalculate any points. You can simply re-use some of the points of your surface, choosing the path which define the shape you want.
I'll use your code to define the initial tore, with just a slight modification: I'll use the upper-case letter for variable which define a matrix (and lower case for variables which are simple 1D vector). So your initial tore becomes:
%% // define initial tore
am = 1.; rm = 3.;
t = linspace(-pi,pi,16);
p = linspace(0,2.*pi,16);
[T,P] = meshgrid(p,t);
X = (rm + am.*cos(P)).*cos(T);
Y = (rm + am.*cos(P)).*sin(T);
Z = am.*sin(P);
hsurf = surf(X,Y,Z,'FaceColor','interp','FaceAlpha',0.5,'EdgeAlpha',0.25);
axis equal ; hold on
Not a big deal but this allow to have only 1D vector coordinates in the case where you define your circle by equation as you were doing. It keeps your variables and workspace cleaner and reduce the risk of mistakes and dimension errors.
For the simple circle, you could redefine the coordinate by equation as you did (developed here for clarity)
%% // your simple circle - calculated
colID = 8 ;
xs = (rm+am.*cos(p)).*cos(t(colID)) ;
ys = (rm+am.*cos(p)).*sin(t(colID)) ;
zs = am.*sin(p) ;
hp = plot3(xs,ys,zs, 'b', 'LineWidth',2);
%// This was generating 2D arrays (matrices) for xs, ys and zs when you
%// were using the meshed version of T and P. Using only the vector version of
%// 't' and 'p' generate only vector coordinates. => cleaner and safer.
But I'd like to bring your attention to another way, just use the points of the surface already defined. This (i) eliminates the need for new calculations, and (ii) insure that the vertices of the circle will be matching exactly some vertices of the tore.
%% // another simple circle - extracted from the tore vertices
colID = 8 ;
xs = X(:,colID) ;
ys = Y(:,colID) ;
zs = Z(:,colID) ;
hpc = plot3(xs,ys,zs, 'b', 'LineWidth',2);
Again, there are extra lines for clarity but you can compact the code once you understand what is going on. These 2 different ways of producing a circle result in the following figures:
For illustration purpose, I highlighted the vertices of the tore (small black dots), so you can see if the circles vertices match or not.
Now for the jagged circle, the last method presented will have even more interest. As you observed, to get a circle coordinate we simply extracted one column of each of the tore matrix coordinates. We used a fixed value for the colID to retrieve. To "jagg" your circle, we just need to introduce a small perturbation in the column number, so we will occasionally retrieve the adjacent vertex coordinates.
The following code generate a column ID varying around the staring column ID. You can define the maximum total spread as well as the maximum single increment:
%% // generate index of columns to be retrieved
idxcol = zeros(size(X,1),1) ;
maxDeviation = 5 ; %// total maximum deviation to the initial center line
maxPerturbation = 2 ; %// max deviation for 1 increment
deviation = cumsum(randi([-maxPerturbation maxPerturbation],size(X,1),1)) ;
%// bound deviations to maximum values
deviation(deviation>maxDeviation) = maxDeviation ;
deviation(deviation<-maxDeviation) = -maxDeviation ;
startColID = 8 ;
colID = startColID + deviation ;
%// close the profile by repeating first point in the end if it's not closed already
if colID(end) ~= colID(1) ; colID(end) = colID(1) ; end
If we now use coordinate from theses columns, we get:
npt = size(colID,1) ;
xcj = zeros(npt) ; ycj = xcj ; zcj = xcj ;
for k=1:npt
xcj(k) = X( k , colID(k) ) ;
ycj(k) = Y( k , colID(k) ) ;
zcj(k) = Z( k , colID(k) ) ;
end
hpc = plot3(xcj,ycj,zcj, 'b', 'LineWidth',2);
It is a closed profile turning around the tore but the lines do not match the lines of the surface as in your drawn example. This is because the transition from one column ID to another happen at each change of x index.
To rectify that, we can use the stairs function:
%% // create a stepped profile
[xd,yd] = stairs(colID) ;
%% // display rectified jagged circle
npt = size(yd,1) ;
xcj = zeros(npt) ; ycj = xcj ; zcj = xcj ;
for k=1:npt
xcj(k) = X( xd(k) , yd(k) ) ;
ycj(k) = Y( xd(k) , yd(k) ) ;
zcj(k) = Z( xd(k) , yd(k) ) ;
end
hpc = plot3(xcj,ycj,zcj, 'b', 'LineWidth',2);
This will now generate the following profile:
Much better ... However, when the column ID moves by more than one index for the same x, we still miss anchor points to keep the jagged circle profile matching the tore profile. If your basic column deviation increment is more than one, you will even have many more of these artefact.
To completely rectify that, we need to add the missing anchor points. I couldn't find a built-in function to do that so I went the good old loop way. Feel free to optimize if you find some way.
%% // recreate index vectors with all the necessary anchor points
colX(1,1) = xd(1) ;
colY(1,1) = yd(1) ;
k = 2 ;
for t=2:size(yd,1)
inc = sign(yd(t)-yd(t-1)) ; %// define increment of 1 with correct sign
ids = yd(t-1):inc:yd(t)-inc ; %// range of index to be covered
if isempty(ids) ; ids = yd(t) ; end %// catch corner cases
%// now add these points to the list
n = length(ids) ;
colX(k:k+n-1,1) = xd(t-1) ;
colY(k:k+n-1,1) = ids ;
k=k+n;
end
%// close profile (add last point in the cases where it is necessary)
if inc ~= 0
colX(end+1,1) = xd(end) ;
colY(end+1,1) = yd(end) ;
end
%% // display fully rectified jagged circle
npt = size(colX,1) ;
xcj = zeros(npt) ; ycj = xcj ; zcj = xcj ;
for k=1:npt
xcj(k) = X( colX(k) , colY(k) ) ;
ycj(k) = Y( colX(k) , colY(k) ) ;
zcj(k) = Z( colX(k) , colY(k) ) ;
end
And now we don't have gaps between two column ID any more, all the points of the jagged circle match a point of the surface profile:
I'm new to Matlab.
I'm trying to apply PCA function(URL listed below)into my palm print recognition program to generate the eigenpalms. My palm print grey scale images dimension are 450*400.
Before using it, I was trying to study these codes and add some codes to save the eigenvector as .mat file. Some of the %comments added by me for my self understanding.
After a few days of studying, I still unable to get the answers.
I decided to ask for helps.I have a few questions to ask regarding this PCA.m.
PCA.m
What is the input of the "options" should be? of "PCA(data,details,options)"
(is it an integer for reduced dimension? I was trying to figure out where is the "options" value passing, but still unable to get the ans. The msgbox of "h & h2", is to check the codes run until where. I was trying to use integer of 10, but the PCA.m processed dimension are 400*400.)
The "eigvector" that I save as ".mat" file is ready to perform Euclidean distance classifier with other eigenvector? (I'm thinking that eigvector is equal to eigenpalm, like in face recognition, the eigen faces. I was trying to convert the eigenvector matrix back to image, but the image after PCA process is in Black and many dots on it)
mySVD.m
In this function, there are two values that can be changed, which are MAX_MATRIX_SIZE set by 1600 and EIGVECTOR_RATIO set by 0.1%. May I know these values will affect the results? ( I was trying to play around with the values, but I cant see the different. My palm print image dimension is set by 450*400, so the Max_matrix_size should set at 180,000?)
** I hope you guys able to understand what I'm asking, please help, Thanks guys (=
Original Version : http://www.cad.zju.edu.cn/home/dengcai/Data/code/PCA.m
mySVD: http://www.cad.zju.edu.cn/home/dengcai/Data/code/mySVD.m
% Edited Version by me
function [eigvector, eigvalue] = PCA(data,details,options)
%PCA Principal Component Analysis
%
% Usage:
% [eigvector, eigvalue] = PCA(data, options)
% [eigvector, eigvalue] = PCA(data)
%
% Input:
% data - Data matrix. Each row vector of fea is a data point.
% fea = finite element analysis ?????
% options.ReducedDim - The dimensionality of the reduced subspace. If 0,
% all the dimensions will be kept.
% Default is 0.
%
% Output:
% eigvector - Each column is an embedding function, for a new
% data point (row vector) x, y = x*eigvector
% will be the embedding result of x.
% eigvalue - The sorted eigvalue of PCA eigen-problem.
%
% Examples:
% fea = rand(7,10);
% options=[]; %store an empty matrix in options
% options.ReducedDim=4;
% [eigvector,eigvalue] = PCA(fea,4);
% Y = fea*eigvector;
%
% version 3.0 --Dec/2011
% version 2.2 --Feb/2009
% version 2.1 --June/2007
% version 2.0 --May/2007
% version 1.1 --Feb/2006
% version 1.0 --April/2004
%
% Written by Deng Cai (dengcai AT gmail.com)
%
if (~exist('options','var'))
%A = exist('name','kind')
% var = Checks only for variables.
%http://www.mathworks.com/help/matlab/matlab_prog/symbol-reference.html#bsv2dx9-1
%The tilde "~" character is used in comparing arrays for unequal values,
%finding the logical NOT of an array,
%and as a placeholder for an input or output argument you want to omit from a function call.
options = [];
end
h2 = msgbox('not yet');
ReducedDim = 0;
if isfield(options,'ReducedDim')
%tf = isfield(S, 'fieldname')
h2 = msgbox('checked');
ReducedDim = options.ReducedDim;
end
[nSmp,nFea] = size(data);
if (ReducedDim > nFea) || (ReducedDim <=0)
ReducedDim = nFea;
end
if issparse(data)
data = full(data);
end
sampleMean = mean(data,1);
data = (data - repmat(sampleMean,nSmp,1));
[eigvector, eigvalue] = mySVD(data',ReducedDim);
eigvalue = full(diag(eigvalue)).^2;
if isfield(options,'PCARatio')
sumEig = sum(eigvalue);
sumEig = sumEig*options.PCARatio;
sumNow = 0;
for idx = 1:length(eigvalue)
sumNow = sumNow + eigvalue(idx);
if sumNow >= sumEig
break;
end
end
eigvector = eigvector(:,1:idx);
end
%dt get from C# program, user ID and name
evFolder = 'ev\';
userIDName = details; %get ID and Name
userIDNameWE = strcat(userIDName,'\');%get ID and Name with extension
filePath = fullfile('C:\Users\***\Desktop\Data Collection\');
userIDNameFolder = strcat(filePath,userIDNameWE); %ID and Name folder
userIDNameEVFolder = strcat(userIDNameFolder,evFolder);%EV folder in ID and Name Folder
userIDNameEVFile = strcat(userIDNameEVFolder,userIDName); % EV file with ID and Name
if ~exist(userIDNameEVFolder, 'dir')
mkdir(userIDNameEVFolder);
end
newFile = strcat(userIDNameEVFile,'_1');
searchMat = strcat(newFile,'.mat');
if exist(searchMat, 'file')
filePattern = strcat(userIDNameEVFile,'_');
D = dir([userIDNameEVFolder, '*.mat']);
Num = length(D(not([D.isdir])))
Num=Num+1;
fileName = [filePattern,num2str(Num)];
save(fileName,'eigvector');
else
newFile = strcat(userIDNameEVFile,'_1');
save(newFile,'eigvector');
end
You pass options in a structure, for instance:
options.ReducedDim = 2;
or
options.PCARatio =0.4;
The option ReducedDim selects the number of dimensions you want to use to represent the final projection of the original matrix. For instance if you pick option.ReducedDim = 2 you use only the two eigenvectors with largest eigenvalues (the two principal components) to represent your data (in effect the PCA will return the two eigenvectors with largest eigenvalues).
PCARatio instead allows you to pick the number of dimensions as the first eigenvectors with largest eigenvalues that account for fraction PCARatio of the total sum of eigenvalues.
In mySVD.m, I would not increase the default values unless you expect more than 1600 eigenvectors to be necessary to describe your dataset. I think you can safely leave the default values.
I am on a project thumb recognition system on matlab. I implemented Kmean Algorithm and I got results as well. Actually now I want to plot the results like here they done. I am trying but couldn't be able to do so. I am using the following code.
load training.mat; % loaded just to get trainingData variable
labelData = zeros(200,1);
labelData(1:100,:) = 0;
labelData(101:200,:) = 1;
k=2;
[trainCtr, traina] = kmeans(trainingData,k);
trainingResult1=[];
for i=1:k
trainingResult1 = [trainingResult1 sum(trainCtr(1:100)==i)];
end
trainingResult2=[];
for i=1:k
trainingResult2 = [trainingResult2 sum(trainCtr(101:200)==i)];
end
load testing.mat; % loaded just to get testingData variable
c1 = zeros(k,1054);
c1 = traina;
cluster = zeros(200,1);
for j=1:200
testTemp = repmat(testingData(j,1:1054),k,1);
difference = sum((c1 - testTemp).^2, 2);
[value index] = min(difference);
cluster(j,1) = index;
end
testingResult1 = [];
for i=1:k
testingResult1 = [testingResult1 sum(cluster(1:100)==i)];
end
testingResult2 = [];
for i=1:k
testingResult2 = [testingResult2 sum(cluster(101:200)==i)];
end
in above code trainingData is matrix of 200 X 1054 in which 200 are images of thumbs and 1054 are columns. actually each image is of 25 X 42. I reshaped each image in to row matrix (1 X 1050) and 4 other (some features) columns so total of 1054 columns are in each image. Similarly testingData I made it in the similar manner as I made testingData It is also the order of 200 X 1054. Now my Problem is just to plot the results as they did in here.
After selecting 2 features, you can just follow the example. Start a figure, use hold on, and use plot or scatter to plot the centroids and the data points. E.g.
selectedFeatures = [42,43];
plot(trainingData(trainCtr==1,selectedFeatures(1)),
trainingData(trainCtr==1,selectedFeatures(2)),
'r.','MarkerSize',12)
Would plot the selected feature values of the data points in cluster 1.