Saturday, July 18, 2015

I guess it's only appropriate

As I tried to explain last time, I consider this habit to be a unilateral affair.  Until now, I'd no need to create a decoder.  Out of curiosity, I decided to go ahead and make one; it was easy enough.  To think all this time I hadn't known of base2dec().  I've been doing shit the hard way too many times.  Oh well.  Learning things is good.

%% text from unicode pictogram (arbitrary-base encoding)
clc; clear all; 
format compact

instring='▄▆▂▆ ▄█▄▄ ▄▆█▂ ▄▆█▂ ▄█▂█ ▄▆▆▂ ▄▆▆▄ ▄█▄▂';
charmap='▂▄▆█'; % length(charmap)=b
space=' '; % thin space

b=length(charmap);
for n=0:1:b-1;
    instring(findstr(instring,charmap(n+1)))=num2str(n);
end

instring(findstr(instring,space))=' ';
instring=str2num(instring); % simplifies indexing
outstring='';

for n=1:1:length(instring);
    outstring=[outstring char(base2dec(num2str(instring(n)),b))];
end

outstring


It works well enough.  I suppose I could make it automagically find a minimal character set from the input pictogram string, but it wouldn't have any way of guessing the likely order.  Granted, even a human would have to guess if the charmap used in encoding weren't conspicuously ordered. 

Thursday, July 16, 2015

▒░▒▓▓ ▒░▒▓▒ ▒▒░▓░ ▒░▓▓▓

▄▂▂▄ ▄█▂█ ▂▆▂▂ ▄▆█▄ ▄█▄▄ ▄▆▂█ ▄▆▆▂ ▂▆▂▂ ▄▆▂▄ ▄█▂█ ▂▆▂▂ ▄▂▆▄ ▂▆▂▂ ▄▆█▄ ▄▆▂▄ ▄█▆▄ ▂▆▂▂ ▄▆▆▂ ▄▆▂▄ ▄█▄▆ ▄▆▄▄ ▂▆▂▂ ▄▆▄▄ ▄▆█▆ ▄▆▆▆ ▄▆██ ▄█▆▄ ▄▆▄▄ ▄▆▄▂ ▂▆▂▂ ▄▆▂█ ▄▆▄▄ ▄█▂▆ ▄█▄▂ ▄▆▂▄ ▄▆▆▄ ▄▆█▆ ▂▆▂▂ ▄█▄▂ ▄▆▆▂ ▄▆▆▄ ▄▆█▆ ▄▆▄█ ▄█▂█ ▂▆▂▂ ▄▆▆▄ ▄▆█▆ ▂▆▂▂ ▄█▂▂ ▄▆▂▄ ▄█▂▆ ▄█▄▂ ▄▆▆▄ ▄▆▂█ ▄█▄▄ ▄▆█▂ ▄▆▂▄ ▄█▂▆ ▂▆█▂ ▂▆▂▂ ▄▂▆▄ ▂▆▄█ ▄█▄▆ ▄▆▄▄ ▂▆▂▂ ▄▆▆▂ ▄▆▂▄ ▄▆▄▂ ▂▆▂▂ ▄▆▂▄ ▂▆▂▂ ▄▆▄█ ▄█▂▆ ▄▆██ ▄█▄█ ▄▆▆▄ ▄▆█▆ ▄▆▄█ ▂▆▂▂ ▄▆▄▂ ▄▆▆▄ ▄▆▄▆ ▄▆▄▆ ▄▆▆▄ ▄▆▂█ ▄█▄▄ ▄▆█▂ ▄█▄▂ ▄█▆▄ ▂▆▂▂ ▄▆▄▄ ▄▆█▆ ▄▆▆▆ ▄▆██ ▄█▆▄ ▄▆▆▄ ▄▆█▆ ▄▆▄█ ▂▆▂▂ ▄▆▂▄ ▄▆█▆ ▄█▆▄ ▄█▄▂ ▄▆▆▂ ▄▆▆▄ ▄▆█▆ ▄▆▄█ ▂▆▂▂ ▄▆▆▄ ▄▆█▆ ▂▆▂▂ ▄▆▄█ ▄▆▄▄ ▄▆█▆ ▄▆▄▄ ▄█▂▆ ▄▆▂▄ ▄▆█▂ ▂▆█▆ ▂▆▂▂ ▂▆▂▂ ▄▂▆▄ ▄█▄▂ ▂▆▂▂ ▄▆▂█ ▄█▂▆ ▄▆▄▄ ▄▆▄▄ ▄█▂▂ ▄█▂█ ▂▆▂▂ ▄▆▆▄ ▄▆█▆ ▄█▄▂ ▄▆██ ▂▆▂▂ ▄▆▄▄ ▄█▄▆ ▄▆▄▄ ▄█▂▆ ▄█▆▄ ▄█▄▂ ▄▆▆▂ ▄▆▆▄ ▄▆█▆ ▄▆▄█ ▂▆█▂ ▂▆▂▂ ▄▆▄▆ ▄█▂▆ ▄▆██ ▄▆█▄ ▂▆▂▂ ▄▆█▄ ▄█▆▄ ▂▆▂▂ ▄▆▄▂ ▄█▂▆ ▄▆▄▄ ▄▆▂▄ ▄▆█▄ ▄█▂█ ▂▆▂▂ ▄█▄▂ ▄▆██ ▂▆▂▂ ▄▆█▄ ▄▆▄▄ ▄▆█▄ ▄▆██ ▄█▂▆ ▄█▆▄ ▂▆█▆ ▂▆▂▂ ▂▆▂▂ ▄▄▄█ ▄▆▆▄ ▄█▄▂ ▄▆▆▂ ▂▆▂▂ ▄█▄█ ▄▆▂▄ ▄▆█▆ ▄▆▆▄ ▄▆█▆ ▄▆▄█ ▂▆▂▂ ▄▆▂█ ▄▆██ ▄▆█▆ ▄▆▄▆ ▄▆▆▄ ▄▆▄▂ ▄▆▄▄ ▄▆█▆ ▄▆▂█ ▄▆▄▄ ▂▆█▂ ▂▆▂▂ ▄▂▆▄ ▂▆▂▂ ▄▆▄▆ ▄▆▄▄ ▄▆▄▄ ▄▆█▂ ▂▆▂▂ ▄▆▂▄ ▄█▂█ ▂▆▂▂ ▄█▄▂ ▄▆▆▂ ▄▆██ ▄█▄▄ ▄▆▄█ ▄▆▆▂ ▂▆▂▂ ▄▂▆▄ ▂▆▂▂ ▄▆▆▂ ▄▆▂▄ ▄█▄▆ ▄▆▄▄ ▄▆█▆ ▂▆▄█ ▄█▄▂ ▂▆▂▂ ▄█▄▂ ▄▆▆▂ ▄▆▄▄ ▂▆▂▂ ▄▆▂▆ ▄█▂▆ ▄▆▄▄ ▄▆▂▄ ▄█▄▂ ▄▆▆▂ ▂▆▂▂ ▄█▄▂ ▄▆██ ▂▆▂▂ ▄▆▄▄ ▄█▄▆ ▄▆▄▄ ▄▆█▆ ▂▆▂▂ ▄▆▄▄ ▄█▆▂ ▄█▂▂ ▄█▂▆ ▄▆▄▄ ▄█▂█ ▄█▂█ ▂▆▂▂ ▄█▄▂ ▄▆▆▂ ▄▆▂▄ ▄█▄▂ ▂▆▂▂ ▄▂▆▄ ▂▆▂▂ ▄▆▂█ ▄▆▂▄ ▄▆█▆ ▂▆▄█ ▄█▄▂ ▂▆▂▂ ▄▆▂█ ▄▆▂▄ ▄█▂▆ ▄▆▄▄ ▂▆▂▂ ▄▆▂▆ ▄▆▄▄ ▄█▆▄ ▄▆██ ▄▆█▆ ▄▆▄▂ ▂▆▂▂ ▄█▄▂ ▄▆▆▂ ▄▆▄▄ ▂▆▂▂ ▄█▂▆ ▄▆▄▄ ▄▆█▄ ▄▆▂▄ ▄▆▆▄ ▄▆█▆ ▄▆▆▄ ▄▆█▆ ▄▆▄█ ▂▆▂▂ ▄▆▄▂ ▄▆▆▄ ▄█▂█ ▄▆▄█ ▄█▄▄ ▄█▂█ ▄█▄▂ ▂▆▂▂ ▄▆██ ▄█▂▆ ▂▆▂▂ ▄█▂▆ ▄▆▄▄ ▄▆▄█ ▄█▂▆ ▄▆▄▄ ▄█▄▂ ▂▆█▆ 

