like numbers

[SOLVED] Black screen in TeamViewer on Mac Mini

December 9th, 2013 Posted in Cloud, Uncategorized | No Comments »

Share on Facebook
Share on LinkedIn
Post on Twitter


images-1 images icon.reg

 

When pulling out the HDMI cable from a Mac Mini, the TeamViewer ses­sion on another com­puter gets a com­pletely black (or white screen). When insert­ing the HDMI cable again into the Mac Mini, the desk­top is shown prop­erly. This prob­lem was con­firmed by the  TeamViewer sup­port, which is very good by the way. They explained that “you need a mon­i­tor to run TeamViewer on a Mac Mini”. It seemed like waste of energy because the Mac Mini loca­tion is rarely vis­ited. Any­way, with a screen I could work remotely.

15pinVGAmaleOne day I had to change so that the Mac Mini would use a smaller scree. (The larger one would be used for another pur­pose). I had a smaller screen of older model with a 15 pin VGA inter­face.

To make the smaller screen con­nect to the Mac Mini, I went to an elec­tron­ics store to get an “HDMI to VGA (15 pin) adapter”. It turned out that such a cable or adapter does not exist. A  “box con­verter” was avail­able but it was too expen­sive, basi­cally same price as a new mon­i­tor.

Then the guy in the elec­tron­ics stor asked me if I had a thun­der­bolt con­nec­tion on it, that is, f the Mac Mini was from this or last year (2012). Yes, it was, so I had a thun­der­bolt con­nec­tion on the back of the Mac Mini.

2011macmini-connectivity

 

With this, it turned out that with thun­der­bolt there was a lower cost alter­na­tive than buy­ing an HDMI-15pin VGA con­verter box or a new dis­play. I bough a white adapter from Apple but there are other brands that are cheaper.

thunderbolt to VGA adapter

This solved my prob­lem of con­nect­ing a VGA dis­play to the Mac Mini and at the same time, it also solved the TeamViewer black (or white) screen prob­lems. Once the VGA screen was set up  with the thun­der­bolt-VGA adapter, the dis­play could even be turned of and TeamViewer would still work, unlike in the case when using HDMI.

Conclusion

Using a Thun­der­bolt-VGA adapter solved the prob­lem of using TeamViewer with a Mac Mini not con­nected to a dis­play in the HDMI port. No more black screen or white screen in the TeamViewer win­dow. And no need to leave the mon­i­tor on any­more. Great, isn’t it?

[SOLVED] FileSystemWatcher problem

September 18th, 2013 Posted in Cloud, Coding | No Comments »

Share on Facebook
Share on LinkedIn
Post on Twitter


FileSystemWatcher problemThe FileSys­temWatcher .NET class lis­tens to the file sys­tem and it is catch­ing any changes and noti­fi­ca­tions in it. When direc­to­ries, or files in direc­to­ries, are changed, events are raised.

When a file is cre­ated, the FileSys­temWatcher class will nor­mally be noti­fied twice. How­ever, up to 4 or more “events” can be fired depend­ing on which soft­ware is used to save the file. This can be dif­fi­cult to han­dle because the fired events caused by the same file will run in par­al­lel. For exam­ple, if the events started are mod­i­fy­ing the file cre­ated, only one will be able to open it. It then often hap­pens that a run-time error occurs in one of the other events. The rea­son is that they are try­ing to do exactly the same thing with the same file. Again. Some oper­a­tions just can’t be done more than once, like delet­ing a file.

Previous FileSystemWatcher solutions

There are plenty of post­ings on the Inter­net regard­ing the prob­lems with FileSys­temWatcher. The dou­ble or mul­ti­ple event prob­lem from file cre­ation is an issue. Actu­ally, I think it got worse with faster com­put­ers. They can han­dle events more and more in a truly par­al­lel fash­ion using mul­ti­ple proces­sors. But, even after try­ing all kinds of FileSys­temWatcher tricks and tips, copy­ing and past­ing from the inter­net, I still could not get a good behav­ior on some com­put­ers. Fur­ther­more, I thought that many of the solu­tions sug­gested (using try-catch, using global Boolean vari­ables to “lock out” or using polling instead), were not good.

The new solution

After giv­ing up the copy­ing and past­ing from the Inter­net, I started to think what should be done using my own brains, instead of copy­ing and past­ing.

The idea I came up with was that the par­al­lelism should be removed. Instead, the FileSys­temWatcher events should be put in a queue and be taken care of one by one. Inter­est­ingly, this can be eas­ily done using so called MONITORS. Actu­ally, I had had hap­pily for­got­ten about mon­i­tors, it’s a long time since I stud­ied, but when search­ing for sem­a­phores (which I hap­pened to remem­ber) I found out that mon­i­tors are more sim­ple and will solve the prob­lem very eas­ily.

Dim lock­This As New Object
Pri­vate Sub OnChange(ByVal source As Object, ByVal e As System.IO.FileSystemEventArgs)
Syn­cLock lock­This
If File.Exists(e.FullPath) Then
Get­DataAnd­Delete(e.FullPath)
End If
End Syn­cLock
End Sub

Comments

First we declare a vari­able point­ing to type “Object”. When the event is fired, this object locked as soon as the first event is started. Any other event try­ing to access it (or even try­ing to lock it) will have to wait at that point in the code until the object is unlocked. As soon as it is released, the next event in line will then hold the object and pro­ceed. The other ones in the queue have to wait. In visual basic this is called Syn­cLock which may actu­ally be a bet­ter name than Mon­i­tor.

In this case, the test­ing con­di­tion was just to see if the file existed becuase in “Get­DataAnd­Delete”, the file will be deleted. There­fore, you may need more advanced con­di­tions to see if it is the same file­name com­ing again (within a short period of time).

Hope you like it!

One bluetooth keyboard, one monitor, but TWO macs.

July 17th, 2012 Posted in Animation, Uncategorized, Video | No Comments »

Share on Facebook
Share on LinkedIn
Post on Twitter


I use and HDMI TV as mon­i­tor, dif­fer­ent macs on dif­fer­ent HDMI chan­nels and it was really tricky for me to find out how to switch between them using ONE blue­tooth key­board and track­pad. The prob­lem is:

You have to cut the estab­lished blue­tooth key­board and touch­pad con­nec­tion in order to be able to estab­lish a new con­nec­tion.

