I have some 2D data that once plotted looks like the following
dat=Import["data.mat","Data"];
picList=ListPlot[,Axes-> None,Frame-> None,AspectRatio-> 1];
e=ColorConvert[Rasterize[picList],"Grayscale"];
Show[picList,Frame-> True]
Now I wrote the following image processing function to detect the outer circular region where the point density starts to decrease. I want to fit that region with a certain parametric curve. For example a circle with unknown radius $r$. I want to achieve this through an automatic code without any manual input (one can use Manipulate
with ListPlot
for instance).
I have included extensive commenting for better understanding my approach for a solution.
The Function:
FinalFit[res_, TotalVariationFilter$Regularization_: .2,
TotalVariationFilter$MaxIterations_: 10,
GradientFilter$PixelRadius_: 1, BoundingBoxPoints_: 50,
ExtraImage_: False] :=
Block[{picList, e, addedIm, Finres, dimX, dimY, imRange, plotrange,
datim, res1, intData, finder, fun, xvals, yvals, boxdat, circledat,
arrow, Pic, r},
(*------ Take the 2D data points and plot it *)
picList =
ListPlot[res, Axes -> None, Frame -> None, AspectRatio -> 1];
(*------
Create a raster image from the plot and convert it to Grayscale *)
e = ColorConvert[Rasterize[picList], "Grayscale"];
(*------ Reduce the noise in the outward part of the cluster *)
addedIm =
ImageAdd[
TotalVariationFilter[Binarize[e],
TotalVariationFilter$Regularization,
MaxIterations -> TotalVariationFilter$MaxIterations], e] //
Binarize;
(*------ Get the ridges of gradient lines in the binarize image *)
Finres = (GradientFilter[addedIm, GradientFilter$PixelRadius,
"NonMaxSuppression" -> True] // Binarize);
(*------ Dimension of the input raster "e" *)
{dimX, dimY} = N[ImageDimensions@e];
imRange = {{1., dimX}, {1., dimY}};
(*------ Extract the 2D data range *)
plotrange = AbsoluteOptions[picList, PlotRange][[1]][[2]];
(*------ Get the image data from last image with gradient lines *)
datim = Reverse@ImageData[Finres];
res1 = Table[{i, j, N[datim[[i, j]]]}, {i, 1, dimX}, {j, 1, dimY}];
(*------ Rescale the contour/
gradient line according to the 2D data range "Bad Coding!" *)
intData =
Join[Transpose@
RescalingTransform[imRange, plotrange][
Flatten[Table[{i, j}, {i, 1, dimX}, {j, 1, dimY}], 1]], {Last@
Transpose@Flatten[res1, 1]}] // Transpose;
(*------
Form a Nearest function with the above obtained points constituting \
the target contour *)
(*------ Select the points on the contour as their third co-
ordinate correspond to the pixel value of "1." *)
finder = Nearest[Take[Select[intData, #[[3]] == 1. &], All, 2]];
(*------
On the contour line image discretize the boundary with uniformly \
seperated points *)
(*------ User specifies the number of total bounding points *)
(*------ Start of "Bad Codeing!" *)
xvals =
Range[#[[1]], #[[2]], (#[[2]] - #[[1]])/(BoundingBoxPoints/
4)] & /@ {plotrange[[1]]} // Flatten;
yvals =
Range[#[[1]], #[[2]], (#[[2]] - #[[1]])/(BoundingBoxPoints/
4)] & /@ {plotrange[[2]]} // Flatten;
boxdat =
DeleteDuplicates@(Join[
Join[Map[{#, yvals // First} &, xvals],
Map[{#, yvals // Last} &, xvals]],
Join[Map[{xvals // First, #} &, yvals],
Map[{xvals // Last, #} &, yvals]]]);
(*------ End of "Bad Codeing!" *)
(*------ Find the points on the quasi-
circular contour that is Nearest to the discrete boundary *)
circledat = Map[First@finder[#] &, boxdat];
(*------ Start Visualization *)
arrow =
Show[Flatten@
MapThread[
Graphics[{Thickness[.002],
Red, {Arrowheads[.02], Arrow[{#1, #2}]}}] &, {boxdat,
circledat}], Frame -> True];
Pic = Show[{arrow,
ListPlot[{boxdat, circledat[[FindCurvePath[circledat][[1]]]],
circledat}, AspectRatio -> 1, PlotStyle -> Thick,
Frame -> True, Axes -> None, Joined -> {False, True, False}]}];
If[ExtraImage == True, Print[Pic]];
(*------ End Visualization *)
(*------ Find the possible radius *)
r = Median[EuclideanDistance[#, {0., 0.}] & /@ circledat];
{r, Rasterize[
Show[Show[picList, Frame -> True],
ParametricPlot[{r Cos[theta], r Sin[theta]}, {theta, 0., 2 Pi},
Axes -> None, PlotStyle -> {{Thick, Red}}]], RasterSize -> 800]}
]
The Output:
Now If we call the above function with following arguments
FinalFit[dat, .85, 100, 21, 200, True]
we get the following results
- In the first pic we can see from the image boundary we detect the points on the outer contour.
- The second picture draws the circle with a radius that is the median of the norms of the vectors connecting the origin with points forming the target contour.
Proof of Robustness:
Questiion:
So far my problem is solved but I am sure one can do this probably more efficiently and elegantly. Any idea for betterment will be helpful. On a sleepless night thought to write a elaborate post....Hope I don't overrun your patience for reading it..
BR