▄▄▂█ ▄█▄▂ ▄▆▆▄ ▄▆█▂ ▄▆█▂ ▂▆█▂ ▂▆▂▂ ▄█▄▂ ▄▆▆▂ ▄▆▆▄ ▄█▂█ ▂▆▂▂ ▄▆▂▄ ▄▆█▆ ▄▆▆▄ ▄▆█▄ ▄▆▂▄ ▄▆█▂ ▂▆▂▂ ▄▆█▆ ▄▆▄▄ ▄▆▄▄ ▄▆▄▂ ▂▆▂▂ ▄█▄▂ ▄▆██ ▂▆▂▂ ▄▆▂█ ▄▆██ ▄▆█▄ ▄▆█▄ ▄█▄▄ ▄▆█▆ ▄▆▆▄ ▄▆▂█ ▄▆▂▄ ▄█▄▂ ▄▆▄▄ ▂▆▂▂ ▄█▄█ ▄▆▄▄ ▄▆█▂ ▄▆█▂ ▄█▂█ ▂▆▂▂ ▄▆▄▆ ▄█▂▆ ▄▆██ ▄▆█▄ ▂▆▂▂ ▄█▄█ ▄▆▆▄ ▄█▄▂ ▄▆▆▂ ▄▆▆▄ ▄▆█▆ ▂▆█▆ ▂▆▂▂ ▂▆▂▂ ▄▂▂▄ ▄█▂█ ▂▆▂▂ ▄▆▂▄ ▂▆▂▂ ▄▆█▄ ▄▆▂▄ ▄█▂█ ▄█▄▂ ▄█▄▄ ▄█▂▆ ▄▆▂▆ ▄▆▂▄ ▄█▄▂ ▄▆▆▄ ▄▆██ ▄▆█▆ ▂▆▂▂ ▄▆██ ▄▆▄▆ ▂▆▂▂ ▄▆█▆ ▄▆▄▄ ▄▆▂█ ▄▆▄▄ ▄█▂█ ▄█▂█ ▄▆▆▄ ▄█▄▂ ▄█▆▄ ▂▆█▂ ▂▆▂▂ ▄▂▆▄ ▂▆▂▂ ▄▆▂█ ▄▆██ ▄▆█▆ ▄█▄▂ ▄▆▆▄ ▄▆█▆ ▄█▄▄ ▄▆▄▄ ▂▆▂▂ ▄█▄▂ ▄▆██ ▂▆▂▂ ▄▆▄█ ▄▆██ ▂▆▂▂ ▄█▄▂ ▄▆▆▂ ▄█▂▆ ▄▆██ ▄█▄▄ ▄▆▄█ ▄▆▆▂ ▂▆▂▂ ▄█▄▂ ▄▆▆▂ ▄▆▄▄ ▂▆▂▂ ▄▆█▄ ▄▆██ ▄█▄▂ ▄▆▆▄ ▄▆██ ▄▆█▆ ▄█▂█ ▂▆█▆ ▂▆▂▂ ▂▆▂▂ ▄▂▆▄ ▄▆▄▆ ▂▆▂▂ ▄█▄▂ ▄▆▆▂ ▄▆▄▄ ▂▆▂▂ ▄▆▄▂ ▄█▂▆ ▄▆▆▄ ▄█▄▆ ▄▆▆▄ ▄▆█▆ ▄▆▄█ ▂▆▂▂ ▄▆▂█ ▄▆██ ▄▆█▆ ▄▆▄▆ ▄▆█▂ ▄▆▆▄ ▄▆▂█ ▄█▄▂ ▂▆▂▂ ▄█▂▆ ▄▆▄▄ ▄▆█▄ ▄▆▂▄ ▄▆▆▄ ▄▆█▆ ▄█▂█ ▂▆▂▂ ▄▆▆▄ ▄▆█▆ ▄█▄▂ ▄▆▄▄ ▄█▂▆ ▄▆█▆ ▄▆▂▄ ▄▆█▂ ▂▆█▂ ▂▆▂▂ ▄▆▂▄ ▄▆█▆ ▄▆▄▂ ▂▆▂▂ ▄▆█▆ ▄▆██ ▂▆▂▂ ▄▆▄▄ ▄█▆▂ ▄█▄▂ ▄▆▄▄ ▄█▂▆ ▄▆█▆ ▄▆▂▄ ▄▆█▂ ▂▆▂▂ ▄▆▄▄ ▄█▆▂ ▄█▂▂ ▄▆▄▄ ▄▆▂█ ▄█▄▂ ▄▆▂▄ ▄█▄▂ ▄▆▆▄ ▄▆██ ▄▆█▆ ▄█▂█ ▂▆▂▂ ▄▆▄▄ ▄█▆▂ ▄▆▆▄ ▄█▂█ ▄█▄▂ ▂▆█▂ ▂▆▂▂ ▄▆▂▄ ▄▆█▂ ▄▆█▂ ▂▆▂▂ ▄█▂▂ ▄█▂▆ ▄▆▄▄ ▄█▄▂ ▄▆▄▄ ▄▆█▆ ▄█▂█ ▄▆▄▄ ▂▆▂▂ ▄▆▂▄ ▄█▂█ ▂▆▂▂ ▄▆▂█ ▄▆██ ▄▆█▄ ▄▆█▄ ▄█▄▄ ▄▆█▆ ▄▆▆▄ ▄▆▂█ ▄▆▂▄ ▄█▄▂ ▄▆▆▄ ▄▆██ ▄▆█▆ ▂▆▂▂ ▄▆█▄ ▄▆▂▄ ▄█▆▄ ▂▆▂▂ ▄▆▂▄ ▄█▂█ ▂▆▂▂ ▄█▄█ ▄▆▄▄ ▄▆█▂ ▄▆█▂ ▂▆▂▂ ▄▆▂▆ ▄▆▄▄ ▂▆▂▂ ▄▆▂▄ ▄▆▂▆ ▄▆▂▄ ▄▆█▆ ▄▆▄▂ ▄▆██ ▄▆█▆ ▄▆▄▄ ▄▆▄▂ ▂▆█▆ 

▄▂▂▄ ▄▆█▆ ▄▆▄▂ ▂▆▂▂ ▄█▂█ ▄▆██ ▂▆▂▂ ▄▆▆▄ ▄█▄▂ ▂▆▂▂ ▄▆▄█ ▄▆██ ▄▆▄▄ ▄█▂█ ▂▆▂▂ ▄▆▂▄ ▄█▄▂ ▂▆▂▂ ▄█▄▂ ▄▆▆▄ ▄▆█▄ ▄▆▄▄ ▄█▂█ ▂█▆█ ▂▆▂▂ ▂▆▂▂ ▄▆▂▄ ▄█▂█ ▂▆▂▂ ▄▆▄▄ ▄▆▂▄ ▄█▂█ ▄▆▆▄ ▄▆█▂ ▄█▆▄ ▂▆▂▂ ▄▆▂▄ ▄█▂█ ▂▆▂▂ ▄▆██ ▄▆█▆ ▄▆▄▄ ▂▆▂▂ ▄▆▂█ ▄▆▂▄ ▄▆█▆ ▂▆▂▂ ▄▆▆▂ ▄▆██ ▄▆█▂ ▄▆▄▂ ▂▆▂▂ ▄▆▄▂ ▄▆▆▄ ▄▆▂▄ ▄▆█▂ ▄▆██ ▄▆▄█ ▄█▄▄ ▄▆▄▄ ▂▆▂▂ ▄█▄█ ▄▆▆▄ ▄█▄▂ ▄▆▆▂ ▂▆▂▂ ▄▆▂▄ ▄▆█▆ ▂▆▂▂ ▄▆▄▄ ▄▆█▄ ▄█▂▂ ▄█▄▂ ▄█▆▄ ▂▆▂▂ ▄█▂▆ ▄▆██ ▄▆██ ▄▆█▄ ▂▆█▂ ▂▆▂▂ ▄▂▆▄ ▂▆▂▂ ▄▆▂▆ ▄█▂▆ ▄█▄▄ ▄█▂█ ▄▆▆▂ ▂▆▂▂ ▄▆▂▄ ▄█▂█ ▄▆▆▄ ▄▆▄▂ ▄▆▄▄ ▂▆▂▂ ▄█▄▂ ▄▆▆▂ ▄▆▄▄ ▂▆▂▂ ▄▆▂▄ ▄█▂█ ▄█▂█ ▄█▄▄ ▄▆█▄ ▄█▂▂ ▄█▄▂ ▄▆▆▄ ▄▆██ ▄▆█▆ ▂▆▂▂ ▄█▄▂ ▄▆▆▂ ▄▆▂▄ ▄█▄▂ ▂▆▂▂ ▄▆█▄ ▄█▆▄ ▂▆▂▂ ▄█▄█ ▄▆██ ▄█▂▆ ▄▆▄▂ ▄█▂█ ▂▆▂▂ ▄█▄█ ▄▆▆▄ ▄▆█▂ ▄▆█▂ ▂▆▂▂ ▄▆▂▆ ▄▆▄▄ ▂▆▂▂ ▄█▂▆ ▄▆▄▄ ▄▆▂▄ ▄▆▄▂ ▂▆█▆ ▂▆▂▂ ▂▆▂▂ ▄▂▂▄ ▄█▂█ ▂▆▂▂ ▄▆▂▄ ▄▆█▆ ▄█▆▄ ▂▆▂▂ ▄█▂▂ ▄█▂▆ ▄▆██ ▄█▂▂ ▄▆▂▄ ▄▆▄█ ▄▆▂▄ ▄▆█▆ ▄▆▄▂ ▄▆▆▄ ▄█▂█ ▄█▄▂ ▂▆▂▂ ▄█▄█ ▄▆▆▄ ▄▆█▂ ▄▆█▂ ▂▆▂▂ ▄▆▄▂ ▄▆▄▄ ▄▆█▂ ▄▆▆▄ ▄▆▄█ ▄▆▆▂ ▄█▄▂ ▂▆▂▂ ▄█▄▂ ▄▆██ ▂▆▂▂ ▄▆▄▂ ▄▆▄▄ ▄▆█▆ ▄█▆▄ ▂▆█▂ ▂▆▂▂ ▄▆█▄ ▄▆▂▄ ▄▆▆█ ▄▆▆▄ ▄▆█▆ ▄▆▄█ ▂▆▂▂ ▄█▂█ ▄█▄▄ ▄█▂▆ ▄▆▄▄ ▂▆▂▂ ▄█▄▂ ▄▆▆▂ ▄▆▂▄ ▄█▄▂ ▂▆▂▂ ▄█▄█ ▄▆██ ▄█▂▆ ▄▆▄▂ ▄█▂█ ▂▆▂▂ ▄▆▂▄ ▄█▂▆ ▄▆▄▄ ▂▆▂▂ ▄▆█▄ ▄▆▄▄ ▄▆▂▄ ▄▆█▆ ▄▆▆▄ ▄▆█▆ ▄▆▄█ ▄▆█▂ ▄▆▄▄ ▄█▂█ ▄█▂█ ▂▆▂▂ ▄▆▆▄ ▄█▂█ ▂▆▂▂ ▄█▄▂ ▄▆▆▂ ▄▆▄▄ ▂▆▂▂ ▄▆▄▆ ▄▆▆▄ ▄█▂▆ ▄█▂█ ▄█▄▂ ▂▆▂▂ ▄█▂█ ▄█▄▂ ▄▆▄▄ ▄█▂▂ ▂▆▂▂ ▄█▄▂ ▄▆██ ▄█▄█ ▄▆▂▄ ▄█▂▆ ▄▆▄▂ ▂▆▂▂ ▄▆▄▄ ▄█▂█ ▄█▄▂ ▄▆▂▄ ▄▆▂▆ ▄▆█▂ ▄▆▆▄ ▄█▂█ ▄▆▆▂ ▄▆▆▄ ▄▆█▆ ▄▆▄█ ▂▆▂▂ ▄▆██ ▄█▂▆ ▂▆▂▂ ▄▆▂▄ ▄▆▂█ ▄▆▆█ ▄▆█▆ ▄▆██ ▄█▄█ ▄▆█▂ ▄▆▄▄ ▄▆▄▂ ▄▆▄█ ▄▆▆▄ ▄▆█▆ ▄▆▄█ ▂▆▂▂ ▄█▄▂ ▄▆▆▂ ▄▆▄▄ ▂▆▂▂ ▄▆█▄ ▄▆▄▄ ▄▆▂▄ ▄▆█▆ ▄▆▆▄ ▄▆█▆ ▄▆▄█ ▄▆█▂ ▄▆▄▄ ▄█▂█ ▄█▂█ ▄▆█▆ ▄▆▄▄ ▄█▂█ ▄█▂█ ▂▆▂▂ ▄▆██ ▄▆▄▆ ▂▆▂▂ ▄▆▂▄ ▄▆▂█ ▄█▄▂ ▄▆▆▄ ▄▆██ ▄▆█▆ ▂▆█▆ 