When mess­ing with this, it seems almost impos­si­ble, a momen­tum 22. You have to cut the con­nec­tion by turn­ing off the Blue­tooth, but then you can do noth­ing. The solu­tion is Apple Remote Desk­top (ARD). Do like this:

  1. Open ARD and con­nect to the com­puter you want to estab­lish a blue­tooth con­nec­tion.
  2. Make sure blue­tooth is enabled on that other com­puter.
  3. Shut down ARD.
  4. Turn OFF Blue­tooth on the com­puter that cur­rently had a blue­tooth con­nec­tion
  5. Switch to the other com­puter and turn on-off the track­pad.
  6. When con­nec­tion is estab­lished, con­nect the key­board as well.

Update: Now that TeamViewer is the best way to run any other com­puter from remote, this post may seem out­dated. How­ever, han­dling one blue­tooth key­board with sev­eral com­put­ers in the same net­work remains, so I decided to keep this post.

 

This works great and I can switch on the TV between the com­put­ers because they are on dif­fer­ent HDMI chan­nels.

[SOLVED] SugarSync on Mac puts files in Trash, “alias” problems

January 31st, 2012 Posted in Cloud, Coding | No Comments »

Share on Facebook
Share on LinkedIn
Post on Twitter


Like many oth­ers, I have started using cloud ser­vices to sync my data across com­put­ers. Although there are many nice alter­na­tives like Drop­box and so on, my choice until now is Sug­ar­Sync. It is very nice and it has com­pletely removed the man­ual copy­ing of files using FTP that I used to do. I really like the way it is designed and it works well.

But I had an prob­lem with Sug­ar­Sync on Mac OS X. The rea­son is that I like to put aliases on the desk­top to the fold­ers I work with often. (Alias cor­re­spond to “short­cut” in Win­dows). It turned out that my aliases on the desk­top were point­ing to fold­ers and files in the Trash! First I didn’t notice it because the trash folder actu­ally looks like any other folder. I noticed it because changes I had done on another com­puter were not updated.

The SugarSync trash problem

Later I under­stood why: when there are newer files in the cloud, Sug­ar­Sync deletes the older files and puts them in the trash. So when click­ing on the alias, I would end up brows­ing files in the Trash! It is like this because aliases in Mac OS X are point­ing in two ways. If you move the file the alias is point­ing to to another folder, the link will still work (unlike short­cuts in Win­dows that are only point­ing in one way). If you move the linked file to the trash, the alias will point to the trash. Same thing when Sug­ar­Sync is delet­ing files. I tried to use sym­bolic links using the ln -s at the com­mand line but it seemed that OS X has changed the mean­ing of ln and sym­bolic links, at least for fold­ers, quite strange. Maybe Drop­box or other cloud ser­vices would be bet­ter, but I think they work in the same way — older files are moved to the trash.

The rem­edy was to use a com­mand line soft­ware pack­age called hardlink. It is easy to install pro­vided you have the gcc com­piler. (If you don’t, it can be down­loaded from the xcode pack­age). The hardlink pack­age can be down­loaded from Sam’s site and it con­tains all the infor­ma­tion you need includ­ing instal­la­tion instruc­tions and how to use the com­mand.

For me, this solved the annoy­ing Sug­ar­Sync ver­sus Trash prob­lems and now I can work the way I like, using aliases on the desk­top to my cur­rent project fold­ers. The only draw­back with this approach that I have noticed until now is that when open­ing the folder with the hardlink, the icons inside it don’t show the green lit­tle dot indi­cat­ing that it’s synced. On the other hand, it can be nice to not see it too because it makes a rather ugly look, so in the end, this can be regarded as a pos­i­tive fea­ture as well.

Note 1: Sug­ar­Sync is not per­fect. It is not sync­ing some­times. On Win­dows machines, it seems like I have to uninstall/resinstall the soft­ware. On Mac it’s enough to exit the soft­ware and restart it.

Note 2: When the hardlink folder is opened it looks just like nor­mal, but it turns out that it does mot really behave like a native folder cre­ated iin a nor­mal way in Mac OS. For exam­ple, is does not update what is dis­played auto­mat­i­cally if files are changed or if time or date stamps are changed. Also, if drag­ging mul­ti­ple files to the hardlink folder, only one of them will be dis­played, but actu­ally, all are there. By mov­ing back and forth to another folder, the con­tents will dis­play cor­rectly. This can be okay for a while, but I will try to make bet­ter hardlinks I the future.

Note 3: I turned out that I never rally liked “hardlink” so I am not using it any­more. I keep the post if some­one is inter­ested. I tried also using the bidfs pack­age, thanks again, Sam for your advices. It took for­ever to install but worked well. Still, in the end, I didn’t like that the folder icons looked like USB disks or net­work dri­ves and also I didn’t like that they couldn’t be renamed so finally I gave up.

Conclusion

Use a sim­ple folder struc­ture instead so it will be eas­ier to access the files. That is what I do now.

The only thing that remains to worry me now is that sud­denly Sug­ar­Sync may be taken down by author­i­ties because other than me are stor­ing ille­gal files, what ever that would be, but that’s another story.

The numerical estimate of matrix rank

January 27th, 2012 Posted in Applied math, Chemometrics, Coding | No Comments »

Share on Facebook
Share on LinkedIn
Post on Twitter


The most com­monly used func­tion to cal­cu­late the matrix rank is prob­a­bly the one in the MATLAB soft­ware pack­age [1]. It is based on the cal­cu­la­tion of sin­gu­lar val­ues of the matrix A, and the num­ber of sin­gu­lar val­ues that are greater than some tol­er­ance defines the rank. The algo­rithm is sim­ple:

r = svd(A);
if nar­gin==1
tol = max(size(A)) * max(s) * eps;
end
r = sum(s > tol);

Math­e­mat­i­cally, the same rank cal­cu­la­tion is used in the soft­ware pack­age Octave, but it com­pletely equiv­a­lent. The func­tion “rank.m” works sur­pris­ingly well but I won­der what the the­o­ret­i­cal foun­da­tion behind it could be. By using SVD, the orig­i­nal data is mod­eled as A = U*S*V’ and if only a r fac­tors are kept, it is becomes A = U(:,1:r) * S(1:r,1:r) * V(:,1:r)’ + E. The sin­gu­lar val­ues of A are equal to the square root of the vari­ance cap­tured by each fac­tors. (Square root of vari­ance is the same is stan­dard devi­a­tion).

