How to detect base of pulse signal - algorithm
I have time series range from 0 to 10 as in the picture. The sample data is here.
I have try moving average, standard deviation in the moving window, but it does not get me the luck to detect base of the pulse I need. I manually put black X markers in the figure to show my interesting points.
How to process this time series?
I use find_peaks() with prominence=10 and width=10 as magic numbers. This partially accepted answer because it has voodoo variable in it.
peaks, props = find_peaks(p_array, prominence=10, width=100)
plt.plot(prediction_array, color='orange', label='predicted label')
plt.scatter(peaks, pp_array[peaks], marker='x', color='red')
plt.scatter([props["left_bases"][0], props["right_bases"][0]], [0, 0], marker='x', color='red', label="width=10,prominence=100")
plt.legend(loc="upper right")
Related
How to generate and concatenate spectrograms efficiently
I am working on a signal processing related problem. I have a dataset of >2000 EEG signals. Each EEG Signal is represented by a 2D Numpy array (19 x 30000). Each row of the array is one of the channels of the signal. What I have to do is to find the spectrograms on these individual channels (rows) and concatenate them vertically. Here is the code I wrote so far. raw = np.load('class_1_ar/'+filename) images = [] for i in range(19): print(i,end=" ") spec,freq,t,im = plt.specgram(raw[i],Fs=100,NFFT=100,noverlap=50) plt.axis('off') figure = plt.gcf() figure.set_size_inches(12, 1) figure.canvas.draw() img = np.array(figure.canvas.buffer_rgba()) img = cv2.cvtColor(img, cv2.COLOR_RGBA2BGRA) b = figure.axes[0].get_window_extent() img = np.array(figure.canvas.buffer_rgba()) img = img[int(b.y0):int(b.y1),int(b.x0):int(b.x1),:] img = cv2.cvtColor(img, cv2.COLOR_RGBA2BGRA) images.append(img) base = cv2.vconcat(images) cv2.imwrite('class_1_sp/'+filename[:-4]+'.png',base) c -= 1 print(c) And here is my output: However, the process is taking too much time to process. It took almost 8 hours for the first 200 samples to process. My question is, What can I do to make it faster?
Like others have said, the overhead of going through matplotlib is likely slowing things down. It would be better to just compute (and not plot) the spectrogram with scipy.signal.spectrogram. This function directly returns the spectrogram as a 2D numpy array, so that you don't have the roundabout step of getting it out of the canvas. Note, that does mean you'll have to map the spectrogram output yourself to pixel intensities. In doing that, beware scipy.signal.spectrogram returns the spectrogram as powers, not decibels, so you probably want to do 10*np.log10(Sxx) to the result (see also scipy.signal.spectrogram compared to matplotlib.pyplot.specgram). Plotting aside, the bottleneck operation in computing a spectrogram are the FFTs. Instead of using a transform size of 100 samples, 128 or some other power of 2 is more efficient. With scipy.signal.spectrogram this is done by setting nfft=128. Note, you can set nperseg=100 and nfft=128 so that 100 samples are still used for each segment, but zero-padded to 128 before doing the FFT. One other thought: if raw is 64-bit float, it may help to cast it to 32-bit: raw = np.load(...).astype(np.float32).
Signal enhancing algorithm
I need an algorithm (preferable in a Pascal-like language, but it the end doesn't really matter) that will make the "signal" (actually a series of data points) in left look like the one in right. Signal origin: The signal is generated by a machine. Oversimplifying the explanation, the machine is measuring the density of a liquid flowing through a transparent tube. So, the signal is nothing similar to an electrical signal (audio/radio frequency). The data points could look like this: [1, 2, 1, 3, 4, 5, 4, 3, 2, 1, 13, 14, 15, 18, 23, 19, 17, 15, 15, 15, 14, 11, 9, 4, 1, 1, 2, 2, 1, 2] What I need: I need to accurately detect the 'peaks'. For this, I already have a piece of code but it is not working on poor signals as the one shown in the image below. I think we can see this, as a signal that was accidentally passed through a low-pass filter, and now I want to restore it. Notes: There are 4 signals, but they are separated so they can be analyzed individually. So, it is enough to think about how to process just one of them. After a peak, if the signal is not coming down fast enough we can consider that there are multiple peaks (you can best see that in the 'red' signal at the end of the series). The advantage is that the whole series is available (so the signal is not in real time, it is already stored on file)! [Edit by Spektre] I extracted Red sample points from the image float f0[]={ 73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,72,71,69,68,66,64,62,58,54,49,41,33,25,17,13,15,21,30,39,47,54,59,62,64,66,67,68,69,70,71,71,72,72,72,71,71,70,69,68,67,66,65,63,62,60,56,51,45,37,29,22,18,18,22,28,33,35,36,35,32,26,20,15,12,15,20,26,31,35,37,36,34,30,25,22,22,27,33,41,48,55,60,63,66,67,68,69,70,71,72,72,73,73,73,73,73,73,72,71,70,69,67,65,63,60,55,49,40,30,21,13, 7,10,17,27,36,45,52,56,59,60,61,62,62,62,62,61,61,59,57,53,47,40,32,24,18,15,18,23,28,32,33,31,28,23,16,10, 6, 8,13,20,27,31,32,31,28,22,15,10, 6,10,16,23,30,34,36,36,34,29,24,20,19,24,30,37,44,51,56,59,61,62,63,64,64,64,65,64,64,62,60,57,53,48,43,38,36,39,43,49,54,59,63,66,68,69,70,71,72,72,73,73,73,73,73,73,73,73,73,73,73,73,73,73 }; float f1[]={ 55,58,60,62,64,66,67,68,68,69,69,70,71,72,72,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,72,72,72,72,72,72,72,72,72,72,72,72,72,72,72,73,73,73,72,72,72,72,71,71,71,71,71,71,70,70,69,68,67,66,64,63,61,60,59,57,55,52,49,46,43,40,37,35,34,33,32,32,33,34,36,37,39,41,43,45,47,50,52,55,57,59,60,61,61,62,62,62,62,61,61,60,58,57,55,53,51,49,48,46,44,42,40,38,35,32,30,29,28,27,27,26,26,26,25,25,24,23,23,23,24,24,25,25,26,26,26,27,28,29,31,33,35,38,40,41,43,44,46,48,50,53,55,57,59,60,61,62,63,64,64,65,65,64,63,61,59,57,54,52,50,47,45,42,39,37,34,32,31,30,30,30,31,32,34,36,37,39,40,41,42,43,44,44,44,44,43,42,41,40,38,36,34,32,30,28,26,25,24,23,22,21,20,18,17,17,17,17,18,18,18,18,18,18,18,18,18,18,19,19,19,19,20,20,21,23,24,25,26,26,26,27,28,29,31,34,36,37,38,40,41,43,45,47,48,49,50,51,51,51,50,49,49,48,48,47,47,47,47,47,47,48,60 }; const int n=sizeof(f0)/sizeof(f0[0]); All the values need to be transformed: f0[i] = 73.0-f0[i]; f1[i] = 73.0-f1[i]; To offset back from image... f0 is the original Red signal and f1 is the distorted Yellow one. This is the closest I get to with first order FIR filter: The upper half is plot of used FIR filter weights (editable by mouse so the FIR is hand drawed for fast weights find...). Bellow are the signal plots: Red original (Ideal) signal f0 Dark green measured signal f1 Light Green FIR filtered Ideal signal f2 The FIR filter is just convolution where zero offset element is the last and here the weight values: float fir[35] = { 0.0007506932, 0.0007506932, 0.0007506932, 0.0007506932, 0.0007506932, 0.0007506932, 0.0007506932, 0.0007506932, 0.0007506932, 0.003784107, 0.007575874, 0.01060929, 0.01591776, 0.02198459, 0.03032647, 0.04170178, 0.05686884, 0.06445237, 0.06900249, 0.07203591, 0.07203591, 0.0705192, 0.06900249, 0.06672744, 0.06217732, 0.05611049, 0.04928531, 0.04170178, 0.03335989, 0.02653471, 0.02046788, 0.01515941, 0.009850933, 0.005300813, 0.0007506932 }; So either there is also some higher degree of FIR or the weights need to be tweaked a bit more. Anyway this should be enough for the deconvolution and or fitting ... btw the FIR filter is done as follows: const int fir_n=35; // size (exposure time) [samples] const int fir_x=fir_n-1; // zero offset element [samples] int x,y,i,j,ii; float a,f2[n]; for (i=0;i<n;i++) for (f2[i]=0.0,ii=i-fir_x,j=0;j<fir_n;j++,ii++) if ((ii>=0)&&(ii<n)) f2[i]+=fir[j]*f0[ii];
In my opinion, you should start with a pretty simple system identification and consecutive signal reconstruction. Also, I would recommend to implement your algorithm first in a mathematical prototyping tool like Matlab (commerical license) or Octave (free → https://www.gnu.org/software/octave/download.html). These tools provide an ease of signal processing no programming language like Pascal or Java could ever offer, no matter what library you use. After you successfully designed your algorithm with Matlab or Octave, then think about how to implement it with Pascal. Lets assume the behaviour of the tube can be characterized by a linear time-invariant system (e.g. a linear lowpass filter). This is by no means guaranteed but a worthwhile approach (at least until it fails:) ). Following the same approach for non-linear and/or time-variant systems becomes pretty involved and you will need professional help to do this I would imagine. If I understood your description correctly, you have access to both the input and output signals of the tube. If I am wrong and you don’t know the input signal, you might be able to apply some calibration signal first, whose characteristics you know, and record the output signal. Knowing input and output signals is a prerequisite for the following approach. Without both signals you can not approximate the impulse response h of the tube. After calculating an approximation of h, we can design an inverse filer called ge and eventually reconstruct the input from the output signal. Here is the signal flow of a input signal x[n] passing your tube described by h producing the output signal y[n]. Taking y[n] and applying an inverse filtering operation described by ge we obtain xr[n] x[n] →| h | → y[n] → | ge | → xr[n] Take an input vector x of length N and a corresponding vector of y of the same length. Now you express the output y as a convolution of an input convolution matrix X (see the code below for its implementation) with the unknown impulse response of your system, i.e. y = X * h with the vector and matrix sizes y = N x 1, X = N x N and h = N x 1 You can calculate a least-squares approximation of the impulse response he, by calculating he = inv(X'*X)*X' * y where X' describes the transpose and inv() the matrix inverse of X. he represents a column vector of the identified impulse response of your tube which we obtained via a 1-D deconvolution. You can check how well the identification worked by calculating the output of your estimated system, ye = X * he and by comparing ye and y. Now, we try to reconstruct x from y and he. The reconstructed input vector xr is calculated by xr = Ge * y where Ge = inv(He) and He is the N x N convolution matrix of he. Here is some Octave code. Copy both functions in their own dedicated file (reconstruct.m and getConvolutionMatrix.m) and type "reconstruct.m" into the Octave command line to examine the outputs of the example. Please note the sample code works only for vector of odd length (N is odd). Play around with the size N of your vectors. This might help the approximation accuracy. function [Ge] = reconstruct () x = [1 2 3]'; # input signal # h = [1 3 2]'; # unknown impulse response y = [5 11 13]'; # output signal y = y + 0.001*randn(length(y),1) # add noise to output signal Xm = getConvolutionMatrix(x) Xps = inv(Xm'*Xm)*Xm'; he = Xps * y He = getConvolutionMatrix(he); Ge = inv(He); # reconstructed impulse signal xr = Ge*y endfunction function [mH] = getConvolutionMatrix(h) h = h(:)'; hlen = length(h); Nc = (hlen-1)/2; mH= zeros(hlen, hlen); hp = [zeros(1,Nc) h zeros(1,Nc)]; for c=1:hlen for r=1:hlen mH(r,c) = hp(r+hlen-c); end end endfunction
extracting many regions of interests ROIs) from thousand images
I have a large set of microscopy images and each image has several hundreds of spots (ROIs). These spots are fixed in space. I want to extract each spot from each image and save into workspace so that I can analyze them further. I have written a code myself and it working perfectly but its too slow. It takes around 250 sec to completely read out all the spots from every image. The core of my code looks as following: for s=1:NumberImages im1=imread(fn(s,1).name); im=im1-medfilt2(im1,[15,15]); for i=1:length(p_g_x) GreenROI(i,s)=double(sum(sum(im(round(p_g_y(i))+(-2:2), round(p_g_x(i))+(-2:2))))); RedROI(i,s)=double(sum(sum(im(round(p_r_y(i))+(-2:2), round(p_r_x(i))+(-2:2))))); end end As you can see from the code I am extracting 5x5 regions. Length of p_g_x is between 500-700. Thanks for your input. I used profile viewer to figure out which function exactly is taking more time. It was median filter which is taking a lot of time (~90%). Any suggestion to fast it up will be greatly appreciated. thanks Mahipal
Use Matlab's profiling tools! profile on % Starts the profiler % Run some code now. profile viewer % Shows you how often each function was called, and % where most time was spent. Try to start with the slowest part. profile off % Resets the Profiler, so you can measure again. Pre-allocate Preallocate the output because you know the size and this way it is much faster. (Matlab told you this already!) GreenROI = zeros(length(p_g_x), NumberImages); % And the same for RedROI. Use convolution Read about Matlab's conv2 code. for s=1:NumberImages im=imread(fn(s,1).name); im=im-medfilt2(im,[15,15]); % Pre-compute the sums first. This will only be faster for large p_g_x roi_image = conv2(im, ones(5,5)); for i=1:length(p_g_x) GreenROI(i,s)=roi_image(round(p_g_y(i)), round(p_g_x(i))); % You might have to offset the indices by 2, because of the convolution. Check that results are the same. RedROI(i,s)=roi_image(round(p_r_y(i)), round(p_r_x(i))); end end Matlab-ize the code Now, that you've used convolution to get an image of sums over 5x5 windows (or you could've used #Shai's accumarray, same thing), you can speed things up further by not iterating through each element in p_g_x but use it as a vector straight away. I leave that as an exercise for the reader. (convert p_g_x and p_g_y to indices using sub2ind, as a hint). Update Our answers, mine included, showed how premature optimisation is a bad thing. Without knowing, I assumed that your loop would take most of the time, but when you measured it (thanks!) it turns out that is not the problem. The bottleneck is medfilt2 the median filter, which takes 90% of the time. So you should address this first. (Note, that on my computer your original code is fast enough for my taste but it is still the median filter taking up most of the time.) Looking at what the median filter operation does might help us figure out how to make it faster. Here is an image. On the left you see the original image. In the middle the median filter and on the right there is the result. To me the result looks awfully similar to an edge detection result. (Mathematically this is no surprise.) I would suggest you start experimenting with various edge detections. Have a look at Canny and Sobel. Or just use conv2(image, kernel_x) where kernel_x = [1, 2, 1; 0, 0, 0; -1, -2, -1] and the same but transposed for a kernel_y. You can find various edge detection options here: edge(im, option). I tried all options from {'sobel', 'canny', 'roberts', 'prewitt'}. Except for Canny, they all take about the same time as your median filter method. Canny is 4x slower, the rest (including the original) take 7.x seconds. All of this without a GPU. imgradient was 9 seconds. So from this I would say that you can't get any faster. If you have a GPU and it works with Matlab, you could speed it up. Load your image as gpuArrays. There is an example on the medfilt2 documentation. You can still do minor speed ups but they can only amount to a 10% speed increase, so are hardly worthwile.
A few things you should do Pre-allocate as suggested by Didac Perez. Use profiler to see what exactly takes long in your code, is it the median filter? is it the indexing? Assuming all images are of the same size, you can use accumarray and a fixed mask subs to quickly sum the values: subs_g = zeros( h, w ); %// allocate mask for green subs_r = zeros( h, w ); subs_g( sub2ind( [h w], round(p_g_y), round(p_g_x) ) = 1:numel(p_g_x); %//index each region subs_g = conv2( subs_g, ones(5), 'same' ); subs_r( sub2ind( [h w], round(p_r_y), round(p_r_x) ) = 1:numel(p_r_x); %//index each region subs_r = conv2( subs_r, ones(5), 'same' ); sel_g = subs_g > 0; sel_r = subs_r > 0; subs_g = subs_g(sel_g); subs_r = subs_r(sel_r); once these masks are fixed, you can process all images %// pre-allocation goes here - I'll leave it to you for s=1:NumberImages im1=imread(fn(s,1).name); im=double( im1-medfilt2(im1,[15,15]) ); accumarray( subs_g, im( sel_g ) ); % summing all the green ROIs accumarray( subs_r, im( sel_r ) ); % summing all the green ROIs end
First, preallocate your GreenROI and RedROI structures since you previously know the final size. Now, you are resizing them again and again in each iteration. Secondly, I do recommend you to use "tic" and "toc" to investigate where is the problem, it will give you useful timings.
Vectorized code that operates on each image - %// Pre-compute green and red indices to be used across all the images r1 = round(bsxfun(#plus,permute(p_g_y,[3 2 1]),[-2:2]')); c1 = round(bsxfun(#plus,permute(p_g_x,[3 2 1]),[-2:2])); green_ind = reshape(bsxfun(#plus,(c1-1)*size(im,1),r1),[],numel(p_g_x)); r2 = round(bsxfun(#plus,permute(p_r_y,[3 2 1]),[-2:2]')); c2 = round(bsxfun(#plus,permute(p_r_x,[3 2 1]),[-2:2])); red_ind = reshape(bsxfun(#plus,(c2-1)*size(im,1),r2),[],numel(p_g_x)); for s=1:NumberImages im1=imread(fn(s,1).name); im=double(im1-medfilt2(im1,[15,15])); GreenROI=sum(im(green_ind)); RedROI =sum(im(red_ind)); end
How does the algorithm to color the song list in iTunes 11 work? [closed]
Closed. This question needs to be more focused. It is not currently accepting answers. Want to improve this question? Update the question so it focuses on one problem only by editing this post. Closed 6 years ago. Improve this question The new iTunes 11 has a very nice view for the song list of an album, picking the colors for the fonts and background in function of album cover. Anyone figured out how the algorithm works?
I approximated the iTunes 11 color algorithm in Mathematica given the album cover as input: How I did it Through trial and error, I came up with an algorithm that works on ~80% of the albums with which I've tested it. Color Differences The bulk of the algorithm deals with finding the dominant color of an image. A prerequisite to finding dominant colors, however, is calculating a quantifiable difference between two colors. One way to calculate the difference between two colors is to calculate their Euclidean distance in the RGB color space. However, human color perception doesn't match up very well with distance in the RGB color space. Therefore, I wrote a function to convert RGB colors (in the form {1,1,1}) to YUV, a color space which is much better at approximating color perception: (EDIT: #cormullion and #Drake pointed out that Mathematica's built-in CIELAB and CIELUV color spaces would be just as suitable... looks like I reinvented the wheel a bit here) convertToYUV[rawRGB_] := Module[{yuv}, yuv = {{0.299, 0.587, 0.114}, {-0.14713, -0.28886, 0.436}, {0.615, -0.51499, -0.10001}}; yuv . rawRGB ] Next, I wrote a function to calculate color distance with the above conversion: ColorDistance[rawRGB1_, rawRGB2_] := EuclideanDistance[convertToYUV # rawRGB1, convertToYUV # rawRGB2] Dominant Colors I quickly discovered that the built-in Mathematica function DominantColors doesn't allow enough fine-grained control to approximate the algorithm that iTunes uses. I wrote my own function instead... A simple method to calculate the dominant color in a group of pixels is to collect all pixels into buckets of similar colors and then find the largest bucket. DominantColorSimple[pixelArray_] := Module[{buckets}, buckets = Gather[pixelArray, ColorDistance[#1,#2] < .1 &]; buckets = Sort[buckets, Length[#1] > Length[#2] &]; RGBColor ## Mean # First # buckets ] Note that .1 is the tolerance for how different colors must be to be considered separate. Also note that although the input is an array of pixels in raw triplet form ({{1,1,1},{0,0,0}}), I return a Mathematica RGBColor element to better approximate the built-in DominantColors function. My actual function DominantColorsNew adds the option of returning up to n dominant colors after filtering out a given other color. It also exposes tolerances for each color comparison: DominantColorsNew[pixelArray_, threshold_: .1, n_: 1, numThreshold_: .2, filterColor_: 0, filterThreshold_: .5] := Module[ {buckets, color, previous, output}, buckets = Gather[pixelArray, ColorDistance[#1, #2] < threshold &]; If[filterColor =!= 0, buckets = Select[buckets, ColorDistance[ Mean[#1], filterColor] > filterThreshold &]]; buckets = Sort[buckets, Length[#1] > Length[#2] &]; If[Length # buckets == 0, Return[{}]]; color = Mean # First # buckets; buckets = Drop[buckets, 1]; output = List[RGBColor ## color]; previous = color; Do[ If[Length # buckets == 0, Return[output]]; While[ ColorDistance[(color = Mean # First # buckets), previous] < numThreshold, If[Length # buckets != 0, buckets = Drop[buckets, 1], Return[output]] ]; output = Append[output, RGBColor ## color]; previous = color, {i, n - 1} ]; output ] The Rest of the Algorithm First I resized the album cover (36px, 36px) & reduced detail with a bilateral filter image = Import["http://i.imgur.com/z2t8y.jpg"] thumb = ImageResize[ image, 36, Resampling -> "Nearest"]; thumb = BilateralFilter[thumb, 1, .2, MaxIterations -> 2]; iTunes picks the background color by finding the dominant color along the edges of the album. However, it ignores narrow album cover borders by cropping the image. thumb = ImageCrop[thumb, 34]; Next, I found the dominant color (with the new function above) along the outermost edge of the image with a default tolerance of .1. border = Flatten[ Join[ImageData[thumb][[1 ;; 34 ;; 33]] , Transpose # ImageData[thumb][[All, 1 ;; 34 ;; 33]]], 1]; background = DominantColorsNew[border][[1]]; Lastly, I returned 2 dominant colors in the image as a whole, telling the function to filter out the background color as well. highlights = DominantColorsNew[Flatten[ImageData[thumb], 1], .1, 2, .2, List ## background, .5]; title = highlights[[1]]; songs = highlights[[2]]; The tolerance values above are as follows: .1 is the minimum difference between "separate" colors; .2 is the minimum difference between numerous dominant colors (A lower value might return black and dark gray, while a higher value ensures more diversity in the dominant colors); .5 is the minimum difference between dominant colors and the background (A higher value will yield higher-contrast color combinations) Voila! Graphics[{background, Disk[]}] Graphics[{title, Disk[]}] Graphics[{songs, Disk[]}] Notes The algorithm can be applied very generally. I tweaked the above settings and tolerance values to the point where they work to produce generally correct colors for ~80% of the album covers I tested. A few edge cases occur when DominantColorsNew doesn't find two colors to return for the highlights (i.e. when the album cover is monochrome). My algorithm doesn't address these cases, but it would be trivial to duplicate iTunes' functionality: when the album yields less than two highlights, the title becomes white or black depending on the best contrast with the background. Then the songs become the one highlight color if there is one, or the title color faded into the background a bit. More Examples
With the answer of #Seth-thompson and the comment of #bluedog, I build a little Objective-C (Cocoa-Touch) project to generate color schemes in function of an image. You can check the project at : https://github.com/luisespinoza/LEColorPicker For now, LEColorPicker is doing: Image is scaled to 36x36 px (this reduce the compute time). It generates a pixel array from the image. Converts the pixel array to YUV space. Gather colors as Seth Thompson's code does it. The color's sets are sorted by count. The algorithm select the three most dominant colors. The most dominant is asigned as Background. The second and third most dominants are tested using the w3c color contrast formula, to check if the colors has enought contrast with the background. If one of the text colors don't pass the test, then is asigned to white or black, depending of the Y component. That is for now, I will be checking the ColorTunes project (https://github.com/Dannvix/ColorTunes) and the Wade Cosgrove project for new features. Also I have some new ideas for improve the color scheme result.
Wade Cosgrove of Panic wrote a nice blog post describing his implementation of an algorithm that approximates the one in iTunes. It includes a sample implementation in Objective-C.
You might also checkout ColorTunes which is a HTML implementation of the Itunes album view which is using the MMCQ (median cut color quantization) algorithm.
I just wrote a JS library implementing roughly the same algorithm that the one described by #Seth. It is freely available on github.com/arcanis/colibrijs, and on NPM as colibrijs.
With #Seth's answer I implemented the algorithm to get the dominant color in the two lateral borders of a picture using PHP and Imagick. https://gist.github.com/philix/5688064#file-simpleimage-php-L81 It's being used to fill the background of cover photos in http://festea.com.br
I asked the same question in a different context and was pointed over to http://charlesleifer.com/blog/using-python-and-k-means-to-find-the-dominant-colors-in-images/ for a learning algorithm (k Means) that rougly does the same thing using random starting points in the image. That way, the algorithm finds dominant colors by itself.
Measuring the average thickness of traces in an image
Here's the problem: I have a number of binary images composed by traces of different thickness. Below there are two images to illustrate the problem: First Image - size: 711 x 643 px Second Image - size: 930 x 951 px What I need is to measure the average thickness (in pixels) of the traces in the images. In fact, the average thickness of traces in an image is a somewhat subjective measure. So, what I need is a measure that have some correlation with the radius of the trace, as indicated in the figure below: Notes Since the measure doesn't need to be very precise, I am willing to trade precision for speed. In other words, speed is an important factor to the solution of this problem. There might be intersections in the traces. The trace thickness might not be constant, but an average measure is OK (even the maximum trace thickness is acceptable). The trace will always be much longer than it is wide.
I'd suggest this algorithm: Apply a distance transformation to the image, so that all background pixels are set to 0, all foreground pixels are set to the distance from the background Find the local maxima in the distance transformed image. These are points in the middle of the lines. Put their pixel values (i.e. distances from the background) image into a list Calculate the median or average of that list
I was impressed by #nikie's answer, and gave it a try ... I simplified the algorithm for just getting the maximum value, not the mean, so evading the local maxima detection algorithm. I think this is enough if the stroke is well-behaved (although for self intersecting lines it may not be accurate). The program in Mathematica is: m = Import["http://imgur.com/3Zs7m.png"] (* Get image from web*) s = Abs[ImageData[m] - 1]; (* Invert colors to detect background *) k = DistanceTransform[Image[s]] (* White Pxs converted to distance to black*) k // ImageAdjust (* Show the image *) Max[ImageData[k]] (* Get the max stroke width *) The generated result is The numerical value (28.46 px X 2) fits pretty well my measurement of 56 px (Although your value is 100px :* ) Edit - Implemented the full algorithm Well ... sort of ... instead of searching the local maxima, finding the fixed point of the distance transformation. Almost, but not quite completely unlike the same thing :) m = Import["http://imgur.com/3Zs7m.png"]; (*Get image from web*) s = Abs[ImageData[m] - 1]; (*Invert colors to detect background*) k = DistanceTransform[Image[s]]; (*White Pxs converted to distance to black*) Print["Distance to Background*"] k // ImageAdjust (*Show the image*) Print["Local Maxima"] weights = Binarize[FixedPoint[ImageAdjust#DistanceTransform[Image[#], .4] &,s]] Print["Stroke Width =", 2 Mean[Select[Flatten[ImageData[k]] Flatten[ImageData[weights]], # != 0 &]]] As you may see, the result is very similar to the previous one, obtained with the simplified algorithm.
From Here. A simple method! 3.1 Estimating Pen Width The pen thickness may be readily estimated from the area A and perimeter length L of the foreground T = A/(L/2) In essence, we have reshaped the foreground into a rectangle and measured the length of the longest side. Stronger modelling of the pen, for instance, as a disc yielding circular ends, might allow greater precision, but rasterisation error would compromise the signicance. While precision is not a major issue, we do need to consider bias and singularities. We should therefore calculate area A and perimeter length L using functions which take into account "roundedness". In MATLAB A = bwarea(.) L = bwarea(bwperim(.; 8)) Since I don't have MATLAB at hand, I made a small program in Mathematica: m = Binarize[Import["http://imgur.com/3Zs7m.png"]] (* Get Image *) k = Binarize[MorphologicalPerimeter[m]] (* Get Perimeter *) p = N[2 Count[ImageData[m], Except[1], 2]/ Count[ImageData[k], Except[0], 2]] (* Calculate *) The output is 36 Px ... Perimeter image follows HTH!
Its been a 3 years since the question was asked :) following the procedure of #nikie, here is a matlab implementation of the stroke width. clc; clear; close all; I = imread('3Zs7m.png'); X = im2bw(I,0.8); subplottight(2,2,1); imshow(X); Dist=bwdist(X); subplottight(2,2,2); imshow(Dist,[]); RegionMax=imregionalmax(Dist); [x, y] = find(RegionMax ~= 0); subplottight(2,2,3); imshow(RegionMax); List(1:size(x))=0; for i = 1:size(x) List(i)=Dist(x(i),y(i)); end fprintf('Stroke Width = %u \n',mean(List));
Assuming that the trace has constant thickness, is much longer than it is wide, is not too strongly curved and has no intersections / crossings, I suggest an edge detection algorithm which also determines the direction of the edge, then a rise/fall detector with some trigonometry and a minimization algorithm. This gives you the minimal thickness across a relatively straight part of the curve. I guess the error to be up to 25%. First use an edge detector that gives us the information where an edge is and which direction (in 45° or PI/4 steps) it has. This is done by filtering with 4 different 3x3 matrices (Example). Usually I'd say it's enough to scan the image horizontally, though you could also scan vertically or diagonally. Assuming line-by-line (horizontal) scanning, once we find an edge, we check if it's a rise (going from background to trace color) or a fall (to background). If the edge's direction is at a right angle to the direction of scanning, skip it. If you found one rise and one fall with the correct directions and without any disturbance in between, measure the distance from the rise to the fall. If the direction is diagonal, multiply by squareroot of 2. Store this measure together with the coordinate data. The algorithm must then search along an edge (can't find a web resource on that right now) for neighboring (by their coordinates) measurements. If there is a local minimum with a padding of maybe 4 to 5 size units to each side (a value to play with - larger: less information, smaller: more noise), this measure qualifies as a candidate. This is to ensure that the ends of the trail or a section bent too much are not taken into account. The minimum of that would be the measurement. Plausibility check: If the trace is not too tangled, there should be a lot of values in that area. Please comment if there are more questions. :-)
Here is an answer that works in any computer language without the need of special functions... Basic idea: Try to fit a circle into the black areas of the image. If you can, try with a bigger circle. Algorithm: set image background = 0 and trace = 1 initialize array result[] set minimalExpectedWidth set w = minimalExpectedWidth loop set counter = 0 create a matrix of zeros size w x w within a circle of diameter w in that matrix, put ones calculate area of the circle (= PI * w) loop through all pixels of the image optimization: if current pixel is of background color -> continue loop multiply the matrix with the image at each pixel (e.g. filtering the image with that matrix) (you can do this using the current x and y position and a double for loop from 0 to w) take the sum of the result of each multiplication if the sum equals the calculated circle's area, increment counter by one store in result[w - minimalExpectedWidth] increment w by one optimization: include algorithm from further down here while counter is greater zero Now the result array contains the number of matches for each tested width. Graph it to have a look at it. For a width of one this will be equal to the number of pixels of trace color. For a greater width value less circle areas will fit into the trace. The result array will thus steadily decrease until there is a sudden drop. This is because the filter matrix with the circular area of that width now only fits into intersections. Right before the drop is the width of your trace. If the width is not constant, the drop will not be that sudden. I don't have MATLAB here for testing and don't know for sure about a function to detect this sudden drop, but we do know that the decrease is continuous, so I'd take the maximum of the second derivative of the (zero-based) result array like this Algorithm: set maximum = 0 set widthFound = 0 set minimalExpectedWidth as above set prevvalue = result[0] set index = 1 set prevFirstDerivative = result[1] - prevvalue loop until index is greater result length firstDerivative = result[index] - prevvalue set secondDerivative = firstDerivative - prevFirstDerivative if secondDerivative > maximum or secondDerivative < maximum * -1 maximum = secondDerivative widthFound = index + minimalExpectedWidth prevFirstDerivative = firstDerivative prevvalue = result[index] increment index by one return widthFound Now widthFound is the trace width for which (in relation to width + 1) many more matches were found. I know that this is in part covered in some of the other answers, but my description is pretty much straightforward and you don't have to have learned image processing to do it.
I have interesting solution: Do edge detection, for edge pixels extraction. Do physical simulation - consider edge pixels as positively charged particles. Now put some number of free positively charged particles in the stroke area. Calculate electrical force equations for determining movement of these free particles. Simulate particles movement for some time until particles reach position equilibrium. (As they will repel from both stoke edges after some time they will stay in the middle line of stoke) Now stroke thickness/2 would be average distance from edge particle to nearest free particle.