% text to unicode pictogram (arbitrary-base encoding)
clc; clear all; 
format compact

instring=sprintf('And of course, I used Matlab to do it!');
charmap='▂▄▆█'; % length(charmap) determines numeric base
space=' '; % thin space

outstring='';
newchar='';
b=length(charmap);
bs=ceil(log(128)/log(b));
for n=1:1:length(instring);
    nc=double(instring(n));
    for m=1:1:bs;
        bitstring=floor(mod(nc,b^m)/b^(m-1));
        for k=0:1:bs-1;
            if bitstring==k
                newchar=[charmap(k+1) newchar];
            end
        end
    end
    
    outstring=[outstring newchar space];
    newchar='';
end
outstring

▄▂▂▄ ▄▆█▆ ▄▆▄▂ ▂▆▂▂ ▄▆██ ▄▆▄▆ ▂▆▂▂ ▄▆▂█ ▄▆██ ▄█▄▄ ▄█▂▆ ▄█▂█ ▄▆▄▄ ▂▆█▂ ▂▆▂▂ ▄▂▆▄ ▂▆▂▂ ▄█▄▄ ▄█▂█ ▄▆▄▄ ▄▆▄▂ ▂▆▂▂ ▄▂█▄ ▄▆▂▄ ▄█▄▂ ▄▆█▂ ▄▆▂▄ ▄▆▂▆ ▂▆▂▂ ▄█▄▂ ▄▆██ ▂▆▂▂ ▄▆▄▂ ▄▆██ ▂▆▂▂ ▄▆▆▄ ▄█▄▂ ▂▆▂▄


Saturday, July 11, 2015

Blend images with MATLAB

UPDATE: These scripts have been vastly improved. Find the current versions here.

In the course of doing some image mutilation in Matlab, I had need to perform some blend operations.  Prior to that point, I had been using GIMP to perform the task, but I'd rather be able to automate things.  There are probably a number of FEX submissions that could replace this function, but like usual, I decided to reinvent the wheel and write my own.

The following function makes available most of the common blend modes available in GIMP or Photoshop, but I decided to add a few extra things.  Uncommon features include:
  • Adjustable dodge/burn amount (not just opacity!) 
  • Several luminance-dependent analogs of common functions
  • Lightness and Intensity modes
  • Several hue permutation modes
  • Several color permutation (hue & saturation) modes
  • Inputs can be single images or image sequences (4-D inputs!)
The ability to handle 4-D inputs is a big plus for processing animation sequences.  The function can take matching 3-D or 4-D array inputs, or a mismatched input of one image and a 4-D array.  In the latter case, the single image is blended with all frames of the 4-D input.  This makes it possible to easily blend a static overlay onto all frames of an animation, for instance. 

This is probably in need of some work still; I'd like to add some sort of alpha support to things perhaps.  In the meantime, it's working well.  Many of the conversions use colorspace() from Pascal Getreuer.
function  outpict=imblend(FG,BG,opacity,blendmode,amount)
%   IMBLEND(FG, BG, OPACITY, BLENDMODE,{AMOUNT})
%       blend images or imagesets as one would blend layers in GIMP or
%       Photoshop.  
%
%   FG, BG are RGB image arrays of same H,V dimension
%       both can be single images or 4-D imagesets of equal length
%       can also blend a single image with a 4-D imageset
%       mismatches of dimensions 3:4 result in array expansion
%       allows blending static overlays with an entire animation
%       mismatches of dimensions 1:2 are not supported
%   OPACITY is a scalar from 0 to 1
%       defines mixing of blended result and original BG
%   AMOUNT is a scalar (optional, default 1)
%       used to internally scale the influence of blend calculations
%   BLENDMODE is a string assignment (see list)
%
%
%   MODES: 
%       normal      
%       screen
%       overlay     (standard method)
%       softlight   (GIMP overlay)
%       hardlight
%       vividlight
%       hardmix     (similar to posterization)                  amount:[0 1]
%       posterize   (stronger influence from mask)
%       colordodge  (similar to GIMP dodge)                     amount:[0 1]
%       colorburn   (similar to GIMP burn)                      amount:[0 1]
%       lineardodge                                             amount:[0 1]
%       linearburn                                              amount:[0 1]
%       lighten RGB     (lighten only (RGB))
%       darken RGB      (darken only (RGB))
%       lighten Y       (lighten only (luma only))
%       darken Y        (darken only (luma only))
%       scale add       (add bg to fg deviation from mean)     amount:(-inf to +inf)
%       scale mult      (scale bg by mean-normalized fg)       amount:(-inf to +inf)
%       multiply
%       divide
%       addition
%       subtraction
%       difference
%       exclusion 
%       hue         
%       saturation  
%       value    
%       luma1       (uses colorspace() YIQ conversion)
%       luma2       (Image Processing toolbox YIQ conversion)
%       lightness   (approx identical to intensity)
%       intensity
%       color       
%       permute H>H     (rotates hue by mask hue)               amount:(-inf to +inf)
%       permute dH>H    (rotates hue by hue difference)         amount:(-inf to +inf)
%       permute Y>H     (rotates hue by mask luma)              amount:(-inf to +inf)
%       permute dY>H    (rotates hue by luma difference)        amount:(-inf to +inf)
%       permute H>HS     (rotates color by mask hue)            amount:(-inf to +inf)
%       permute dH>HS    (rotates color by hue difference)      amount:(-inf to +inf)
%       permute Y>HS     (rotates color by mask luma)           amount:(-inf to +inf)
%       permute dY>HS    (rotates color by luma difference)     amount:(-inf to +inf)
%
%   NOTE:
%       modes which accept 'amount' argument are marked with effective range
%       dH>H and dH>HS permutations are same as 'hue' when amount==-1
%       color permutations combine hue rotation and saturation blending
%           saturation blending is maximized when abs(amount)==1
%
%   CLASS SUPPORT:
%       Accepts images of 'uint8', 'double', and 'logical'
%       Return type is inherited from BG
%       In the case of a 'double' input, any image containing values >1
%       is assumed to have a white value of 255. 

