diff --git a/get_cnn_activations.m b/cnn_shape_get_activations.m similarity index 74% rename from get_cnn_activations.m rename to cnn_shape_get_activations.m index 5251acd..5b5e9ac 100644 --- a/get_cnn_activations.m +++ b/cnn_shape_get_activations.m @@ -1,23 +1,27 @@ -function [ feat ] = get_cnn_activations( im, net, subWins, layers, varargin) -%GET_CNN_FEATURE Get CNN activation responses for im +function [ feat ] = cnn_shape_get_activations(im, net, layers, subWins, varargin) +%CNN_SHAPE_GET_ACTIVATIONS Get CNN activation responses for im % im:: % image matrix, #channels (size(im,3)) must be compatible with net % 0~255 % net:: % cnn model structure +% layers:: {'relu7'} +% can be either a structure (.name, .sizes, .index) or string array % subWins:: [0; 1; 0; 1; 0] % see get_augmentation_matrix.m for details -% layers:: {'fc7'} -% can be either a structure (.name, .sizes, .index) or string array % `gpuMode`:: false % set to true to compute on GPU -if nargin<4 || isempty(layers), - layers = {'fc7'}; +if ~exist('layers','var') || isempty(layers), + layers = {'relu7'}; end -if nargin<3 || isempty(subWins), +if ~exist('subWins','var') || isempty(subWins), subWins = get_augmentation_matrix('none'); end + +opts.gpuMode = false; +opts = vl_argparse(opts,varargin); + nSubWins = size(subWins,2); if isfield(net.layers{1},'weights'), nChannels = size(net.layers{1}.weights{1},3); @@ -27,8 +31,8 @@ if iscell(im), imCell = im; - im = zeros(net.normalization.imageSize(1), ... - net.normalization.imageSize(2), ... + im = zeros(net.meta.normalization.imageSize(1), ... + net.meta.normalization.imageSize(2), ... nChannels, ... numel(imCell)); for i=1:numel(imCell), @@ -36,7 +40,7 @@ error('image (%d channels) is not compatible with net (%d channels)', ... size(imCell{i},3), nChannels); end - im(:,:,:,i) = imresize(imCell{i}, net.normalization.imageSize(1:2)); + im(:,:,:,i) = imresize(imCell{i}, net.meta.normalization.imageSize(1:2)); end elseif size(im,3) ~= nChannels, error('image (%d channels) is not compatible with net (%d channels)', ... @@ -49,14 +53,11 @@ if numel(viewpoolIdx)>1, error('More than one viewpool layers found!'); end - nViews = net.layers{viewpoolIdx}.stride; + nViews = net.layers{viewpoolIdx}.vstride; else nViews = 1; end -opts.gpuMode = false; -opts = vl_argparse(opts,varargin); - if iscell(layers), layersName = layers; layers = struct; @@ -67,8 +68,8 @@ [~,layers.index] = ismember(layers.name,allLayersName); layers.index = layers.index + 1; % sizes - im0 = zeros(net.normalization.imageSize(1), ... - net.normalization.imageSize(2), nChannels, nViews, 'single') * 255; + im0 = zeros(net.meta.normalization.imageSize(1), ... + net.meta.normalization.imageSize(2), nChannels, nViews, 'single') * 255; if opts.gpuMode, im0 = gpuArray(im0); end res = vl_simplenn(net,im0); layers.sizes = zeros(3,numel(layers.name)); @@ -84,6 +85,10 @@ end im = single(im); +averageImage = net.meta.normalization.averageImage; +if numel(averageImage)==nChannels, + averageImage = reshape(averageImage, [1 1 nChannels]); +end for ri = 1:nSubWins, r = subWins(1:4,ri).*[size(im,2);size(im,2);size(im,1);size(im,1)]; @@ -91,8 +96,8 @@ im_ = im(max(1,r(3)):min(size(im,1),r(3)+r(4)),... max(1,r(1)):min(size(im,2),r(1)+r(2)),:,:); if subWins(5,ri), im_ = flipdim(im_,2); end - im_ = bsxfun(@minus, imresize(im_, net.normalization.imageSize(1:2)), ... - net.normalization.averageImage); + im_ = bsxfun(@minus, imresize(im_, net.meta.normalization.imageSize(1:2)), ... + averageImage); if opts.gpuMode, im_ = gpuArray(im_); end diff --git a/evaluate_classification.m b/evaluate_classification.m deleted file mode 100644 index f6ab01a..0000000 --- a/evaluate_classification.m +++ /dev/null @@ -1,224 +0,0 @@ -function evaluate_classification(feat, varargin) -%EVALUATE_CLASSIFICATION Evaluate CNN features for classification -% -% feat:: -% a structure containing cnn feature -% `imdb`:: [] -% optional, usually a field in feat -% `cv`:: 5 -% #folds in cross validation (-v) -% `log2c`:: [-4:2:4] -% tunable liblinear svm parameter (-c) -% `quiet`:: true -% liblinear parameter (-q) -% `multiview`:: true -% set to false to evaluate on every single views of same instances -% `method`:: 'avgdesc' -% used only if multiview is true; other choices: 'maxdesc','avgsvmscore' -% `logPath`:: 'log/eval.txt' -% place to save log information -% `predPath`:: 'data/pred.mat' -% place to save prediction results -% `confusionPath`:: 'data/confusion.pdf' -% place to save confusion matrix plot -% -% NOTE: assume all classes in imdb appear in training set - -% default options -opts.imdb = []; -opts.cv = 5; -opts.log2c = [-4:2:4]; -opts.quiet = true; -opts.multiview = true; -opts.method = 'avgsdesc'; -opts.logPath = fullfile('log','eval.txt'); -opts.predPath = fullfile('data','pred.mat'); -[opts, varargin] = vl_argparse(opts, varargin) ; - -opts.confusionPath = fullfile(fileparts(opts.predPath), 'confusion.pdf'); -[opts, varargin] = vl_argparse(opts, varargin) ; - -if ~exist(fileparts(opts.logPath),'dir'), - vl_xmkdir(fileparts(opts.logPath)); -end -if ~exist(fileparts(opts.predPath),'dir'), - vl_xmkdir(fileparts(opts.predPath)); -end -if ischar(feat), - feat = load(feat); -end - -% ------------------------------------------------------------------------- -% sort imdb.images & feat.x w.r.t (sid,view) or (id) -% ------------------------------------------------------------------------- -if isfield(feat,'imdb'), - imdb = feat.imdb; -else - imdb = opts.imdb; -end -if ~isfield(imdb.images,'sid'), - nInstances = numel(imdb.images.name); -else - nInstances = length(unique(imdb.images.sid)); -end - -% sort imdb.images wrt id -[imdb.images.id,I] = sort(imdb.images.id); -imdb.images.name = imdb.images.name(I); -imdb.images.class = imdb.images.class(I); -imdb.images.set = imdb.images.set(I); -if isfield(imdb.images,'sid'), imdb.images.sid = imdb.images.sid(I); end - -% sort feat.x wrt id/sid -if isfield(feat, 'sid'), - pooledFeat = true; - opts.multiview = false; - [feat.sid,I] = sort(feat.sid); - feat.x = feat.x(I,:); -else - pooledFeat = false; - [feat.id,I] = sort(feat.id); - feat.x = feat.x(I,:); -end - -% sort imdb.images wrt sid -if isfield(imdb.images,'sid'), - [imdb.images.sid, I] = sort(imdb.images.sid); - imdb.images.name = imdb.images.name(I); - imdb.images.class = imdb.images.class(I); - imdb.images.set = imdb.images.set(I); - imdb.images.id = imdb.images.id(I); - if ~pooledFeat, feat.x = feat.x(I,:); end -end - -% ------------------------------------------------------------------------- -% feature descriptors -% ------------------------------------------------------------------------- -nViews = length(imdb.images.name)/nInstances; -nDescPerShape = size(feat.x,1)/nInstances; -shapeGtClasses = imdb.images.class(1:nViews:end); -shapeSets = imdb.images.set(1:nViews:end); -nDims = size(feat.x,2); - -% train & val -trainSets = {'train','val'}; -testSets = {'test'}; -[~,I] = ismember(trainSets,imdb.meta.sets); -trainSids = find(ismember(shapeSets, I)); -tmp = zeros(nDescPerShape, nInstances); -tmp(:,trainSids) = 1; -trainFeat = feat.x(find(tmp)',:); -trainLabel = shapeGtClasses(trainSids)'; -nTrainShapes = length(trainLabel); - -% test -[~,I] = ismember(testSets,imdb.meta.sets); -testSids = find(ismember(shapeSets, I)); -tmp = zeros(nDescPerShape, nInstances); -tmp(:,testSids) = 1; -testFeat = feat.x(find(tmp)',:); -testLabel = shapeGtClasses(testSids)'; -nTestShapes = length(testLabel); - -if ~pooledFeat, - if opts.multiview && strcmp(opts.method,'avgdesc'), - % average descriptor across views - trainFeat = reshape(mean(reshape(trainFeat, ... - [nDescPerShape nTrainShapes*nDims]),1),[nTrainShapes nDims]); - testFeat = reshape(mean(reshape(testFeat, ... - [nDescPerShape nTestShapes*nDims]),1),[nTestShapes nDims]); - elseif opts.multiview && strcmp(opts.method,'maxdesc'), - % max descriptor across views - trainFeat = reshape(max(reshape(trainFeat, ... - [nDescPerShape nTrainShapes*nDims]),[],1),[nTrainShapes nDims]); - testFeat = reshape(max(reshape(testFeat, ... - [nDescPerShape nTestShapes*nDims]),[],1),[nTestShapes nDims]); - else - % expand labels across views - trainLabel = reshape(repmat(trainLabel',[nDescPerShape 1]),... - [nDescPerShape*nTrainShapes 1]); - testLabel = reshape(repmat(testLabel',[nDescPerShape 1]),... - [nDescPerShape*nTestShapes 1]); - end -end -trainFeat = sparse(double(trainFeat)); -testFeat = sparse(double(testFeat)); - - -% ------------------------------------------------------------------------- -% train and evaluate SVM -% ------------------------------------------------------------------------- -if exist(opts.predPath, 'file'), - load(opts.predPath); - fprintf('SVM model and predictions loaded from %s. \n', opts.predPath); -else - fprintf('Evaluating ... \n'); - - bestcv = 0; - for log2c = opts.log2c, - cmd = ['-v ', num2str(opts.cv) ,' -c ', num2str(2^log2c)]; - if opts.quiet, cmd = [cmd ' -q']; end; - cv = liblinear_train(trainLabel,trainFeat,cmd); - if (cv >= bestcv), - bestcv = cv; bestc = 2^log2c; - end - fprintf('%g %g (best c=%g, rate=%g)\n', log2c, cv, bestc, bestcv); - end - - cmd = ['-c ', num2str(bestc)]; - if opts.quiet, cmd = [cmd ' -q']; end; - model = liblinear_train(trainLabel,trainFeat,cmd); - - cmd = ['']; - if opts.quiet, cmd = [cmd ' -q']; end; - [~,accuTrain,~] = liblinear_predict(trainLabel,trainFeat,model,cmd); - [predTest,accuTest,decTest] = liblinear_predict(testLabel,testFeat,... - model,cmd); - [~,I] = sort(model.Label); - decTest = decTest(:,I); - accuTest = accuTest(1)/100; - accuTrain = accuTrain(1)/100; - - if opts.multiview && strcmp(opts.method,'avgsvmscore'), - testLabel = testLabel(1:nDescPerShape:end); - nClasses = length(model.Label); - decTest = reshape(mean(reshape(decTest, ... - [nDescPerShape nTestShapes*nClasses]),1),[nTestShapes nClasses]); - [~,predTest] = max(decTest,[],2); - accuTest = sum(predTest==testLabel)/length(predTest); - end - - save(opts.predPath,'bestc','accuTrain','predTest','accuTest','decTest',... - 'model','opts'); -end - -% ------------------------------------------------------------------------- -% write & output results -% ------------------------------------------------------------------------- - -% confusion matrix -plot_confusionmat(testLabel, predTest, imdb.meta.classes, ... - opts.confusionPath, [imdb.imageDir ' : ' feat.modelName]); - -STR_01 = {'false', 'true'}; -fprintf('Evaluation finished! \n'); -fprintf('\tc: %g (cv=%d)\n', bestc, opts.cv); -fprintf('\tdataset: %s\n', imdb.imageDir); -fprintf('\tmodel: %s\n',feat.modelName); -fprintf('\tlayer: %s\n',feat.layerName); -fprintf('\tmultiview: %s', STR_01{(opts.multiview~=0)+1}); -if opts.multiview, fprintf(' (%s)', opts.method); end; fprintf('\n'); -fprintf('\taccuracy (train): %g%%\n',accuTrain*100); -fprintf('\taccuracy (test): %g%%\n',accuTest*100); - -fid = fopen(opts.logPath,'a+'); -fprintf(fid, '(%s) -- Classification\n', datestr(now)); -fprintf(fid, '\tc: %g (cv=%d)\n', bestc, opts.cv); -fprintf(fid, '\tdataset: %s\n', imdb.imageDir); -fprintf(fid, '\tmodel: %s\n',feat.modelName); -fprintf(fid, '\tlayer: %s\n',feat.layerName); -fprintf(fid, '\tmultiview: %s', STR_01{(opts.multiview~=0)+1}); -if opts.multiview, fprintf(fid,' (%s)', opts.method); end; fprintf(fid,'\n'); -fprintf(fid, '\taccuracy (train): %g%%\n',accuTrain*100); -fprintf(fid, '\taccuracy (test): %g%%\n',accuTest*100); -fclose(fid); diff --git a/imdb_compute_cnn_features.m b/imdb_compute_cnn_features.m index 1c1c09a..9b8c560 100644 --- a/imdb_compute_cnn_features.m +++ b/imdb_compute_cnn_features.m @@ -222,7 +222,7 @@ end end - feat = get_cnn_activations( im, net, subWins, layers, ... + feat = cnn_shape_get_activations( im, net, layers, subWins, ... 'gpuMode', opts.gpuMode); parsave(fullfile(cacheDir, [num2str(i) '.mat']),feat); diff --git a/imdb_render_views.m b/imdb_render_views.m deleted file mode 100644 index 79652d1..0000000 --- a/imdb_render_views.m +++ /dev/null @@ -1,58 +0,0 @@ -function imdb_render_views( imdb, saveDir, varargin ) -%IMDB_RENDER_VIEWS render 3d shapes in database -% imdb:: -% structure containing info about all 3d shape objects -% saveDir:: -% place to save rendered images -% `az`:: [0:30:330] -% horizontal viewing angles -% `el`:: 30 -% vertical elevation -% `colorMode`:: 'gray' -% color mode of output images (only 'gray' is supported now) -% `outputSize`:: 224 -% output image size (both dimensions) -% `minMargin`:: 0.1 -% minimun margin ratio in output images -% `maxArea`:: 0.3 -% maximun area ratio in output images -% `figureStartIdx`:: floor(rand()*1e8) -% used to avoid conflict - -opts.az = [0:30:330]; -opts.el = 30; -opts.imageExt = '.jpg'; -opts.colorMode = 'gray'; -opts.outputSize = 224; -opts.minMargin = 0.1; -opts.maxArea = 0.3; -opts.figureStartIdx = floor(rand()*1e8); -opts = vl_argparse(opts,varargin); -renderOpts = rmfield(opts,'figureStartIdx'); - -%{ -poolObj = gcp('nocreate'); -if isempty(poolObj), - poolSize = 1; -else - poolSize = poolObj.NumWorkers; -end -parfor (i=1:numel(imdb.images.name),poolSize), -%} -for i=1:numel(imdb.images.name), - shapePath = fullfile(imdb.imageDir,imdb.images.name{i}); - fprintf('%d %s\n',i,shapePath); - fh = figure(i+opts.figureStartIdx-1); - ims = render_views(shapePath,'figHandle',fh,renderOpts); - [pathstr,namestr,extstr] = fileparts(imdb.images.name{i}); - savePathPrefix = fullfile(saveDir,pathstr); - vl_xmkdir(savePathPrefix); - for j = 1:numel(ims), - savePath = fullfile(savePathPrefix,sprintf('%s_%03d%s',namestr,j,opts.imageExt)); - imwrite(ims{j},savePath); - end - close(fh); -end - -end - diff --git a/retrieve_shapes_cnn.m b/retrieve_shapes_cnn.m deleted file mode 100644 index 4d77616..0000000 --- a/retrieve_shapes_cnn.m +++ /dev/null @@ -1,419 +0,0 @@ -function [ results,info ] = retrieve_shapes_cnn( shape, feat, varargin ) -%RETRIEVE_SHAPES_CNN Retrieve 3d shapes using CNN features -% -% shape:: -% Can be either -% cell array containing projected images or paths to them -% OR -% [] (evaluation within dataset) -% feat:: -% structure containing features from reference images and meta data -% .x -% .id -% .imdb -% .modelName -% .layerName -% .pcaMean -% .pcaCoeff -% .powerTrans -% `method`:: 'avgdesc' -% other choices: 'maxdesc','avgdist','mindist','avgmindist','minavgdist' -% `net`::[] -% preloaded cnn model (required when feat.modelName is not available) -% `gpuMode`:: false -% set to true to compute on GPU -% `numWorkers`:: 1 -% number of workers used to compute pairwise -% `nTop`:: Inf -% number of images in results -% `querySets`:: {'test'} -% set of query shapes (used only when shape==[]) -% `refSets`:: {'test'} -% set of reference images -% `metric`:: 'L2' -% other choices: 'LINF', 'L1', 'L0', 'CHI2', 'HELL' -% `multiview`:: true -% set to false to evaluate on single views of each instance -% `logPath:: [] -% place to save log information -% -% Hang Su - -% default options -opts.method = 'avgdesc'; -opts.net = []; -opts.gpuMode = false; -opts.numWorkers = 1; -opts.nTop = Inf; -opts.querySets = {'test'}; -opts.refSets = {'test'}; -opts.metric = 'L2'; -opts.multiview = true; -opts.logPath = []; -[opts, varargin] = vl_argparse(opts,varargin); - -if opts.numWorkers>1, - pool = gcp('nocreate'); - if isempty(pool) || pool.NumWorkers1, - parfor q = 1:nQueryShapes, - [r, p, info] = vl_pr(... - (refClass(q,:)==queryClass(q))-0.5, ... % LABELS - -1*qrDists(q,:), ... % SCORES - 'interpolate', false); - recall(q,:) = r; - precision(q,:) = p; - ap(q) = info.ap; - auc(q) = info.auc; - % interpolated - [r, p, info] = vl_pr(... - (refClass(q,:)==queryClass(q))-0.5, ... % LABELS - -1*qrDists(q,:), ... % SCORES - 'interpolate', true); - recall_i(q,:) = r; - precision_i(q,:) = p; - ap_i(q) = info.ap; - auc_i(q) = info.auc; - end - else - for q = 1:nQueryShapes, - [r, p, info] = vl_pr(... - (refClass(q,:)==queryClass(q))-0.5, ... % LABELS - -1*qrDists(q,:), ... % SCORES - 'interpolate', false); - recall(q,:) = r; - precision(q,:) = p; - ap(q) = info.ap; - auc(q) = info.auc; - % interpolated - [r, p, info] = vl_pr(... - (refClass(q,:)==queryClass(q))-0.5, ... % LABELS - -1*qrDists(q,:), ... % SCORES - 'interpolate', true); - recall_i(q,:) = r; - precision_i(q,:) = p; - ap_i(q) = info.ap; - auc_i(q) = info.auc; - end - end - info = []; - info.recall = recall; - info.precision = precision; - info.ap = ap; - info.auc = auc; - info.recall_i = recall_i; - info.precision_i = precision_i; - info.ap_i = ap_i; - info.auc_i = auc_i; - clear results; - results.dists = dists; - results.dists0 = dists0; - [~,I] = sort(dists,2,'ascend'); - results.rankings = refShapeIds(I(:,1:min(opts.nTop,nRefShapes))); - - % output to log - if ~isempty(opts.logPath), - fprintf('Evaluation finished! \n'); - fprintf('\tdataset: %s\n', imdb.imageDir); - fprintf('\tmodel: %s\n',feat.modelName); - fprintf('\tlayer: %s\n',feat.layerName); - fprintf('\tmethod: %s\n', opts.method); - fprintf('\tmAP: %g%%\n',mean(info.ap)*100); - fprintf('\tAUC: %g%%\n',mean(info.auc)*100); - fprintf('\tmAP (interpolated): %g%%\n',mean(info.ap_i)*100); - fprintf('\tAUC (interpolated): %g%%\n',mean(info.auc_i)*100); - - fid = fopen(opts.logPath,'a+'); - fprintf(fid, '(%s) -- Retrieval\n', datestr(now)); - fprintf(fid, '\tdataset: %s\n', imdb.imageDir); - fprintf(fid, '\tmodel: %s\n',feat.modelName); - fprintf(fid, '\tlayer: %s\n',feat.layerName); - fprintf(fid, '\tmethod: %s\n', opts.method); - fprintf(fid, '\tmAP: %g%%\n',mean(info.ap)*100); - fprintf(fid, '\tAUC: %g%%\n',mean(info.auc)*100); - fprintf(fid, '\tmAP (interpolated): %g%%\n',mean(info.ap_i)*100); - fprintf(fid, '\tAUC (interpolated): %g%%\n',mean(info.auc_i)*100); - fclose(fid); - end - -end - -end - -function M = rmDiagOnRow(M) - assert(ismatrix(M)); - [sz1, sz2] = size(M); - if mod(sz1,sz2)==0, - sy = sz1/sz2; - sx = 1; - elseif mod(sz2,sz1)==0, - sy = 1; - sx = sz2/sz1; - elseif sz1==sz2, - u = triu(M,1); - l = tril(M,-1); - M = u(:,2:end) + l(:,1:end-1); - return; - else - error('Wrong matrix size: [%d %d]',sz1, sz2); - end - ix = 1; - M0 = M; - M = zeros(sz1, sz2-sx); - for iy=1:sy:sz1, - M(iy:iy+sy-1,:) = M0(iy:iy+sy-1,[1:ix-1 ix+sx:sz2]); - ix = ix + sx; - end -end diff --git a/shape_compute_descriptor.m b/shape_compute_descriptor.m index 7c36f96..e6b84da 100644 --- a/shape_compute_descriptor.m +++ b/shape_compute_descriptor.m @@ -27,15 +27,11 @@ % 'metric-relu7-v2.mat' % `gpuMode`:: (default) false % set to true to compute on GPU -% `numWorkers`:: (default) 12 -% number of CPU workers, only in use when gpuMode is false -addpath(genpath('utils')); -run dependencies/vlfeat/toolbox/vl_setup.m -run dependencies/matconvnet/matlab/vl_setupnn.m +setup; if nargin<1 || isempty(path_to_shape), - imdbName = 'data/'; + path_to_shape = 'data/'; end % default options @@ -43,7 +39,6 @@ opts.post_process_desriptor_metric = true; opts.metric_model = 'metric-relu7-v1.mat'; opts.gpuMode = false; -opts.numWorkers = 12; opts = vl_argparse(opts,varargin); if exist(opts.cnn_model, 'file') @@ -88,7 +83,11 @@ if numel(viewpoolIdx)>1, error('More than one viewpool layers found!'); end - num_views = cnn.layers{viewpoolIdx}.stride; + if ~isfield(cnn.layers{viewpoolIdx},'vstride'), + num_views = cnn.layers{viewpoolIdx}.stride; % old format + else + num_views = cnn.layers{viewpoolIdx}.vstride; + end fprintf('CNN model is based on %d views. Will process %d views per mesh.\n', num_views, num_views); else error('Computing a descriptor per shape requires a multi-view CNN.'); @@ -122,7 +121,7 @@ else ims = render_views(mesh, 'use_dodecahedron_views', true, 'figHandle', fig); end - outs = get_cnn_activations(ims, cnn, [], {'relu7','prob'}); + outs = cnn_shape_get_activations(ims, cnn, {'relu7','prob'}, [], 'gpuMode', opts.gpuMode); out = outs.relu7(:); if opts.post_process_desriptor_metric out = double(modelDimRedFV.W*out); diff --git a/utils/render_views_of_all_meshes_in_a_folder.m b/utils/render_views_of_all_meshes_in_a_folder.m index 7860ea3..5abc926 100644 --- a/utils/render_views_of_all_meshes_in_a_folder.m +++ b/utils/render_views_of_all_meshes_in_a_folder.m @@ -1,7 +1,7 @@ function render_views_of_all_meshes_in_a_folder(folder, varargin) % calls 'render_views' for every shape found in the given folder -opts.ext = '.png'; % extension of target files +opts.ext = '.jpg'; % extension of target files opts.range = []; % if empty, all found shapes will be rendered, while a range [X:Y] will render shapes in the given range opts.useUprightAssumption = true; % if true, 12 views will be used to render meshes, otherwise 80 views based on a dodecahedron opts = vl_argparse(opts, varargin); @@ -30,7 +30,7 @@ function render_views_of_all_meshes_in_a_folder(folder, varargin) end for ij=1:length(ims) - imwrite( ims{ij}, sprintf('%s_%03d.png', mesh_filenames(fi).name(1:end-4), ij) ); + imwrite( ims{ij}, sprintf('%s_%03d%s', mesh_filenames(fi).name(1:end-4), ij, opts.ext) ); end end close(fig);