If the sin­gu­lar val­ues are scaled so the first one is equal to 1, the con­tri­bu­tion for an addi­tional fac­tor mea­sured as added stan­dard devi­a­tion that is smaller than the machine pre­ci­sion, eps = 2.2e-16 for dou­ble pre­ci­sion, will be used as the cut­off for when fac­tors with insignif­i­cant sin­gu­lar val­ues are added. How­ever, if addi­tional data is added to a matrix, the sin­gu­lar val­ues will in prac­tice always become larger. This is related to the sum of squares of a matrix which is equal to the sum of all the sin­gu­lar val­ues squared. There­fore the cut­off limit should be scaled by some­thing and in the MATLAB rank func­tion, it is scaled with the fac­tor max(size(A)’).

Again, this works well, but it is dif­fi­cult to under­stand the prin­ci­ple behind it. Ques­tions that come to mind are

  • Why are stan­dard devi­a­tions used for the cut­off?
  • Why not use the vari­ance instead which is related to the vari­ance explained?
  • Why is it scaled with max(size(A)’)?
  • Why not look at the root mean square errors (rms) of the resid­u­als instead?
  • Can it be improved in per­for­mance and can it become more intu­itive?

To address these ques­tions, and to cre­ate some­thing that was easy for myself to under­stand, I made a new algo­rithm for cal­cu­lat­ing the rank, called RANKS where the tail­ing let­ter s stands for search (as in rank-search). I don’t know it is bet­ter, and it is not my inten­tion to make the supe­rior or best algo­rithm for rank esti­ma­tion. The pur­pose is just to make some­thing rea­son­able, under­stand­able for myself,

[m,n]=size(X);
s=flipud(svd(X));
csq = cum­sum(s.^2);
max­val = csq(end);
if max­val > 0,
csq_nrm = csq/maxval;
df = max(m,n)*(min(m,n):-1:1)’;
msq = csq_nrm./df;
r = sum(msq > rms_tol^2);
else
r = 0;
end
%for i=1:length(msq),
% fprintf(‘%5g %20e\n’,i,sqrt(msq(i)));
%end
%semilogy(1:min(m,n),sqrt(msq),‘bo-’,[1 min(m,n)],[rms_tol rms_tol],‘k-’); figure(gcf);

The algo­rithm can be sim­pli­fied but the above makes it easy under­stand the idea. The full algo­rithm, mod­i­fied to avoid divi­sions and unnec­es­sary oper­a­tions is fol­low­ing below.

func­tion r = ranks(X,rms_tol);
%RANKS Matrix rank *search*.
% RANKS(X,rms_tol) is the num­ber of lin­earely inde­pen­dent
% row or columns with the root mean square error of
% resid­u­als larger than rms_tol, slightly dif­fer­ent to
% the stan­dard RANK func­tion in MATLAB.
%
% RANKS(X) pro­vides an esti­mate of the num­ber of lin­early
% inde­pen­dent rows or columns of a matrix A using a
% default root means square error rms_tol = 2.2e16 (at
% machine pre­ci­sion).
%
% RANKS is search­ing over r for the SVD model with r fac­tors
% U(:,1:r)*R(1:5,1:r)*V(:,1:r)’
% where the rel­a­tive root mean square of the resid­u­als of
% E = X — U(:,1:r)*R(1:5,1:r)*V(:,1:r)’
% is less than rms_tol.
%
% See also RANK
%
% Mar­tin Ander­s­son, 2012
if nar­gin==1
rms_tol = eps;
end
rms_tol=eps;
[m,n]=size(X);
s=flipud(svd(X));
csq = cum­sum(s.^2);
max­val = csq(end);
df = max(m,n)*(min(m,n):-1:1)’;
r = sum(csq > df.*(maxval*rms_tol^2));

Finally a remark about the dif­fer­ence between the clas­sic RANK algo­rithm imple­mented in MATLAB and the new RANKS algo­rithm pre­sented here and why nobody cared about it. The rea­son is prob­a­bly that this topic is near to being mean­ing­less. Nobody seemed to argue about it because any­way, if the cal­cu­lated the rank is slightly dif­fer­ent between the two algo­rithms, it does not change much about the con­clu­sions about the orig­i­nal data set.
😛

[1] Don­garra, J.J., J.R. Bunch, C.B. Moler, and G.W. Stew­art, LINPACK Users’ Guide, SIAM, Philadel­phia, 1979.

A numerically stable PLS algorithm

December 30th, 2011 Posted in Applied math, Chemometrics, Coding | No Comments »

Share on Facebook
Share on LinkedIn
Post on Twitter


Dur­ing 2011 year, I have tried many dif­fer­ent ways of mak­ing a PLS algo­rithm (PLS1) as sta­ble pos­si­ble. The goal was to make it as numer­i­cally good as the SVD imple­mented in MATLAB. The pseudocode that solved the PLS1 case for one y-varable is shown below and it includes the addi­tion of null vec­tors to enable full mod­els in the same way as the SVD and QR decom­po­si­tion algo­rithms in MATLAB are.

The dif­fi­culty was when the model was get­ting near to the null space and the solu­tion was to allow for the loop­ing to break either dur­ing a right side (load­ing weight vec­tor) com­pu­ta­tion or dur­ing a left side (score vec­tor) com­pu­ta­tion. The result was a PLS algo­rithm that can­not be bet­ter in terms of numer­i­cal accu­racy. You could say that with this algo­rithm we reach the end of the road in terms of numer­i­cal sta­bil­ity. But, of course, there may still be work done to speed it up.

The new PLS algo­rithm has been con­firmed to work well for var­i­ous data sets and because there is no algo­rithm pub­lished yet that is sta­ble down to rank defi­ciency, is should be pub­lished. I have tried with BIT Numer­i­cal Math­e­mat­ics, but they did not under­stand the prob­lems with cur­rently pub­lished algo­rithms.

Numerically good null spaces

November 3rd, 2011 Posted in Applied math, Chemometrics, Coding | No Comments »

Share on Facebook
Share on LinkedIn
Post on Twitter


There are many meth­ods to obtain null vec­tors to a given set of vec­tors. One of the numer­i­cally best meth­ods is imple­mented in MATLAB, based on sin­gu­lar value decom­po­si­tion, SVD. Null vec­tors can be used for many dif­fer­ent things; one of them is to remove prop­er­ties that are ore orthog­o­nal to another, given prop­erty. Such oper­a­tions are used often within chemo­met­rics and one of the algo­rithms that got most atten­tion recently is the OPLS and the O2PLS algo­rithms by Trygg and Wold, but I wanted to use it together with my research on PLS-algo­rithms.