%   SOURCES:
%       http://www.venture-ware.com/kevin/coding/lets-learn-math-photoshop-blend-modes/
%       http://www.deepskycolors.com/archive/2010/04/21/formulas-for-Photoshop-blending-modes.html
%       http://en.wikipedia.org/wiki/Blend_modes
%       http://en.wikipedia.org/wiki/YUV
%       http://www.kineticsystem.org/?q=node/13
%       http://www.simplefilter.de/en/basics/mixmods.html

if nargin ~= 5
    amount=1;
end


% i had intended to make this more class-insensitive, but i never need it
% output type is inherited from BG, assumes white value of either 1 or 255
inclassFG=class(FG);
inclassBG=class(BG);
if strcmp(inclassFG,'uint8')
    fgmax=255;
elseif strcmp(inclassFG,'double')
    if max(max(max(FG)))<=1
        fgmax=1;
    else 
        fgmax=255;
    end
elseif strcmp(inclassFG,'logical')
    fgmax=1;
else
    disp('IMBLEND: unsupported class for FG')
    return
end

if strcmp(inclassBG,'uint8')
    bgmax=255;
elseif strcmp(inclassBG,'double')
    if max(max(max(BG)))<=1
        bgmax=1;
    else 
        bgmax=255;
    end
elseif strcmp(inclassBG,'logical')
    bgmax=1;
else
    disp('IMBLEND: unsupported class for BG')
    return
end


% expand along dimension 3 where necessary
if size(FG,3)&ltsize(BG,3)
    FG=repmat(FG,[1 1 size(BG,3) 1]);
elseif size(FG,3)&gtsize(BG,3)
    BG=repmat(BG,[1 1 size(FG,3) 1]);
end

% check if height & width match
sFG=size(FG);
sBG=size(BG);  
if any(sFG(1:2)~=sBG(1:2)) 
    disp('IMBLEND: images of mismatched dimension')
    return
end

% check frame count and expand as necessary
if length(sFG)~=4 && length(sBG)~=4 % two single images
    images=1;
else
    if length(sFG)~=4 % single FG, multiple BG
        FG=repmat(FG,[1 1 1 sBG(4)]);
    elseif length(sBG)~=4 % multiple FG, single BG
        BG=repmat(BG,[1 1 1 sFG(4)]); sBG=size(BG);
    elseif sFG(4)~=sBG(4) % two unequal imagesets
        disp('IMBLEND: imagesets of unequal length')
        return
    end
    images=sBG(4);
end

% perform blend operations
outpict=zeros(sBG);    
for n=1:1:images
    I=double(BG(:,:,:,n))/bgmax;
    M=double(FG(:,:,:,n))/fgmax;

    switch lower(blendmode)
        case 'normal'
            R=M;

        case 'screen'
            R=1-((1-M).*(1-I));

        case 'overlay'  % actual standard overlay mode 
            hi=I>0.5; lo=I&lt=0.5;
            R=zeros(size(I));
            R(lo)=2*I(lo).*M(lo);
            R(hi)=1-2*(1-M(hi)).*(1-I(hi));

        case 'softlight' % same as GIMP 'overlay' due to legacy bug
            Rs=1-((1-M).*(1-I));
            R=(I.*((1-I).*M+Rs));

        case 'hardlight'
            hi=M&gt0.5; lo=M&lt=0.5;
            R=zeros(size(I));
            R(lo)=2*I(lo).*M(lo);
            R(hi)=1-2*(1-M(hi)).*(1-I(hi));

        case 'vividlight'  % test this; example formulae are inconsistent
            hi=M&gt0.5; lo=M&lt=0.5;
            R=zeros(size(I));
            R(lo)=1-(1-I(lo))./(2*M(lo));
            R(hi)=I(hi)./(1-2*(M(hi)-0.5));

        case 'posterize'  % actually a broken version of vividlight
            hi=M&gt0.5; lo=M&lt=0.5;
            R=zeros(size(I));
            R(lo)=(1-I(lo))./(2*(M(lo)-0.5));
            R(hi)=1-I(hi)./(1-2*M(hi));

        case 'hardmix' % ps mode similar to posterization
            amount=max(min(amount,1),0);
            Rs=M+I;
            R=Rs;
            R(Rs&gt1)=1*amount;
            R(Rs&lt1)=0;

        % DODGES/BURNS    
        case'colordodge'
            amount=max(min(amount,1),0);
            R=I./(1-M*amount);

        case 'colorburn'
            amount=max(min(amount,1),0);
            R=1-(1-I)./(M*amount+(1-amount));

        case 'lineardodge' % addition
            amount=max(min(amount,1),0);
            R=M*amount+I;

        case 'linearburn' 
            amount=max(min(amount,1),0);
            R=M*amount+I-1*amount;

        % SIMPLE MATH OPS    
        case 'lighten rgb' % lighten only (RGB, no luminance)
            R=max(I,M);

        case 'darken rgb' % darken only (RGB, no luminance)
            R=min(I,M);

        case 'lighten y' % lighten only (based on luminance)
            Myiq=colorspace('RGB-&gtYIQ',M);
            Iyiq=colorspace('RGB-&gtYIQ',I);
            mask=Myiq(:,:,1)&gtIyiq(:,:,1);
            R=double(replacepixels(255*I,mask,255*M))/255;
            
        case 'darken y' % darken only (based on luminance)
            Myiq=colorspace('RGB-&gtYIQ',M);
            Iyiq=colorspace('RGB-&gtYIQ',I);
            mask=Myiq(:,:,1)&ltIyiq(:,:,1);
            R=double(replacepixels(255*I,mask,255*M))/255;

        case 'multiply'
            R=M.*I;

        case 'divide'
            R=I./(M+1E-3);

        case 'addition' % same as lineardodge
            R=M+I;

        case 'subtraction'
            R=I-M;

        case 'difference'
            R=abs(M-I);

        case 'exclusion'
            R=M+I-2*M.*I;

        case 'hue'
            Mhsv=rgb2hsv(M);
            Rhsv=rgb2hsv(I);
            Rhsv(:,:,1)=Mhsv(:,:,1);
            R=hsv2rgb(Rhsv);    

        case 'saturation'
            Mhsv=rgb2hsv(M);
            Rhsv=rgb2hsv(I);
            Rhsv(:,:,2)=Mhsv(:,:,2);
            R=hsv2rgb(Rhsv); 

        % V=max([R G B])
        % L=mean(max([R G B]),min([R G B]))
        % I=mean([R G B])
        % Y=[0.299 0.587 0.114]*[R G B]'

        case 'value'
            Mhsv=rgb2hsv(M);
            Rhsv=rgb2hsv(I);
            Rhsv(:,:,3)=Mhsv(:,:,3);
            R=hsv2rgb(Rhsv); 

        % all colorspace() Y-swaps produce identical results within 1 LSB
        % (YUV, YIQ, YCbCr, YPbPr, YDbDr)
        case 'luma1' % swaps fg bg luma
            Myiq=colorspace('RGB-&gtYIQ',M);
            Ryiq=colorspace('RGB-&gtYIQ',I);
            Ryiq(:,:,1)=Myiq(:,:,1);
            R=colorspace('RGB&lt-YIQ',Ryiq);

        case 'luma2' % swaps fg bg luma (using IP toolbox)
            Myiq=rgb2ntsc(M);
            Ryiq=rgb2ntsc(I);
            Ryiq(:,:,1)=Myiq(:,:,1);
            R=ntsc2rgb(Ryiq); 

        % L and I swaps are calculated differently,  
        % but results are practically identical (within 1 LSB) 
        % for all available HSL and HSI conversion implementations
        case 'lightness' % swaps fg bg lightness
            Mhsl=colorspace('RGB-&gtHSL',M);
            Rhsl=colorspace('RGB-&gtHSL',I);
            Rhsl(:,:,3)=Mhsl(:,:,3);
            R=colorspace('RGB&lt-HSL',Rhsl);

        case 'intensity' % swaps fg bg intensity 
            Mhsi=colorspace('RGB-&gtHSI',M);
            Rhsi=colorspace('RGB-&gtHSI',I);
            Rhsi(:,:,3)=Mhsi(:,:,3);
            R=colorspace('RGB&lt-HSI',Rhsi);

        case 'color' % same as GIMP, swap H&S
            Mhsv=rgb2hsv(M);
            Rhsv=rgb2hsv(I);
            Rhsv(:,:,1:2)=Mhsv(:,:,1:2);
            R=hsv2rgb(Rhsv); 

        % HUE PERMUTATIONS 
        case 'permute y>h' % permutes bg hue based on fg luma
            factors=[0.299 0.587 0.114];
            osize=size(M(:,:,1));
            cscale=repmat(reshape(factors,1,1,3),[osize 1]);
            Y=sum(M.*cscale,3);
            Rhsv=rgb2hsv(I);
            Rhsv(:,:,1)=mod(Rhsv(:,:,1)+Y*amount,1);
            R=hsv2rgb(Rhsv); 

        case 'permute h>h' % permutes bg hue based on fg hue
            Mhsv=rgb2hsv(M);
            Rhsv=rgb2hsv(I);
            Rhsv(:,:,1)=mod(Rhsv(:,:,1)+Mhsv(:,:,1)*amount,1);
            R=hsv2rgb(Rhsv);    

        case 'permute dy>h' % permutes bg hue based on luma difference
            factors=[0.299 0.587 0.114];
            osize=size(M(:,:,1));
            cscale=repmat(reshape(factors,1,1,3),[osize 1]);
            Ym=sum(M.*cscale,3);
            Yi=sum(I.*cscale,3);
            dY=Yi-Ym;
            Rhsv=rgb2hsv(I);
            Rhsv(:,:,1)=mod(Rhsv(:,:,1)+dY*amount,1);
            R=hsv2rgb(Rhsv); 

        % note that dH+H permutation is same as a hue swap when amount==-1
        case 'permute dh>h' % permutes bg hue based on hue difference
            Mhsv=rgb2hsv(M);
            Rhsv=rgb2hsv(I);
            dH=Rhsv(:,:,1)-Mhsv(:,:,1);
            Rhsv(:,:,1)=mod(Rhsv(:,:,1)+dH*amount,1);
            R=hsv2rgb(Rhsv); 

        % COLOR PERMUTATIONS (rotate hue and blend saturation)
        case 'permute y>hs' % permutes bg color based on fg luma
            factors=[0.299 0.587 0.114];
            osize=size(M(:,:,1));
            cscale=repmat(reshape(factors,1,1,3),[osize 1]);
            Y=sum(M.*cscale,3);
            amt=max(min(abs(amount),1),0); % needed since S-blending has limited range
            Mhsv=rgb2hsv(M);
            Rhsv=rgb2hsv(I);
            Rhsv(:,:,1)=mod(Rhsv(:,:,1)+Y*amount,1);
            Rhsv(:,:,2)=amt*Mhsv(:,:,2)+(1-amt)*Rhsv(:,:,2);
            R=hsv2rgb(Rhsv); 

        case 'permute h>hs' % permutes bg color based on fg hue
            amt=max(min(abs(amount),1),0); % needed since S-blending has limited range
            Mhsv=rgb2hsv(M);
            Rhsv=rgb2hsv(I);
            Rhsv(:,:,1)=mod(Rhsv(:,:,1)+Mhsv(:,:,1)*amount,1);
            Rhsv(:,:,2)=amt*Mhsv(:,:,2)+(1-amt)*Rhsv(:,:,2);
            R=hsv2rgb(Rhsv);    

        case 'permute dy>hs' % permutes bg color based on luma difference
            factors=[0.299 0.587 0.114];
            osize=size(M(:,:,1));
            cscale=repmat(reshape(factors,1,1,3),[osize 1]);
            Ym=sum(M.*cscale,3);
            Yi=sum(I.*cscale,3);
            dY=Yi-Ym;
            amt=max(min(abs(amount),1),0); % needed since S-blending has limited range
            Mhsv=rgb2hsv(M);
            Rhsv=rgb2hsv(I);
            Rhsv(:,:,1)=mod(Rhsv(:,:,1)+dY*amount,1);
            Rhsv(:,:,2)=amt*Mhsv(:,:,2)+(1-amt)*Rhsv(:,:,2);
            R=hsv2rgb(Rhsv); 

        % note that dH+H permutation is same as a hue swap when amount==-1
        case 'permute dh>hs' % permutes bg color based on hue difference
            amt=max(min(abs(amount),1),0); % needed since S-blending has limited range
            Mhsv=rgb2hsv(M);
            Rhsv=rgb2hsv(I);
            dH=Rhsv(:,:,1)-Mhsv(:,:,1);
            Rhsv(:,:,1)=mod(Rhsv(:,:,1)+dH*amount,1);
            Rhsv(:,:,2)=amt*Mhsv(:,:,2)+(1-amt)*Rhsv(:,:,2);
            R=hsv2rgb(Rhsv); 
            
        % SCALE ADD treats FG as an additive gain map with a null point at its mean
        case 'scale add'
            Mstretch=imadjust(M,stretchlim(M));
            centercolor=mean(mean(Mstretch,1),2);
            R=zeros(size(I));
            for c=1:1:3;
                R(:,:,c)=I(:,:,c)+(Mstretch(:,:,c)-centercolor(:,:,c))*amount;
            end

        % SCALE MULT treats FG as a gain map with a null point at its mean
        case 'scale mult'
            Mstretch=imadjust(M,stretchlim(M));
            centercolor=mean(mean(Mstretch,1),2);
            R=zeros(size(I));
            for c=1:1:3;
                R(:,:,c)=I(:,:,c).*(Mstretch(:,:,c)./centercolor(:,:,c))*amount;
            end
            
        otherwise
            disp('IMBLEND: unknown blend mode');
            return

    end

    R=min(R,1); 
    R=max(R,0);
    R(isnan(R))=1;
    outpict(:,:,:,n)=bgmax*(opacity*R + I*(1-opacity));
