6

Imagine I have saved a hand drawn curve as a jpg file. I want to be able to enclose it in a box and read off coordinates of the points that lie on the curve. One way is to click on a point and get the coordinates. But I want to automate this.

Quasar Supernova
  • 1,678
  • 8
  • 13

1 Answers1

10

img curve

(* get the image *)
img = Import["https://i.stack.imgur.com/FFioq.png"]

(* get the pixel positions on the line *) binz = Thinning@ColorNegate@Binarize@CurvatureFlowFilter[img, 2]; positions = PixelValuePositions[binz, 1];

(* create a graph structure connecting pixel positions in a chain *) gr = SimpleGraph[ NearestNeighborGraph[positions, DistanceFunction -> ChessboardDistance, DirectedEdges -> False]];

(* make sure we discard any small pixels that are not on the main curve *) largestgr = First@MaximalBy[ConnectedGraphComponents[gr], VertexCount];

(* the start/end pixels are the ones with lowest degree *) {start, end} = VertexList[largestgr][[ Flatten@Position[VertexDegree[largestgr], 1] ]];

(* find path from start to end and plot *) path = First[FindPath[largestgr, start, end]]; ListLinePlot[path, AspectRatio -> 1]

curve plot


This is a bit shorter if you use DeleteSmallComponents instead to remove any stray pixels not part of the curve:

bwimg = DeleteSmallComponents@Thinning@ColorNegate@Binarize@img;
gr = NearestNeighborGraph[PixelValuePositions[bwimg, 1], 
  DistanceFunction -> ChessboardDistance];
{start, end} = Select[VertexList@gr, VertexDegree[gr, #] == 1 &];
path = First@FindPath[gr, start, end];

HighlightImage[img, path] ListLinePlot[path, AspectRatio -> 1]


If you need arbitrary paths that may contain loops and intersections, then maybe you should look at FindCurvePath instead:

img = Rasterize[Text["A"], RasterSize -> 64];
bwimg = DeleteSmallComponents@Thinning@ColorNegate@Binarize@img;
positions = PixelValuePositions[bwimg, 1];
paths = FindCurvePath[positions];
ListLinePlot[positions[[#]] & /@ paths]

enter image description here

Also, ListCurvePathPlot[positions] would work too.

flinty
  • 25,147
  • 2
  • 20
  • 86
  • Even thought I am not supposed to say "Thanks" I will. A huge thanks to flinty. – Quasar Supernova Dec 02 '20 at 15:09
  • @QuasarSupernova Cheers! I will continue to elaborate on this answer if I find a shorter or more robust way to do it. – flinty Dec 02 '20 at 15:11
  • However it does not seem to work on the letter R and many others. I get this error : Set::shape: Lists {start,end} and {{244,184},{271,181},{250,113},{267,121},{270,109},{242,86},{282,137},{297,84},{294,83},{282,95}} are not the same shape. – Quasar Supernova Dec 02 '20 at 16:09
  • Letters like R, X, A etc all have multiple endpoints, so there is no way to sort them (except maybe topologically) and therefore no way to list plot them as a single cuve. If you just need the points then PixelValuePositions[#, 1]&@DeleteSmallComponents@Thinning@ColorNegate@Binarize@img will work, and so will everything before the line {start, end} ... - You should provide examples of the curves you're working with and what you expect from them in your question – flinty Dec 02 '20 at 16:22
  • Just points will do. I want to use this to parametrise the curve and to classify them topologically. – Quasar Supernova Dec 03 '20 at 02:49
  • @QuasarSupernova Then you have everything you need with this line: positions = PixelValuePositions[binz, 1]; With the graph, you could use FindCycle[gr, Infinity, All] for the topology. – flinty Dec 03 '20 at 15:51
  • See this link. I am getting errors for anything more complicated than a line segment. https://drive.google.com/file/d/1Hu9XA7ZPCJhnSix1nR-wr_DHpUgPiUbq/view?usp=sharing – Quasar Supernova Dec 07 '20 at 16:23
  • Even the Star of David which has no end points does not work, gives same error as R,A e.t.c. – Quasar Supernova Dec 07 '20 at 16:26
  • It's not a curve or a line. There is no 'start' or 'end' on the shape of the letter 'A' so I don't know why you expect a path to exist. You cannot ListLinePlot shapes like that in general unless you have an ordering to the graph vertices. At best you can just plot the points alone. – flinty Dec 07 '20 at 16:26
  • Even a triangle gives the same error. – Quasar Supernova Dec 07 '20 at 16:33
  • I can easily order points on an triangle (or a circle) – Quasar Supernova Dec 07 '20 at 16:39
  • @QuasarSupernova have a look at my edit. FindCurvePath might be better suited to your application than the graph method. Maybe consider adding examples of the curves you're interested in to your question as I told you in an earlier comment. – flinty Dec 07 '20 at 16:43