Exam­ple prob­lems:

  • You have a vec­tor w and want to find all vec­tors that are orthog­o­nal to it.
  • You have a matrix W with ortho­nor­mal vec­tors and want to find all vec­tors that are orthog­o­nal to them,

I wanted to have a method that is sim­i­lar to the NULL-func­tion that can be found in MATLAB, mean­ing that I wanted to have all the null vec­tors avail­able. If I would need only one or two more null vec­tors, other meth­ods could have been used. The ones that seemed to be most suit­able were the ones based on House­holder reflec­tions.

The data that I wanted to pad null vec­tors to con­sist of numer­i­cally highly ortho­nor­mal vec­tors. I there­fore thought I could make some­thing faster than MATLAB’s method based on SVD. I tried many ways but one of the most promis­ing meth­ods was a mod­i­fied QR-decom­po­si­tion, using its full mode ver­sion. After get­ting loop­ing right, it turned out that the QR decom­po­si­tion imple­mented in MATLAB was unbeat­able, even after port­ing it to C++ and con­vert­ing it to MATLAB exe­cuta­bles. I there­fore gave up on mak­ing my own mod­i­fied QR-decom­po­si­tion and used the one imple­mented in MATLAB.

Next sur­prise came when I com­pared the exe­cu­tion times and the orthog­o­naly prop­er­ties when using the null space vec­tors obtained from MATLAB’s SVD with the ones from MATLAB’s QR. The cal­cu­la­tion time was very sim­i­lar for the two. The most log­i­cal choise would per­haps then be to use the one that already existed, the one based on SVD in MATLAB. How­ever, because the SVD-algo­rithm is com­pli­cated, I decided to use the QR ver­sion, which would also be much eas­ier to imple­ment in other code, for exam­ple C++.

After this work, I thought I would not reach any fur­ther in the null space and decided to con­tine the work on a new PLS algo­rithm instead.

My null space algo­rithm based on QR

func­tion Z = nul­lQR(A);
%nullA = nullQR(A)
%
% get the null space of A by con­tin­u­ing using QR decom­po­si­tion,
% *** assum­ing that:                                   ***
% ***     m > n                                        ***
% ***     A is already per­fectly orthog­o­nal.           ***
% No check will be done so use with care.
% The I/O is dif­fer­ent to NULL using a trans­posed matrix.
%
%   See also NULL, SVD, ORTH, RANK, RREF.
%
%
% Mar­tin Ander­s­son, 2011-11
[m,n] = size(A);
[Q,R] = qr(A);
Z = Q(:,n+1:m);

Null space from MATLAB’s SVD

func­tion Z = null(A,how)
%NULL   Null space.
%   Z = NULL(A) is an ortho­nor­mal basis for the null space of A obtained
%   from the sin­gu­lar value decom­po­si­tion.  That is,  A*Z has neg­li­gi­ble
%   ele­ments, size(Z,2) is the nul­lity of A, and Z’*Z = I.
%
%   Z = NULL(A,‘r’) is a “ratio­nal” basis for the null space obtained
%   from the reduced row ech­e­lon form.  A*Z is zero, size(Z,2) is an
%   esti­mate for the nul­lity of A, and, if A is a small matrix with
%   inte­ger ele­ments, the ele­ments of R are ratios of small inte­gers.
%
%   The ortho­nor­mal basis is prefer­able numer­i­cally, while the ratio­nal
%   basis may be prefer­able ped­a­gog­i­cally.
%
%   Exam­ple:
%
%       A =
%
%           1     2     3
%           1     2     3
%           1     2     3
%
%       Z = null(A);
%
%       Com­put­ing the 1-norm of the matrix A*Z will be
%       within a small tol­er­ance
%
%       norm(A*Z,1)< 1e-12
%       ans =
%
%          1
%
%       null(A,‘r’) =
%
%          -2    -3
%           1     0
%           0     1
%
%   Class sup­port for input A:
%      float: dou­ble, sin­gle
%
%   See also SVD, ORTH, RANK, RREF.
%   Copy­right 1984–2006 The Math­Works, Inc.
m,n] = size(A);
if (nar­gin > 1) && ise­qual(how,‘r’)
% Ratio­nal basis
[R,pivcol] = rref(A);
r = length(piv­col);
nopiv = 1:n;
nopiv(piv­col) = [];
Z = zeros(n,n-r,class(A));
if n > r
Z(nopiv,:) = eye(n-r,n-r,class(A));
if r > 0
Z(piv­col,:) = -R(1:r,nopiv);
end
end
else
% Ortho­nor­mal basis
[~,S,V] = svd(A,0);
if m > 1, s = diag(S);
elseif m == 1, s = S(1);
else s = 0;
end
tol = max(m,n) * max(s) * eps(class(A));
r = sum(s > tol);
Z = V(:,r+1:n);
end

Tips and tricks for video handling

August 24th, 2011 Posted in Animation, Video | No Comments »

Share on Facebook
Share on LinkedIn
Post on Twitter


In this post­ing, I have col­lected quite many tips and tricks that I use for video edit­ing and for video for­mat con­ver­sion. I used to have lots of “Stick­ies” on my desk­top, but decided to move them away from there, and why not to my blog page.

Make mp4 m4v and ogg for streaming

  1. Export from iMovie as Medium
  2. Con­vert using ffm­pegX to get down the bitrate to 500. This will stream well well enough for most users and it is slightly lower than what money sites are using so it’s save. Con­vert using the H264 codec to get an mp4,

Maybe it does not work to export from ffmpegX。If so, some more pro­gram should be run after to pre­pare for stream­ing. It actu­ally seems SAFER to use MpegStream­clip instead, makes mp4-s that play with iPhone directly. I should find out…

Create OGG Video file from an mp4 file

In ter­mi­nal, type ffmpeg2theora XXX.m4v. This will put an OVG file right next to the mp4 file. Wow!!! Quite messy to install ffmpeg2theora, but pos­si­ble. This is nice to have and it’s nice to pub­lish OGVs because it a FREE for­mat.