end

outpict=cast(outpict,inclassBG);

return

I've been thinking about putting some polish on the rest of my image-garbling toolbox, so maybe I'll reveal some examples in time.  I've also been toying with the idea of doing some animated plots of large datasets.  That may be interesting.

Wednesday, July 1, 2015

Revisiting STARS-922 and Other Stuff

There seems to be some skepticism and curiosity that surround STARS-922 thermal compound (aka "heat sink plaster") and some similar products.  I know I've wondered for a while myself and I've run across a handful of forum posts that question its purpose or properties.  Places like DealExtreme and Ebay sell lots of this product along side other more common zinc-oxide thermal pastes with limited product differentiation.  The poorly translated and dubious specifications and naming don't help to instill confidence that it is trustworthy of any particular application. On some occasions, customer reviews boast "strong" or "excellent conductivity" although i doubt either metric had been actually evaluated beyond the emotional satisfaction associated with the completion or failure of the parent project as determined by myriad other factors. I've found few satisfactorily detailed reviews, so I figured I'd add this bit of info to augment my halfass thermal testing from the prior post.

What the hell do you mean "plaster"?

Some of the questionable property specifications are:
  • Thermal conductivity: > 1.2W/m-K
  • Thermal Impedance: < 0.06 (what units?) 
  • Clotting time: 3min (25 degree celsius) (all of it or just the exposed stuff?)
  • Strength of connected buildings: 25Kg  (what?) 
  • Temperature resistance: 200 degree celsius
Following some of my own experience and a few comments noting that it had little adhesive capability, I figured I would try to get a better feel for the mechanical properties of the product in a way that should at least lend intuition a better handle on what's appropriate to expect.

First off, this product is not a thermal grease.  It is much more viscous and does indeed congeal on exposure to air. The cured product is slightly rubbery, but not as elastic as one might expect from a conventional RTV silicone caulking.  The most common place I have seen this is in the bonding of the MCPCB to the radiator body of cheap LED retrofit lamps.  In such an application, temperatures are low and mechanical stress is small.  The MCPCB has only a small mass, and the aspect ratio ensures that it is difficult to produce a tension stress at the interface.  This is all overshadowed by the advantage of being enclosed.  Nothing can stress the bonded part except its own inertia.  This seems to be a rather forgiving application, mechanically speaking.  Will it work for larger SMT heat sinks or other things?  We need to have a bit more understanding about its actual strength in shear and tension.


In order to test shear and tension strength, I put together a setup in the lathe consisting of various repurposed fixturing and a 1000# load cell with a selectable-gain instrumentation amplifier.  Since I never finished the microcontroller DRO that was supposed to go with the amplifier I made for this load cell, I have to use the multimeter to take direct readings.  Despite the extra clumsiness of operations, the results would be the same.  To get the approximate peak value at break, I simply used the phone to record operations.  Accepting that the periodic multimeter update is the result of an averaging operation and may lag the actual value, and forgiving my inability to produce a smooth and monotonically increasing tension by handscrew, the captured values should be acceptable for the intended purpose if averaged across a few samples.

some assembled samples and spring clamp for alignment

The shear samples consist of aluminum plates cut so that they produce a lap joint with approximately one square inch of area.  The tension samples are old elevator bucket bolts that have had their head faces ground and scoured.  All samples are ground and scoured in degreaser as needed to prepare a fresh surface each time.  No particular assembly fixturing was used; no clamping was performed except to gently maintain alignment with adhesives that were flowable (e.g. molten hot glue).  Samples were tested with heat sink plaster, some Permatex RTV silicone products, as well as generic superglue and two unknown hot glue products.  One was a soft and flexible hobby-marketed product, and the other was a harder Arrow brand carpentry-marketed product, if I recall correctly.



In approximate terms, the heat sink plaster (HSP) is about on par with a low-strength RTV silicone.  As with the prior thermal resistance testing, I ran a few sample groups of Permatex Ultra Grey RTV and High Temp Red RTV gasketing products.  These represent the extremes of the product spectrum in terms of specified tensile strength and filler density.  It's a fair bet to assume that if you'd be comfortable with the strength of your favorite RTV silicone, the strength of HSP will be sufficient for your task.  There are a few things to consider, though.