Ripping ANY DVD into to Apple TV

  1. Play with DVD player and push TITLE in the counter win­dow to find out which “chap­ter” the movie is actu­ally run­ning on.
  2. Hand­brake — select only the “chap­ter” found with DVD player, usu­ally one of the largest files.
  3. Dou­ble click to  import to iTunes and to Apple TV. Note that if you want to chance the poster, the whole move will be synced again so if pos­si­ble, set it before immur­ing to iTunes.

Until now, ALL DVDs could be ripped, includ­ing Dis­ney. Great for my kids who have destroys lots of DVDs. Now we don’t need them any­more. Just for the notes, one hour equals about one GB.

Another, not as safe way of getting DVDs into Apple TV

  1. Mac the rip­per, saves into VIDEO_TS folder.
  2. Toast the VIDEOT_TS folder to Apple TV, iTunes.
  3. Clean up chap­ters with Sim­plMovieX
  4. Optional: Toast a disk image of the VIDEO_TS folder.0

I have failed with some DVDs using this method.

Con­vert DVD to mp4 for iPod

  1. Use Hand­brake, using the pre­de­fined for­mats.
  2. Use Sim­ple­MovieX if there is a need to add chap­ters

 

Chapter in iMovie

Don’t put ant num­bers on in the text describ­ing the chap­ters. AppleTV will put num­bers there itself.

Compress a DVD from “9 to 5”

There are “dou­ble den­sity” DVDs that can fit up to 9 Gb. Bet­ter way — use Toast. Poor man’s way: com­pres­sion to a 4.7 Gb disc from Ubuntu:

  1. Con­nect the DVD or DVD-image to the VM
  2. Open a ter­mi­nal and run dvd95, skip the view­ing of files
  3. Copy the com­pressed file to the OS X.

 

Import from Sony HD camera: Large or full size?

The video was 10 Gb in the cam­era, good com­pres­sion. When I cov­ered to full size, it became 200 Gb. When I con­verted to large size, it was 30 Gb. I could not notice any dif­fer­ence at all, so after that, I have always used “large”.

 

Setting the menus in iDVD

It is best to use the gray scheme because stills can be pasted there after con­trol cli­cling on the menu picure of the chap­ter. Then an Inspec­tor win­dow will appear that can be used to get a still pic­ture. Nice. After this, other shemes can also be selected and then they will inheirit the cor­rect still instead of mov­ing pic­tures.

iDVD “multiplexer error” when sharing from iMovie.

To avoid this:

  • Make sure that the SCREEN SAVER is off. In “Energy Saver” set the com­puter to be always on. (Tem­porar­ilt).
  • Make sure to save the iDVD project on the startup drive.
  • Save the disk image to the startup drive, for exam­ple to the DESKTOP
  • Don’t put a chap­ter marker at the very begin­ning of you DVD? Don’t!!
  • Don’t mess around too much and it will be OK.

Things I tried that did not help:

  • delet­ing library/caches/com.apple.idvd (folder)
  • delet­ing library/preferences/com.apple.iDVD.plist.
  • Advanced > delete Encoded Assets
  • Advanced > Encode in Back­ground  (was grayed out, so I didn’t need to turn off)

Pro­cess­ing the project in iDVD took 6 hours, so I went to bed. When I woke up I had suc­cess.

Create Flash Video from iMovie or from a WMV file

  • Con­vert high res­o­lu­tion wmv in mpg stream­clip. Use Quick­time,  Apple BMP,  and spec­ify 1440x1080.
  • Import to imovie. WORK!
  • Export from iMovie “Medium” class to make a m4v that is 640x480. Also DV has been work­ing well as a start­ing for­mat.
  • Use ffm­pexX to con­vert to FLV.
  • Tun­ing of bitrate and frame rate can be a good ting. 500 seems to be good num­ber. 640x480/ NTSC29.97 (makes about 4 Mb per minute)
  • Finally, run yandi avail­able for most OSes. This will set the head­ers right in the FLV.

 

Create flash video from iPhoto slideshow

  1.  Export as Large
  2. In QT7, export as DV stream, watch out for 16:9 wich will become a HUGE file with .MOV exten­sion
  3. With FFm­pegX con­vert DV to FLV, tun­ing of bitrate and frame rate can be a good ting. 500 seems to be good num­ber.
  4. Finally, run yandi avail­able for most OSes. This will set the head­ers right in the FLV

A fast Householder bidiagonalization algorithm

August 10th, 2011 Posted in Applied math, Chemometrics, Coding | No Comments »

Share on Facebook
Share on LinkedIn
Post on Twitter


Please note that this is not regard­ing the Lanc­zos or the PLS bidi­ag­o­nal­iza­tion algo­rithm. This is about the House­holder bidi­ag­o­nal­iza­tion algo­rithm. The dif­fer­ence is huge. For exam­ple, there are no y-vari­ables in the House­holder decom­po­si­tion. It is a decom­po­si­tion of X that results in a fac­tor­iza­tion with a bidi­ag­o­nal matrix in the mid­dle, sim­i­lar to sin­gu­lar value decom­po­si­tion (SVD, which has a diag­o­nal matrix in the mid­dle). The House­holder bidi­ag­o­nal­iza­tion decom­po­si­tion has excel­lent numer­i­cal prop­er­ties and that is why I wanted to make a fast algo­rithm and imple­ment it in C fol­lowed by com­pil­ing it so it could be used as a binary file in MATLAB. That said…

MATLAB has built-in func­tions for matrix fac­tor­iza­tions. Exam­ples are SVD, QR and LU decom­po­si­tion, but there was no algo­rithm for the House­holder bidi­ag­o­nal­iza­tion. It is quite straight­for­ward to imple­ment the House­holder bidi­ag­o­nal­iza­tion MATLAB [1], espe­cially if some­one did exactly this before so you can copy it (smile). The imple­men­ta­tion of House­holder fac­tor­iza­tion in MATLAB by Hansen [2] is very good because it cal­cu­lates the out­puts nec­es­sary, noth­ing more, noth­ing less, thus min­i­miz­ing the amount of cal­cu­la­tions to be per­formed. That MATLAB code can be down­loaded from here.