One of the primary obstacles I ran into with this setup is that I'm testing air-cured materials in thin sections.  With nonporous sample substrate, the geometry of the test patch does end up becoming important.  Especially with attempts to cure products at room temperature, it was not uncommon to find samples that were only cured on an annulus around the perimeter of the bond patch.  Most of these partially-cured sample groups were discarded for various reasons, typically nonuniformity or large variance within a group.  In the case of some samples with uniform partial curing, I made my best attempt to measure the effective cured area using image processing after the break test.  While I note that my goal here is to measure strength of the cured adhesive fraction, I did not make any differentiation in the thermal testing process.  It may be worth knowing if the thermal resistance is much higher when the center of the sample patch has cured completely, but I can't exactly put the thermal test fixture in the oven with all its plastic bits.  I'm also far too lazy to do larger test groups over a longer natural curing interval of say a month. 


For what it's worth, the image processing used to calculate partial bond area is rather simple and goes to show that GIMP or Photoshop are useful for more than just meandering artfaggotry drawing and editing photos.  It goes like this:
  • Remove uncured adhesive with solvent
  • Take photos of both halves
  • Transform image areas to geometric correspondence
  • Perform threshold operation to isolate cured material
  • Multiply images to get combined coverage
  • Find average pixel value
The example shown is not a particularly well-cured sample (42% cured), but it was from a group that were uniform.  

In my attempts to expedite thorough curing of the samples, I tried several runs at elevated temperatures in the oven.  Some of these resulted in reduced and typically inconsistent strength.  In the case of the RTV, some of the extended cure times (96 hours at 70-80 °C) showed very low shear strength, and the bond patterns were not characteristic of radial curing.  My guess is that as the silicone cured, it becomes increasingly difficult to outgas any remaining volatile products and pressure produced tear channels in the material near which further curing can take place.

some bad rtv shear samples

Some of the samples of heat sink plaster which were cured at high temperatures (100 °C) became dry and friable, taking on a tan color.  These samples still exhibited normal shear strength, but had dramatically reduced tensile strength.  Whether this loss of pliability and tensile strength occurs at lower temperatures over a longer time frame is unknown, but could potentially be a limiting factor in reliability.  It also calls into direct question the product specifications.  I doubt that the dry state is an intended condition for the product; consequently, I doubt that the 200 °C rating is realistic.  The condition of samples cured at 70 °C was significantly better than those cured at only 30 °C more.

Overall, the product performance is more than adequate for small heat sinks, particularly low-height heat sinks.  The thermal performance is not exceptional, but for the same area, it has a similar thermal resistance compared to a grease, despite forming a relatively thick interface film.  I still would hesitate to refer to it as a self-shimming type TIM, as I doubt that it's intended to serve as an electrical insulator.  That said, It's important to note the limitations of these tests.

All of the RTV silicone and HSP samples are very fresh and really don't represent thoroughly cured material.  It may be reasonable to expect stronger bonds over a time span of a month or so, depending on the ability of the center of the bond patch to breathe.  Similarly, these tests do not indicate whether strength or thermal performance will be sustained over much longer time frames at elevated temperatures.  Longevity is still uncertain, and performance at temperatures beyond 100 °C is questionable. 

That said, there are always other products available that fill a similar role.  STARS-922 is simply the cheapest option.  There are other cheap options out there in the realm of silicone products, and there are also epoxies for use when greater strength is desired and disassembly isn't intended.  There are always the myriad products from Loctite and Dow for specific applications, though most of these are far too expensive for the hobbyist.

One other note as an aside: the hot glue samples were not applied with a glue gun.  These, like most cases where I use hot glue, were applied with a heat gun.  This is due to the simple fact that molten glue typically lacks the thermal capacity to heat a conductive substrate sufficiently to allow bonding to take place before the glue solidifies.  Disregard for this simple fact appears widespread, as it seems every time I encounter hot glue in assembled products, it simply pops off the surfaces cleanly.  So long as you can heat parts manually, hot glue does indeed have utility outside the realm of gluing googly eyes onto cotton balls.  Even the cheap hobby products are incredibly strong so long as the application temperatures stay low.

Finally, I'll leave you with this bit of Matlab kludge that I put together to create the bar graph above.  I always get so frustrated trying to use spreadsheet graphing utilities to do anyhing.  It's impossible to get half the features that are desired, and the figures come out with cartoonish inelegance and poor repeatability.  Granted, a configuration with proportionally-correspondent dual axes is a hassle even in Matlab, but at least Matlab provides a superabundance of flexibility.  One might ask why anyone would ever need dual x or y axes, but the simple answer seems like a terribly common necessity to me: depicting one set of data in both metric and imperial units. 

%% plot adhesive properties
clc; clf; clear all
format compact;

xt={'HT RTV 48h @ 70dC' 'HSP 24h @ 25dC' 'HSP 48h @ 70dC' 'UG RTV 48h @ 70dC' ...
    'Super Glue (generic)' 'Hot Glue (soft clear)' 'Hot Glue (hard amber)'};
ytkpa=[1299 1366 1860 2418 2552 3354 4758];
scalefactor=690/4758;
ytpsi=ytkpa*scalefactor;
scalelabels={'kPa' 'PSI'};
titlestring='Tensile Stress at Break';
ystretch=0; % stretches bar widths for better fit

% create first axis
hl1 = barh(1:1:length(xt),ytkpa);
ax1 = gca;
set(ax1,'XColor','r','YColor','k','box','off','color','none','YTickLabel',xt,...
    'XAxisLocation','bottom','YAxisLocation','left','XGrid','on',...
    'TickLength',[0.015 0],'ylim',[0+ystretch length(xt)+1-ystretch])

% this hackery removes the top and right tick marks from ax1
b = axes('Position',get(ax1,'Position'),'box','on','xtick',[],'ytick',[]);
axes(ax1); linkaxes([ax1 b]);

% create second axis
ax2 = axes('Position',get(ax1,'Position'),'XColor','b','YColor','k',...
    'XAxisLocation','top','YAxisLocation','right','Color','none',...
    'XGrid','on','Ytick',[],'TickLength',[0.02 0],...
    'xlim',get(ax1,'xlim')*scalefactor,'ylim',get(ax1,'ylim'));
hold(ax2,'on')
hl2 = barh(1:1:length(xt),ytpsi,'Parent',ax2);
set(hl2,'facecolor',[1 1 1]*0.5,'edgecolor',[1 1 1]*0);
set(get(hl2,'BaseLine'),'color','k')

% set and position the labels and titles
xl1=xlabel(ax1,scalelabels(1),'units','normalized','position',[-0.1 -0.02 0]);
xl2=xlabel(ax2,scalelabels(2),'units','normalized','position',[-0.1 1 0]);
t1=title(ax2,titlestring,'units','normalized','position',[0.5 1.09 0],'fontweight','bold');

% apply value labels on bars (not exactly robust geometry retention)
for n=1:1:length(xt)
    scalemax=max(get(ax2,'xlim'));    
    valb=text(ytpsi(n)-0.01*scalemax,n-0.0,['\color {red}' num2str(round(ytkpa(n))) ...
        ' \color {blue}' num2str(round(ytpsi(n)))], ...
        'horizontalalignment','right','fontweight','bold');
end

% this mess massages the plot to fit well within its container
marginoffset=[0.04 0.01];
margins=get(ax2,'tightinset'); h=[margins(1) margins(3)]; v=[margins(2) margins(4)];
set(ax1,'units','normalized','outerposition',[[h(1) v(1)]+marginoffset 1-sum(h) 1-sum(v)])
set(ax2,'units','normalized','outerposition',[[h(1) v(1)]+marginoffset 1-sum(h) 1-sum(v)])
set(b,'units','normalized','outerposition',[[h(1) v(1)]+marginoffset 1-sum(h) 1-sum(v)])

% updating axis sync without this is a bunch of shit
% similar to 'linkaxes()', but works with unequal axis limits (and 3 axes)
addlistener(ax1,'Position','PostSet',@(src,evt) updateAxis(ax1,ax2,b));
function updateAxes(a1,a2,a3)
    % callback function for updating internal plot geometry
    % to keep axis objects aligned if container geometry is altered
    xLim1=get(a2,'xLim');
    yLim1=get(a2,'yLim');

    set(a2,'position',get(a1,'position'));
    set(a3,'position',get(a1,'position'));
    set(a2,'xLim',xLim1);
    set(a2,'yLim',yLim1);
end

Tuesday, June 16, 2015

Digitize all those binders full of notes

Ever find yourself referring to your old notes and school work?  Why not cram that giant stack of sketchy, error-riddled paper into a pdf or nine?  Just think of all the advantages!
  • they would be more portable - you could put them on a usb stick or a phone
  • they would be easier to reference while doing design work on the computer
  • they would take up less space and collect fewer dead bugs 
  • they might not get damaged by the leaky roof or eaten by termites
  • they could be shared with people who want to learn that they can't read your shitty handwriting
Maybe you could even have the prescience to approach this task before you're two years past graduation!