Although this was a very ele­gant imple­men­ta­tion of House­holder fac­tor­iza­tion, it ran very slowly in MATLAB because of its loop-in-loop struc­ture. Fur­ther­more, it was lim­ited to han­dle only tall (por­trait) matri­ces. Even if a sim­ple trans­po­si­tion would make a workaround for this, I thought it would be nice to let the algo­rithm take care of any size of matrix, tall, wide or square — any shape. In this way, it would be eas­ier to use for chemo­met­ric appli­ca­tions where all sizes of matri­ces do appear. Another point that could be of the­o­ret­i­cal inter­est as well as for com­pat­i­bil­ity would be to allow for both full House­holder decom­po­si­tion as well as econ­omy decom­po­si­tion, mak­ing the algo­rithm behave very sim­i­larly to the fac­tor­iza­tions that are deliv­ered with MATLAB, for exam­ple QR, LU and SVD.

The work was done by extend­ing the MAT­LAB-code by Hansen [2] to get the desired behav­ior, while at the same time keep­ing the ele­gant prop­er­ties of the orig­i­nal imple­men­ta­tion by Hansen. An attempt to con­vert the House­holder code using the MATLAB com­piler for embed­ded sys­tems resulted in ter­ri­ble and inef­fi­cient code to my sur­prise. The MATLAB code was there­fore ported to C-code man­u­ally.

Imple­ment­ing House­holder decom­po­si­tion in C is not is rocket sci­ence, and it is prob­a­bly not the most beau­ti­ful cod­ing accord­ing to C-purists, but I think it’s effi­cient. All of this has already been done in the LAPACK imple­men­tions, so it is def­i­nitely also a rein­ven­tion of the wheel, BUT to get an algo­rithm that is good at doing exactly the House­holder bidi­ag­o­nal­iza­tion and noth­ing more and noth­ing less was what I wanted. After 16 days of irreg­u­lar work last year (2010), the algo­rithm was in place. The result­ing C-code is pre­sented below and can be down­loaded from here too as a text file. If you pre­fer just to put pre­com­piled files into MATLAB, you may pre­fer do down­load only this file instead. EDIT: It now con­tains pre­com­piled files for Win­dows 32 and 64 bit oper­at­ing sys­tems as well as 32 and 64 bit oper­at­ing sys­tems for Mac OS, added 2014-03-19.

It may seem strange that I started look­ing into the House­holder bidi­ag­o­nal­iz­tion algo­rithmm. The rea­son is that I have found a new way to make accu­rate PLS-cal­i­bra­tions, espe­cially suit­able for loads of data. This fast House­holder algo­rithms, together with the ones that are already imple­mented in MATLAB like SVD, QR and LU, the will pro­vide new pos­si­bil­i­ties. I think it will be espe­cially use­ful for process data.

Ref­er­ences
1. L. Elden, “Algo­rithms for reg­u­lar­iza­tion of ill-con­di­tioned least-squares prob­lems”, BIT 17 (1977), 134–145.
2. http://www.math.wsu.edu/faculty/tsat/files/matlab/contributed/bidiag.m

/*
BIDIAG Bidi­ag­o­nal­iza­tion of a matrix.
B = bidiag(A);
[U,B,V] = bidiag(A);
[U,B] = bidiag(A);
the m-times-n matrix A:
A = U*B*V’ ,
where B is an upper bidi­ag­o­nal square matrix, and U and
V have orthog­o­nal columns. Econ­omy size matri­ces are obatained by adding a zeros for the sec­ond input argu­ment.

L. Elden, “Algo­rithms for reg­u­lar­iza­tion of ill-
con­di­tioned least-squares prob­lems”, BIT 17 (1977), 134–145.

Revi­sion his­tory:
o For Matalb, m >= n:
Per Chris­t­ian Hansen, UNI-C, 03/11/92.
o For Mat­lab, m >= n and m < n:
Mar­tin Ander­s­son, Foss Japan, 2011-08-11
o C-code com­piled Mat­lab mex-file:
Mar­tin Ander­s­son, Foss Japan, 2011-09-06
o C-code for full/economy size done:
Mar­tin Ander­s­son, Foss Japan, 2011-09-10
*/


#include “stdafx.h”
#include “mex.h”
#include “limits.h”
#include “mexFunction.h”
#include “math.h”

// This is for Win­dows
#pragma comment(lib, “libmx.lib”)
#pragma comment(lib, “libmat.lib”)
#pragma comment(lib, “libmex.lib”)
//#pragma comment(lib, “libmatlb.lib”)

// This is also for Win­dows
BOOL APIENTRY Dll­Main( HANDLE hMod­ule, DWORD ul_reason_for_call, LPVOID lpRe­served)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

void ini­tial­izeU(int m, int n, mxAr­ray *pU);
void ini­tial­izeV(int m, int n, mxAr­ray *pV);
void copy_vector_to_col_in_matrix(int row, int col, mxAr­ray *pA, mxAr­ray *col_pv);
void copy_vector_to_row_in_matrix(int row, int col, mxAr­ray *pA, mxAr­ray *row_pv);
void copy_col_in_matrix_to_vector(int row, int col, mxAr­ray *col_pv, mxAr­ray *pA);
void copy_row_in_matrix_to_vector(int row, int col, mxAr­ray *row_pv, mxAr­ray *pA);
dou­ble norm(int ind­Start, mxAr­ray *pv);

void col_gen_hh(int row, int col, mxAr­ray *pv, dou­ble& x1, dou­ble& beta, mxAr­ray *pA);
void row_gen_hh(int row, int col, mxAr­ray *pv, dou­ble& x1, dou­ble& beta, mxAr­ray *pA);
void col_app_hh(int row, int col, mxAr­ray *pA, dou­ble& beta, mxAr­ray *pv);
void row_app_hh(int row, int col, mxAr­ray *pA, dou­ble& beta, mxAr­ray *pv);

void fliupud_fliplr_Btransp(mxAr­ray *pB);
void fli­plr(int last­Col, mxAr­ray *pA);