Just some books and binders

Over my many years wasting my life with a bad drawing habit, I've learned one thing from flatbed scanners: graphite pencil marks on paper are reflective.  This means that for particular illumination angles, scanned or photographed pencil handwriting/drawings may be washed out very effectively.  Long ago, I simply abandoned scanners for drawing purposes because of this.  I decided to simply use my second-hand smartphone as a camera.  I grabbed some scrap metal from the pile next to the bandsaw, and after some sawing and hammering and hot glue, I created a tripod mount for the phone.  I set up a light tent and made a staging easel out of a pizza box.  After ten minutes of page flipping, I have a hundred pictures of upside-down pages that I can hardly read with my D-grade eyeballs. 

Rectification and contrast enhancement

Now, how to process the images?  For most purposes, processing of scanned books or handwriting can be done with a simple procedure:
  • desaturate original image
  • make a copy
  • blur the copy with a large kernel
  • blend the blurred mask with the original in a Divide mode
  • re-adjust levels as appropriate
This works well and produces a high-contrast image.  Keep in mind though why it works.  This is essentially a high-pass filter method.  Only the low spatial frequency content survives the blurring operation and is removed in the blend operation.  This removes the effects of uneven lighting or slight paper contours, but if the page content is not restricted to narrow lines, we'll run into problems. 

Excess filtering on pages with graphics

Let's say some of the pages have printed figures or tables; the removal of low-frequency content will tend to leave only edges of any solid dark regions.  In the binders that contained occasional printed graphics, I used a different method for processing printed pages.  Since most of my handwritten notes are on yellow paper, I simply processed non-yellow pages differently.  If I know there are no graphics, I can just ignore the testing routine.

The color-testing routine finds the average color of an annulus of the page so as to ignore content and page placement inaccuracy.  One convenience of this is that images that are processed with the high-pass filter method can be re-colorized if desired.  I personally don't find this to help with visual contrast, so I didn't use it.

#!/bin/bash
# process photos of coursework pages from binders
# uses slower method of contrast mask generation and overlay 

#581 1773x2283+45+543 180
#487 1758x2286+54+546 180
#488 2220x1716+321+36 270
#221 1785x2325+51+531 180
#GDn 1755x2394+24+657 180
#471 1803x2319+33+540 180
#537 1779x2286+45+552 180

pdfname="ECE537_Integrated_Photonics"
cropsize="1779x2286+45+552" #the rect parameters for cropping
rotateamt="180"    #how much to rotate after cropping

indir="originals" #this is the local directory name where the original images are
outdir="output"   #this is the local directory name where the script will shit out the processed pages
outqual=85   #percent jpeg quality
hconly=1   #always assume pages are handwritten (used when there aren't any printed graphics pages)
retint=0   #percent retint for yellow pages
retintmode="multiply"

# ###########################################################################
if [ $hconly == 1 ]; then 
 echo "high contrast mode"
else
 echo "auto contrast mode"
fi