void mex­Func­tion(int nlhs, mxAr­ray *plhs[], int nrhs, const mxAr­ray *prhs[])
{
// Check the inputs and the ouputs
bool Econ­o­my­SizeDe­com­po­si­tion = (nlhs == 1);
if(nlhs > 3) mex­Er­rMs­gTxt(“Too many out­put argu­ments.”);
if(nrhs > 2) mex­Er­rMs­gTxt(“Too many input argu­ments.”);
if(nrhs ==2)
if (*mxGetPr(prhs[1]) == 0)
Econ­o­my­SizeDe­com­po­si­tion = true;
else
mex­Er­rMs­gTxt(“Use bidiag(X,0) for econ­omy size decom­po­si­tion.”);

//Create scalars, vec­tors and matri­ces
dou­ble *origA, *A, *B, *betaU, *betaV, *col_v, *row_v;
int m, n, ind;
mxAr­ray *pU, *pB, *pV, *col_pv, *row_pv, *pbe­taU, *pbe­taV, *pA;
origA = mxGetPr(prhs[0]);
int M = mxGetM(prhs[0]);
int N = mxGetN(prhs[0]);
if (M >= N)
{
m = M;
n = N;
if (Econ­o­my­SizeDe­com­po­si­tion)
pA = mxCre­ate­Dou­bleMa­trix(m,n,mxREAL);
else
pA = mxCre­ate­Dou­bleMa­trix(m,m,mxREAL);
A = mxGetPr(pA);
mem­cpy(A, origA, m*n*sizeof(dou­ble));
}
else
{
m = N;
n = M;
if (Econ­o­my­SizeDe­com­po­si­tion)
pA = mxCre­ate­Dou­bleMa­trix(m,n,mxREAL);
else
pA = mxCre­ate­Dou­bleMa­trix(m,m,mxREAL);
A = mxGetPr(pA);
int i = 0;
for (int r = 0; r < m; r++)
{
ind = r;
for (int c = 0; c < n; c++)
{
A[ind]= origA[i++];
ind += m;
}
}
}
int indB =0;
col_pv = mxCre­ate­Dou­bleMa­trix(m,1,mxREAL);
pbe­taU = mxCre­ate­Dou­bleMa­trix(m,1,mxREAL);
if (Econ­o­my­SizeDe­com­po­si­tion)
{
row_pv = mxCre­ate­Dou­bleMa­trix(n,1,mxREAL);
pbe­taV = mxCre­ate­Dou­bleMa­trix(n,1,mxREAL);
pB = mxCre­ate­Dou­bleMa­trix(n,n,mxREAL);
}
else
{
row_pv = mxCre­ate­Dou­bleMa­trix(m,1,mxREAL);
pbe­taV = mxCre­ate­Dou­bleMa­trix(m,1,mxREAL);
pB = mxCre­ate­Dou­bleMa­trix(M,N,mxREAL);
}
B = mxGetPr(pB);
betaU = mxGetPr(pbe­taU);
betaV = mxGetPr(pbe­taV);
col_v = mxGetPr(col_pv);
row_v = mxGetPr(row_pv);

//Initialization done.

//Start the main loop­ing
for (int k = 0; k < n; k++)
{
col_gen_hh(k, k, col_pv, B[indB], betaU[k], pA);
if (nlhs>1) copy_vector_to_col_in_matrix(k, k, pA, col_pv);
col_app_hh(k, k+1, pA, betaU[k], col_pv);
if (k < (n-2))
{
indB += mxGetM(pB);
row_gen_hh(k, k+1, row_pv, B[indB++], betaV[k], pA);
if (nlhs>1) copy_vector_to_row_in_matrix(k, k+1, pA, row_pv);
row_app_hh(k+1, k+1, pA, betaV[k], row_pv);
}
else if (k == (n-2))
{
indB += mxGetM(pB);
B[indB++] = A[(m+1)*(n-1)-1];
}
}
if (((nlhs > 1) && (M >= N)) || ((nlhs == 3) && (M < N)))
{
if (Econ­o­my­SizeDe­com­po­si­tion)
{
pU = mxCre­ate­Dou­bleMa­trix(m,n,mxREAL);
ini­tial­izeU(m, n, pU);
}
else
{
pU = mxCre­ate­Dou­bleMa­trix(m,m,mxREAL);
ini­tial­izeU(m, m, pU);
}

for (int k = n-1; k >= 0 ; k)
{
copy_col_in_matrix_to_vector(k, k, col_pv, pA);
col_app_hh(k, k, pU, betaU[k], col_pv);
}
}
if (((nlhs == 3) && (M >= N)) || ((nlhs > 1) && (M < N)))
{
pV = mxCre­ate­Dou­bleMa­trix(n,n,mxREAL);
ini­tial­izeV(m, n, pV);
for (int k = n-3; k >= 0 ; k)
{
copy_row_in_matrix_to_vector(k, k, row_pv, pA);
col_app_hh(k+1, k, pV, betaV[k], row_pv);
}
}

//Main loop­ing done, assign out­puts and flip matri­ces if M < N.
if (M >= N)
{
if (nlhs <= 1)
plhs[0] = pB;
else
{
plhs[0] = pU;
plhs[1] = pB;
if (nlhs == 3) plhs[2] = pV;
}
}
else
{
fliupud_fliplr_Btransp(pB);
if (nlhs <= 1)
plhs[0] = pB;
else
{
fli­plr(n,pV);
plhs[0] = pV;
plhs[1] = pB;
if (nlhs == 3)
{
fli­plr(n,pU);
plhs[2] = pU;
}
}
}
}

void ini­tial­izeU(int m, int n, mxAr­ray *pU)
{
dou­ble *U;
U = mxGetPr(pU);
int kLim = m*n;
for (int k = 0; k < kLim; k += (m+1))
U[k] = 1.0;
}

void ini­tial­izeV(int m, int n, mxAr­ray *pV)
{
dou­ble *V;
V = mxGetPr(pV);
int kLim = n*n;
for (int k = 0; k < kLim; k += (n+1))
V[k] = 1.0;
}

dou­ble norm(int ind­Start, mxAr­ray *pv)
{
int i,m;
dou­ble *v;
dou­ble aux = 0.0;
m = mxGetM(pv);
v = mxGetPr(pv);
for (i = ind­Start; i < m; i++)
aux += v[i]*v[i];
return sqrt(aux);
}

void copy_vector_to_col_in_matrix(int from­Row, int col, mxAr­ray *pA, mxAr­ray *col_pv)
{
dou­ble *v, *A;
v = mxGetPr(col_pv);
A = mxGetPr(pA);
int m = mxGetM(pA);
int indA = from­Row+col*m;
for (int i = from­Row; i < m; i++)
A[indA++] = v[i];
}

void copy_vector_to_row_in_matrix(int row, int from­Col, mxAr­ray *pA, mxAr­ray *row_pv)
{
dou­ble *v, *A;
v = mxGetPr(row_pv);
A = mxGetPr(pA);
int m = mxGetM(pA);
int n = mxGetN(pA);
int indv = from­Col;
int indAs­tart = row + from­Col*m;
int indAlimit = m*n;
for (int indA = indAs­tart; indA < indAlimit; indA += m)
A[indA] = v[indv++];
}