page=1
for infile in $(ls -1 $indir/*.jpg); do
 outfile="$outdir/output-$(printf "%04d" $page).jpg"
 jpegtran -crop $cropsize -trim -copy none $infile | \
 jpegtran -rotate $rotateamt -trim -outfile temp.jpg
 
 if [ $hconly == 0 ]; then 
  # get average page color excluding border and content
  imgstring=$(convert \( temp.jpg -threshold -1 -scale 95% \) \
    \( temp.jpg -threshold 100% -scale 80% \) \
   -gravity center -compose multiply -composite - | \
   convert temp.jpg - -alpha off -gravity center -compose copy_opacity -composite -resize 1x1 txt:)
  RGB=$(echo $imgstring | sed 's/ //g' | sed 's/(/ /g' | sed 's/)/ /g' | sed 's/,/ /g' | cut -d ' ' -f 6-8)
  R=$(echo $RGB | cut -d ' ' -f 1)
  G=$(echo $RGB | cut -d ' ' -f 2)
  B=$(echo $RGB | cut -d ' ' -f 3)
  isyel=$(echo "($R+$G)/2 > $B*1.3" | bc)
  #echo $imgstring
  echo $R $G $B ">> $page is yellow? >>" $isyel
 fi

 if [ $hconly == 1 ] || [ $isyel == 1 ]; then
  # if page is yellow, do 100% contrast enhancement and partial page re-tinting 
  if [ $retint != 0 ]; then 
   convert -modulate 100,0 temp.jpg - | \
   convert - \( +clone -filter Gaussian -resize 25% -define filter:sigma=25 -resize 400% \) -compose Divide_Src -composite - | \
   convert -level 70%,100% -quality 100 - temp.jpg
   convert temp.jpg \( +clone -background "rgb($R,$G,$B)" -compose Dst -flatten \) -compose $retintmode -composite - | \
   convert temp.jpg - -compose blend -define compose:args=$retint -composite - | \
   convert -quality $outqual - $outfile
  else
   convert -modulate 100,0 temp.jpg - | \
   convert - \( +clone -filter Gaussian -resize 25% -define filter:sigma=25 -resize 400% \) -compose Divide_Src -composite - | \
   convert -level 70%,100% -quality $outqual - $outfile  
  fi
 else
  # if page is not yellow, retain most color and do a 50% contrast enhancement
  convert -modulate 100,80 temp.jpg - | \
  convert - \( +clone -filter Gaussian -resize 25% -define filter:sigma=25 -resize 400% \) -compose Divide_Src -composite - | \
  convert - temp.jpg -compose blend -define compose:args=50 -composite - | \
  convert -level 25%,100% -quality $outqual - $outfile
 fi

 #echo $infile
 page=$[$page+1]
done

rm temp.jpg
convert $outdir/*.jpg $pdfname.pdf


The blurring method entails a size reduction and expansion.  This has two purposes: First, it speeds up blurs with a large kernel (in this case, by about a factor of 3); second, it helps reduce vignetting effects that would otherwise be caused by a simple "-blur 0,100" operation.  If a simple blur is used, it would help to crop the page oversize, then trim it down after contrast enhancement or after the blur itself.

-blur 0,100 -filter Gaussian -resize 25% -define filter:sigma=25 -resize 400%
Difference between simple blur and resize+blur methods

Of course you can guess that I'd do this in bash.  This is about as ad-hoc as they come.  It's not even externally parameterized.  I was thinking about doing this with Matlab, but I decided that I'd rather pull my hair out trying to do image stacks in ImageMagick.  Tell it where the files are, how they should be checked and compressed, and the script will grind them into a pdf for you.  I highly doubt anyone would ever actually use this ugly code, but I'm posting it anyway because I have nothing else to do. Still, I'm not dumb enough to post my horrible notes in whole.

Monday, June 15, 2015

Measuring thermal resistance

In the course of all the dumb things I do with electronics, I have often been forced to ask if some on-hand adhesive would be adequate for mounting heat sinks.  Often this is a matter of ad-hoc retrofitting rather than better design practice.  An example might be putting small milled heat sinks on a Raspberry Pi or replacing a small GPU heat sink with a trashy integral fan.  Maybe I need an insulating washer for a Multwatt-15 package, and all I have is mica for a TO-220.  Will an aluminized mylar antistatic bag work well enough?

"Don't worry, this is just temporary" --me, ca. 2009

Beyond that, what about more typical design guidelines?  Figures for typical case-sink thermal resistance for a common TO-220 package vary widely.  Whose numbers should I have confidence in using -- or should I just assume the worst numbers I can find?  That hardly seems better than guessing.

Asking the internet for the thermal properties of anything other than a legitimate TIM is pretty much futile.  Even those figures listed for half of the common materials at the bottom of the market are probably questionable anyway.  Is that $0.99 white goo from ebay really 1.5 W/mK?  After stumbling on enough clickbait advertisements non-quantitative or questionable computer cooling articles or product reviews wherein heat flows are measured in minutes of videogame, I figured I might as well just do it myself.  That's not to say my efforts were all that accurate.  Consider this more as an inexpensive method in development than a set of authoritative results.


So how to measure thermal resistance of a TIM?  There are a number of ways. A smart way might be like the above image.  Say we use heated and chilled volumes of water pumped through heat exchangers.  If we can measure the inlet and outlet temperatures on the heat exchangers, as well as the volume flow rates on either side, we can quantify the heat flow that actually passes through the thermal interface.  Sure, heat is being being lost in the hoses and into the air, but so long as we know the temperature differentials and the mass flow rates (implied by volumetric flow), we can eliminate those unknowns.  This could be a static configuration, or maybe we could switch the inlet from chilled to heated and extract the thermal resistance from the transient response.


That's a really cool idea, but I don't have a bunch of flow meters and pumps and I don't feel like putting that together just to test some goo once in a while.  Certainly, there are a lot of other methods that are all documented and standardized.  It seems that in most cases though, fabrication of either heaters, sensors, or interface surfaces becomes difficult, expensive, or at least tedious.  I don't want to have to hand-finish any more surfaces than necessary. 

Let's try something that's cheaper lazier simpler.  Consider the above diagram involving an electric heater and a heat sink.  In this configuration, it's simple enough to find the input heat flow, but how can the flux through the interface be quantified if the cold side is just a heat sink and ambient losses are unknown?  My primary assumption is this: if the stray losses on the heater can be made negligible, then the input power is approximately equal to the heat flux through the TIM.  Then, for the fixture area:


Maybe that's a bit too hopefully simplistic.  Maybe we can try to come up with an approximate loss ratio by using a simulation model.  Or better yet, if we can run the heater in the absence of the heat sink, we can get a rough figure for the heater-ambient thermal resistance, i.e. the resistance of the stray loss path.  If that information were available, the resistance of the conduction path then becomes:


By the time the numbers congeal in my mind, I already have visions of an assembly method.  Before we bother getting out of the chair, let's see if any of this seems reasonable.  With a few minutes of effort in FEMM, I threw together a questionable simulation of such a fixture.  The primary flaw is the fact that I didn't model any sort of convection transfer at the solid-air boundaries.  I just set a fixed boundary temperature at the extremities of the heat sink. It'll be close enough.  There are only a few things I want to know here:
  • What is the temperature gradient like around the interface?  
  • Will sensor placement be critical? 
  • Is the flux density uniform across the interface plane?
  • What's a ballpark figure for the stray losses from the heater?
Along with the goofy boundary condition selection, the axisymmetric model often forces approximations of shapes that aren't cylinders coaxial to the z-axis.  The heater resistors are modeled as a single cylinder with a geometry that approximates the degree to which cross-bored holes will occlude the cross-sectional area of the heater bar.


These quick checks suggest that for a relatively small heater power (23W), only a modest amount of insulation should be required to keep losses quite low (1-5%), even when jacket temperatures are at ambient.  So long as attention is paid to geometry, sensor placement shouldn't be terribly critical either.  Let's cobble together some rickety bullshit!

Heater bar, resistors, thermal overload, spring, scrap aluminum
Milled CPU heat sink
Heater bar and spring guide in insulator case (nested peanut butter jars)

The assembly is fairly self-explanatory.  The heater bar is machined from aluminum; the heat sink is fabricated by modifying a CPU heat sink.  The frame is assembled from scrap aluminum.  The heater has an integral spring and the compression screw uses a stop so that pressures are repeatable.


The resistor leads are insulated with wire insulation trimmings.  The resistors are bonded to the heater bar using STARS-922 "heat sink plaster".  This is a semi-setting heat sink compound.  It congeals within a few minutes, but doesn't form a tough film like an RTV silicone would.  This makes it easier to replace parts if necessary.  At the time, I was not sure of the properties of this material, but I wanted to be able to press the resistors out if necessary.  Besides, experimentally determining unknown properties was kind of the whole point behind building the fixture, wasn't it?

WOULD YOU LOOK AT ALL THAT STUFF

Once things are together, there are a couple things we can try to test.  The generic ebay thermocouples can be characterized to eliminate any static offsets they might cause.  The heater losses can also be checked to some degree.  If the exposed face of the heater is insulated, the bar can be gradually brought up to a nominal operating temperature to find its resistance to ambient.  Combining this figure with preliminary TIM tests suggest that stray losses are well within 3%.  For a good thermal contact such as a metal-filled grease, stray losses should constitute only about 1-2% of the input power.  It's nice when simulations jive.  With thermal resistance of the loss path known, the more accurate method for calculating interface resistance can be used.

We can also try to figure out the spring constant and set the stop at a reasonable interface pressure.  Due to a mismeasurement, I ended up adjusting this in the middle of things; not everything is measured at the same pressure.  For the most part though, the differences are negligible.  As a reference, typical TO-220 mounting forces from Philips suggest a pressure range of about 80-600 kPa. I'm testing at the low end, but I'd have to use different springs and brackets to go much higher. 

I've tested a few things so far, and the results are fairly expected.  When translated to suit transistor packages, the resistance figures seem appropriate.  What's interesting (and disconcerting) is that the areal resistance (K*m^2/W) doesn't quite jive with any of the specifications for the products used.  Of course, they're generic ebay grease, so I'd sooner accept the experimental result.  The kapton tape is the film alone with its adhesive removed.  The grease used with the insulator films is the ZP brand white grease.  Permatex Ultra Grey has the highest filler content and density of any of Permatex's RTV gasket products (afaik).  The orange high-temperature product is one of the lowest-solids materials they offer. 

I could've made a table, but nnnnope
Thermal resistances when translated to common semiconductor packages

I'd like to try to work out some volumetric conductivity (W/(m*k)) figures, but I've had difficulty getting repeatable results.  I need to come up with a better method of determining or enforcing TIM film thickness.  I'm not that terribly concerned with volumetric conductivity, though.  For most electronics applications, I feel that it makes more sense to leave the otherwise unknown film thickness buried in the metric. 

I'd like to try testing a bunch of other things, but these are just the common sorts that I had on hand.  If anyone wants me to test something, let me know.  Hah! Just kidding.  I know nobody will ever read this.  In case you don't exist, here are some perishable links to the materials used:
It should be noted that some materials like the metal-filled greases and acetoxy-cure RTV silicones have electrically important properties that bear consideration.  Some day I might show my method for testing that.  I also hope to have some of the mechanical properties of the STARS-922 tested soon as well.

Saturday, June 13, 2015

The Journey Never Ends

Well, that didn't take long.  It seems I have validated my doubts about the longevity of the ballast resistors I used in my motor starter project, though that is not to say that the resistors were all that had failed.

Ahh, memories

A while after being successfully installed, the motor start ramp became noticeably shorter.  At the time, I wasn't in the mood to even guess what had gone wrong, knowing that it would eventually get worse.  Indeed, within a few days, the resistors open-circuited and left the motor to start hard after the timed delay.  Upon inspection, two of the resistor legs were opened, and the third pair was intact ... but the corresponding SSR channel was shorted.

I had mentioned building three of these SSR's to use in an undervoltage start configuration and that I had subsequently watched them get violently destroyed when a relay collision dumped the branch SCC through the nine triacs.  One of those nine triacs actually survived and was re-used in the resistor starter configuration where it eventually failed.  Although I still question my snubber design and whether these triacs are genuine, the resistor-start configuration of the relay involves much lower voltages and switch stresses.  With no real evidence in support, I'll just assume that most recent triac failure was a product of the prior severe overcurrent.

That brings us to the resistors.  I clearly had little confidence in these things from the start, but they should have only been operating at about 20-25% of their allowable short-time overload rating. Did the SSR failure overstress them?  My rough calculations say no; shunting one leg during start would only increase average power in the remaining resistors by about 30%.  That's based on the false (but convenient) assumption that doing so did not also decrease the time required to reach steady state.  The increase should be negligible in comparison to the margin.


Let's take a closer look at the resistors.  When I decided to open these up, I honestly expected to find a 3W four band metal film resistor in a silicone blob.  I was surprised to find what looked like a wirewound element of decent construction.  The wire is 26 AWG, approximately 3.9 ohms/m.  My guess at an alloy would be alloy 294, maybe.  The ceramic former, end caps and terminations are all very sturdy.  The plastic end plugs are of a hard composition and are very tight in the extrusion.  Much like my other experiences with some of these cheap components from china, it seems there isn't an overarching theme of inadequacy.  Every bit of the part seems quite appropriate and well made -- all except one bad idea or decision made with complete disregard for part functionality.  In this case, the encapsulating cement ruined what was otherwise a useful part.

Blurry attempt to capture the porosity and coarseness of the cement sand

One might expect to find a wirewound resistance element like this encapsulated in a very fine-grained alumina cement made for the purpose.  The properties of interest might be its electrical conductivity (low), its thermal conductivity (high), and its maximum operating temperature.  Contrary to the goal of increased thermal conductivity, the resistor is packed with a porous and friable cake of coarse sand.  I honestly can't tell if there was originally any binder used or if the sand had just been weakly sintered from the heat of what must have been a yellow-hot resistance wire. The remaining material serves better as an insulator than anything else.  Fetching a questionably applicable thermal conductivity for packed sand of 0.4-0.5 W/m*K, rough calculations place the element temperature near the melting point after the first second's delivered energy.  All of the resistors showed this pattern of overheating (scaling, sagging, shorting wires), suggesting that the failures of the resistors and the triac were unrelated after all. 

At this point, I'm questioning my sanity and double-checking prices on soft-start modules.  No, they're still unaffordable.  It's interesting how much time and failure is still cheaper than a motor starter.  I guess that only works when your time is worth nothing.  Working with the assumption that the part failures were merely coincidental, I figured I'd rebuild it again with parts that weren't shit.  I rebuilt the relay using actual ST BTA26-800CW triacs from Mouser, and I replaced the chassis mount ballasts with three massive 6 ohm corrugated element resistors.  These resistors are not only 300W, but they have a 1000% 5 second overload capacity.  Compared to the prior configuration, these are so ridiculously oversized, they barely get warm.  Surprisingly, I only got these because they were the cheapest option.  I did have to machine some bushings and make some brackets though.

The resistor equivalent of a bigger hammer

With a few tweaks to the relay (I added a contact state indicator and adjusted trigger currents), everything seems to be working fine again.  I have no reason to think that the new ballast will be an issue.  Hopefully everything stays working.