void copy_col_in_matrix_to_vector(int from­Row, int col, mxAr­ray *col_pv, mxAr­ray *pA)
{
dou­ble *v, *A;
v = mxGetPr(col_pv);
A = mxGetPr(pA);
int m = mxGetM(pA);
int indA = from­Row+col*m;
for (int i = from­Row; i < m; i++)
v[i] = A[indA++];
}

void copy_row_in_matrix_to_vector(int row, int from­Col, mxAr­ray *row_pv, mxAr­ray *pA)
{
dou­ble *v, *A;
v = mxGetPr(row_pv);
A = mxGetPr(pA);
int m = mxGetM(pA);
int n = mxGetN(pA);
int indv = from­Col;
int indAs­tart = row + from­Col*m;
int indAlimit = m*n;
for (int indA = indAs­tart; indA < indAlimit; indA += m)
v[indv++] = A[indA];
}

void col_gen_hh(int row, int col, mxAr­ray *col_pv, dou­ble& x1, dou­ble& beta, mxAr­ray *pA)
{
dou­ble *v, *A;
dou­ble alpha;
v = mxGetPr(col_pv);
A = mxGetPr(pA);
int m = mxGetM(pA);
int n = mxGetN(pA);

int r = row;
int indAs­tart = row + col*m;
int indAlimit = (col+1)*m;
for (int indA = indAs­tart; indA < indAlimit; indA++)
v[r++] = A[indA];

alpha = norm(row,col_pv);
if (alpha == 0) beta = 0.0;
else beta = 1/(alpha*(alpha + abs(v[row])));

if (v[row] < 0) alpha = -alpha;
v[row] = v[row] + alpha;
x1 = -alpha;
}

void row_gen_hh(int row, int col, mxAr­ray *row_pv, dou­ble& x1, dou­ble& beta, mxAr­ray *pA)
{
dou­ble *v, *A;
dou­ble alpha;
v = mxGetPr(row_pv);
A = mxGetPr(pA);
int n = mxGetN(pA);
int m = mxGetM(pA);

int indv = col;
int indAs­tart = row + col*m;
int indAlimit = m*n;
for (int indA = indAs­tart; indA < indAlimit; indA += m)
v[indv++] = A[indA];

alpha = norm(col, row_pv);
if (alpha == 0) beta = 0.0;
else beta = 1/(alpha*(alpha + abs(v[col])));

if (v[col] < 0) alpha = -alpha;
v[col] = v[col] + alpha;
x1 = -alpha;
}

void col_app_hh(int row, int col, mxAr­ray *pA, dou­ble& beta, mxAr­ray *pv)
{
int r, c, m, n, indA;
dou­ble *A, *v, d;

m = mxGetM(pA);
n = mxGetN(pA);
A = mxGetPr(pA);
v = mxGetPr(pv);

for (c = col; c < n; c++)
{
d = 0.0;
indA = row + c*m;
for (r = row; r < m; r++)
d = d + v[r]*A[indA++];
indA = row + c*m;
for (r = row; r < m; r++)
{
A[indA] = A[indA] - beta*v[r]*d;
indA++;
}
}
}

void row_app_hh(int row, int col, mxAr­ray *pA, dou­ble& beta, mxAr­ray *pv)
{
int r, c, m, n;
dou­ble *A, *v, d;
int indAs­tart;

m = mxGetM(pA);
n = mxGetN(pA);
int indAlimit = m*n;
A = mxGetPr(pA);
v = mxGetPr(pv);

for (r = row; r < m; r++)
{
d = 0.0;
c = col;
indAs­tart = r + col*m;
for (int indA = indAs­tart; indA < indAlimit; indA += m)
d = d + A[indA]*v[c++];
c = col;
indAs­tart = r + col*m;
for (int indA = indAs­tart; indA < indAlimit; indA += m)
A[indA] = A[indA] - beta*v[c++]*d;
}
}

void fliupud_fliplr_Btransp(mxAr­ray *pB)
{
int m = mxGetM(pB);
int n = mxGetN(pB);
dou­ble *B = mxGetPr(pB);
dou­ble tmp;
int halfway = min(m,n)/2;
int indD1 = 0;
int indD2;
if (m > n)
indD2 = (m + 1)*(n - 1);
else
indD2 = m*m-1;
for (int i = 0; i < halfway; i++)
{
tmp = B[indD1];
B[indD1] = B[indD2];
B[indD2] = tmp;
if (i < halfway)
{
indD1 += m;
indD2;
tmp = B[indD1];
B[indD1] = B[indD2];
B[indD2] = tmp;
indD1++;
indD2 -= m;
}
}
}

void fli­plr(int last­Col, mxAr­ray *pA)
{
int m = mxGetM(pA);
int n = mxGetN(pA);
dou­ble *A = mxGetPr(pA);
dou­ble tmp;
int halfway = m*(last­Col/2);
int rightInd = m*(last­Col-1);
for (int i = 0; i < halfway; i++)
{
tmp = A[i];
A[i] = A[rightInd];
A[rightInd] = tmp;
rightInd ++;
if ((rightInd % m) == 0) rightInd = rightInd - 2*m;
}
}

PLS regression — NEW animation

July 21st, 2011 Posted in Animation, Applied math, Chemometrics, Video | No Comments »

Share on Facebook
Share on LinkedIn
Post on Twitter


Dur­ing the con­fer­ence in Cape Town on Near Infrared Spec­troscopy, NIR2011, I had a keynote in the chemo­met­rics ses­sion. I pre­sented a com­par­i­son of three dif­fer­ent meth­ods for mak­ing cal­i­bra­tions includ­ing the LOCAL con­cept by Shenk and West­er­haus, and fol­lowng that, I pre­sented another com­par­i­son, on dif­fer­ent PLS algo­rithms. As a link between these two sec­tions, I wanted to decribe how the dif­fer­ences between PLS, MPLS and other meth­ods relate so I made an ani­ma­tion. It was well received and I gave copies of to researchers and teacher so that they can use it in their pre­sen­ta­tions too. Today, I added it to my home­page to the page of videos so now every­one can down­load it, see http://www.sondette.com/public/Video-PLS-animation.html.