Browse Source

initial commit

Keelan Lightfoot 1 year ago
commit
9470cb23e2
3 changed files with 940 additions and 0 deletions
  1. 3
    0
      README.md
  2. 12
    0
      go.mod
  3. 925
    0
      main.go

+ 3
- 0
README.md View File

@@ -0,0 +1,3 @@
1
+# circlefit
2
+
3
+circlefit analyzes a G-code file, then replaces blocks of successive G1 commands that represent arcs with the appropriate G2 or G3 arc commands.

+ 12
- 0
go.mod View File

@@ -0,0 +1,12 @@
1
+module code.beefchicken.com/circlefit
2
+
3
+go 1.20
4
+
5
+require (
6
+	bitbucket.wtfast.net/go/fit v1.0.1 // indirect
7
+	bitbucket.wtfast.net/go/geo v1.0.1 // indirect
8
+	github.com/256dpi/gcode v0.3.0 // indirect
9
+	gonum.org/v1/gonum v0.13.0 // indirect
10
+	google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
11
+	google.golang.org/protobuf v1.30.0 // indirect
12
+)

+ 925
- 0
main.go View File

@@ -0,0 +1,925 @@
1
+package main
2
+
3
+import (
4
+	"bitbucket.wtfast.net/go/fit"
5
+	"bufio"
6
+	"fmt"
7
+//	"gonum.org/v1/gonum/mat"
8
+	"log"
9
+	"math"
10
+	"os"
11
+	"regexp"
12
+	"strconv"
13
+	"strings"
14
+)
15
+
16
+type command struct {
17
+	verb string
18
+	noun float64
19
+}
20
+
21
+type line struct {
22
+	commands []command
23
+}
24
+
25
+func (l *line) Empty() bool {
26
+	return len(l.commands) == 0
27
+}
28
+
29
+func (l *line) String() string {
30
+	s := []string{}
31
+	for _, cmd := range l.commands {
32
+		_, frac := math.Modf(cmd.noun)
33
+		if frac == 0 {
34
+			s = append(s, fmt.Sprintf("%s%0.0f", cmd.verb, cmd.noun))
35
+		} else {
36
+			s = append(s, fmt.Sprintf("%s%f", cmd.verb, cmd.noun))
37
+		}
38
+	}
39
+	return strings.Join(s, " ")
40
+}
41
+
42
+func (l *line) get(verb string) (float64, bool) {
43
+	for _, c := range l.commands {
44
+		if c.verb == verb {
45
+			return c.noun, true
46
+		}
47
+	}
48
+	return 0, false
49
+}
50
+
51
+func (l *line) has(verb string, noun float64) bool {
52
+	fmt.Println(l)
53
+	for _, c := range l.commands {
54
+		if c.verb == verb && c.noun == noun {
55
+			return true
56
+		}
57
+	}
58
+	return false
59
+}
60
+
61
+type point struct {
62
+	x, y       float64
63
+	lineNumber int
64
+}
65
+
66
+func (p *point) IsZero() bool {
67
+	return p.x == 0 && p.y == 0
68
+}
69
+
70
+var scale = 1.0
71
+
72
+func main() {
73
+	file, err := os.Open("tp3.nc")
74
+	//file, err := os.Open("tp3.nc")
75
+	if err != nil {
76
+		log.Fatal(err)
77
+	}
78
+	defer file.Close()
79
+
80
+	r := regexp.MustCompile(`([A-Z])(\-?[0-9]+\.?[0-9]*)`)
81
+
82
+	//lines := make([]line, 0)
83
+	lines := make(map[int]*line)
84
+	scanner := bufio.NewScanner(file)
85
+	lineNumber := 0
86
+	for scanner.Scan() {
87
+		lineStr := scanner.Text()
88
+		l := &line{}
89
+		matches := r.FindAllStringSubmatch(lineStr, -1)
90
+		for _, cmd := range matches {
91
+			verb := cmd[1]
92
+			noun, err := strconv.ParseFloat(cmd[2], 64)
93
+			if err != nil {
94
+				log.Fatal(err)
95
+			}
96
+			l.commands = append(l.commands, command{verb: verb, noun: noun})
97
+		}
98
+		lines[lineNumber] = l
99
+		lineNumber++
100
+
101
+	}
102
+
103
+	log.Printf("Read %d lines.", len(lines))
104
+	if err := scanner.Err(); err != nil {
105
+		log.Fatal(err)
106
+	}
107
+
108
+	mode := ""
109
+	var x, y float64
110
+	var lastPoints []point
111
+	arcs := []*arc{}
112
+	
113
+	for lineNumber:=0;lineNumber<len(lines);lineNumber++ {
114
+		line := lines[lineNumber]
115
+		xyChanged, zChanged, modeChanged := false, false, false
116
+		//oldPt := point{x:x,y:y}
117
+		for _, cmd := range line.commands {
118
+			oldMode := mode
119
+			switch cmd.verb {
120
+			case "G":
121
+				switch cmd.noun {
122
+				case 0:
123
+					mode = "g0"
124
+				case 1:
125
+					mode = "g1"
126
+				case 2:
127
+					mode = "g2"
128
+				case 3:
129
+					mode = "g3"
130
+				case 20:
131
+					//fmt.Println("inches!")
132
+					scale = 25.4
133
+				case 21:
134
+					scale = 1
135
+				}
136
+			case "X":
137
+				x = cmd.noun
138
+				xyChanged = true
139
+			case "Y":
140
+				y = cmd.noun
141
+				xyChanged = true
142
+			case "Z":
143
+				//z = cmd.noun
144
+				zChanged = true
145
+			}
146
+			if mode != oldMode {
147
+				modeChanged = true
148
+			}
149
+		}
150
+
151
+		newPt := point{x: x, y: y, lineNumber: lineNumber}
152
+
153
+		if zChanged || modeChanged {
154
+			if len(lastPoints) > 0 {
155
+				arcs = append(arcs, checkPoints4(lastPoints)...)
156
+				lastPoints = make([]point, 0)
157
+			}
158
+		} else if mode == "g1" && xyChanged {
159
+			lastPoints = append(lastPoints, newPt)
160
+		}
161
+
162
+	}
163
+
164
+	if len(lastPoints) > 0 {
165
+		arcs = append(arcs, checkPoints4(lastPoints)...)
166
+	}
167
+	
168
+
169
+	// for _, arc := range arcs {
170
+// 		fmt.Printf("-----------------------------\n")
171
+// 		fmt.Printf("     Lines: %d-%d\n",arc.startLine, arc.endLine)
172
+// 		fmt.Printf("    Centre: %0.2f, %0.2f\n", arc.centre.x,arc.centre.y )
173
+// 		fmt.Printf("        SD: %0.4f\n", arc.bestSD )
174
+// 	}
175
+// 	
176
+	
177
+	
178
+	numLines := len(lines)
179
+	
180
+	for _, arc := range arcs {
181
+		// fmt.Println("start", arc.startPt)
182
+// 		fmt.Println("end", arc.endPt)
183
+		i := arc.centre.x - arc.startPt.x
184
+		j := arc.centre.y - arc.startPt.y
185
+		
186
+		// fmt.Printf("G0 X%0.4f Y%0.4f\n", arc.startPt.x, arc.startPt.y)
187
+		//
188
+		// 		fmt.Printf("G2 I%0.4f J%0.4f X%0.4f Y%0.4f\n", -i, -j, arc.endPt.x, arc.endPt.y)
189
+
190
+		// clear out old commands
191
+		for i := arc.startLine; i <= arc.endLine; i++ {
192
+			delete(lines, i)
193
+		}
194
+
195
+		//
196
+		lines[arc.startLine]=&line{commands:[]command{
197
+			{verb: "G", noun: 1},
198
+			{verb: "X", noun: arc.startPt.x},
199
+			{verb: "Y", noun: arc.startPt.y},
200
+		}}
201
+		dir := 3.0
202
+		if arc.clockwise {
203
+			dir = 2.0
204
+		}
205
+		lines[arc.startLine+1]=&line{commands:[]command{
206
+			{verb: "G", noun: dir},
207
+			{verb: "I", noun: i},
208
+			{verb: "J", noun: j},
209
+			{verb: "X", noun: arc.endPt.x},
210
+			{verb: "Y", noun: arc.endPt.y},
211
+		}}
212
+		lines[arc.startLine+2]=&line{commands:[]command{
213
+			{verb: "G", noun: 1},
214
+		}}
215
+	}
216
+
217
+	for i:=0;i<numLines;i++ {
218
+		if lines[i] != nil {
219
+			fmt.Println(lines[i].String())
220
+		}
221
+	}
222
+}
223
+
224
+// func  (p *cr)dist(n cr) float64 {
225
+// 	return math.Sqrt(math.Pow((p.r-n.r), 2))
226
+// }
227
+
228
+type arc struct {
229
+	startLine  int
230
+	endLine    int
231
+	startPt    point
232
+	secondPt   point
233
+	endPt      point
234
+	radius     float64
235
+	centre     point
236
+	clockwise  bool
237
+	startIndex int
238
+	endIndex   int
239
+	bestSD     float64
240
+}
241
+
242
+type curveInfo struct {
243
+	midpoint int
244
+	width int
245
+}
246
+
247
+func checkPoints4(points []point) []*arc {
248
+
249
+
250
+	numTests := 3
251
+
252
+	state := "searching"
253
+	
254
+	curveMidPoints := []curveInfo{}
255
+	
256
+	curveStart := 0
257
+	
258
+	for i := 0; i < len(points)-3*numTests; i++ {
259
+
260
+	
261
+		vX := make([]float64, numTests)
262
+		vY := make([]float64, numTests)
263
+		vR := make([]float64, numTests)
264
+		vD := make([]float64, numTests)
265
+		for j := 0; j < numTests; j++ {
266
+			vX[j], vY[j], vR[j] = circleFromThreePointsXYR(points[i+j], points[i+j+numTests], points[i+j+(2*numTests)])
267
+			vX[j] *= scale
268
+			vY[j] *= scale
269
+			vR[j] *= scale
270
+			vD[j] =  (vX[j]-points[i+j].x)*(points[i+j+1].y-points[i+j].y) - (vY[j]-points[i+j].y)*(points[i+j+1].x-points[i+j].x)
271
+		}
272
+		sd1 := math.Sqrt(math.Pow(stdDev(vX), 2) + math.Pow(stdDev(vY), 2))
273
+	//	sd2 := math.Sqrt(math.Pow(stdDev(vR), 2))
274
+		
275
+		//fmt.Printf("%d\t%f\n",points[i+9].lineNumber,sd1)
276
+		
277
+		if i == len(points)-3*numTests-1 && state == "curve" {
278
+			sd1 = 100 // kludge
279
+		} 
280
+		thresh := 2.5 // 2.5
281
+		switch state {
282
+		case "searching":
283
+			if sd1 >= thresh {
284
+				state = "deadzone"
285
+			} else {
286
+				state = "curve"
287
+				curveStart = i
288
+			}
289
+		case "deadzone":
290
+			if sd1 < thresh {
291
+				state = "curve"
292
+				curveStart = i
293
+			}
294
+		case "curve":
295
+			if sd1 >= thresh {
296
+				mid := curveStart+((i-curveStart)/2)
297
+				//fmt.Printf("curve start = %d end = %d mid = %d\n", points[curveStart+9].lineNumber, points[i+9].lineNumber, points[mid+9].lineNumber)
298
+				curveMidPoints=append(curveMidPoints,curveInfo{
299
+					midpoint: mid+(3*numTests)/2-1,
300
+					width:(i-curveStart)+(4*numTests),
301
+				})
302
+				state = "deadzone"
303
+			}
304
+			 
305
+		}
306
+	}
307
+	//fmt.Println(curveMidPoints)
308
+	spillover := 0
309
+	
310
+	arcs := []*arc{}
311
+
312
+
313
+
314
+	for _, midpoint := range curveMidPoints {
315
+		vX := make([]float64, 0)
316
+		vY := make([]float64, 0)
317
+		vR := make([]float64, 0)
318
+
319
+		
320
+		var bestStart, bestEnd int 
321
+		var bestCenter point
322
+		var bestRadius float64
323
+		thresh := .01
324
+		bestSD := 100.0
325
+		
326
+		
327
+	//	fmt.Println("----------")
328
+		for i:=midpoint.width/3;i<midpoint.width/2+spillover;i++ {
329
+			lowerPt  := midpoint.midpoint-i
330
+			if lowerPt < 0 {
331
+				lowerPt = 0
332
+			}
333
+			upperPt := midpoint.midpoint+i
334
+			if upperPt > len(points)-1  {
335
+				upperPt = len(points)-1
336
+			}
337
+			x,y,r := circleFromThreePointsXYR(points[lowerPt], points[midpoint.midpoint], points[upperPt])
338
+			vX = append(vX, x*scale)
339
+			vY = append(vY, y*scale)
340
+			vR = append(vR, r*scale)
341
+		//	fmt.Printf("lines: %d, %d %d = %0.2f,%0.2f,%0.2f\n", points[lowerPt].lineNumber, points[midpoint.midpoint].lineNumber, points[upperPt].lineNumber, x*scale, y*scale, r*scale)
342
+			sd1 := math.Sqrt(math.Pow(stdDev(vX), 2) + math.Pow(stdDev(vY), 2) + math.Pow(stdDev(vR), 2))
343
+			
344
+			//fmt.Printf("Averages: %d, %d %d = %0.4f\n", points[lowerPt].lineNumber, points[midpoint.midpoint].lineNumber, points[upperPt].lineNumber, sd1)
345
+			if sd1 > thresh {
346
+				break
347
+			}
348
+			bestStart, bestEnd = lowerPt, upperPt
349
+			bestCenter = point{x:avg(vX), y:avg(vY)}
350
+			bestRadius = avg(vR)
351
+			bestSD = sd1
352
+			
353
+		
354
+		
355
+		//	fmt.Printf("Line %d SD=%0.4f start=%d end=%d\n",points[midpoint.midpoint].lineNumber, sd1, points[lowerPt].lineNumber, points[upperPt].lineNumber)
356
+		
357
+		}
358
+		
359
+		
360
+		arc := arc{
361
+			startPt:   points[bestStart],
362
+			secondPt:  points[bestStart+2],
363
+			endPt:     points[bestEnd],
364
+			startLine: points[bestStart].lineNumber,
365
+			endLine:   points[bestEnd].lineNumber,
366
+			startIndex: bestStart,
367
+			endIndex:  bestEnd,
368
+			centre:    point{x:bestCenter.x/scale,y:bestCenter.y/scale},
369
+			radius:    bestRadius/scale,
370
+			bestSD:    bestSD,
371
+		}
372
+		d := (arc.centre.x-arc.startPt.x)*(arc.secondPt.y-arc.startPt.y) - (arc.centre.y-arc.startPt.y)*(arc.secondPt.x-arc.startPt.x)
373
+		arc.clockwise = d > 0
374
+			//	fmt.Println("QQQ", bestStart, bestEnd, d, arc.clockwise )
375
+
376
+		arcs = append(arcs, &arc)
377
+	}
378
+return arcs
379
+}
380
+
381
+
382
+func checkPoints3(points []point) []*arc {
383
+
384
+
385
+	numTests := 3
386
+	//foo(points)
387
+	
388
+	arcs := []*arc{}
389
+
390
+	offset := 0
391
+	searching := true
392
+	for searching {
393
+		if len(points)-offset < 3*numTests {
394
+			searching = false
395
+			break
396
+		}
397
+
398
+		arcStart := 0
399
+		var cX, cY, radius float64
400
+
401
+		// find start of a circle
402
+		
403
+		foundArc := false
404
+		for i := offset; i < len(points)-3*numTests; i++ {
405
+			vX := make([]float64, numTests)
406
+			vY := make([]float64, numTests)
407
+			vR := make([]float64, numTests)
408
+			for j := 0; j < numTests; j++ {
409
+				vX[j], vY[j], vR[j] = circleFromThreePointsXYR(points[i+j], points[i+j+numTests], points[i+j+(2*numTests)])
410
+				vX[j] *= scale
411
+				vY[j] *= scale
412
+				vR[j] *= scale
413
+			}
414
+
415
+			sd := math.Sqrt(math.Pow(stdDev(vX), 2) + math.Pow(stdDev(vY), 2) + math.Pow(stdDev(vR), 2))
416
+
417
+			if sd < 1 {
418
+				// looks like a circle
419
+				fmt.Printf("Found candidate arc starting at %d (line %d) (SD=%0.4f)\n", i, points[i].lineNumber+1, sd)
420
+				arcStart = i
421
+				cX = avg(vX)
422
+				cY = avg(vY)								
423
+				fmt.Println(vR)
424
+				radius = avg(vR)
425
+				foundArc = true
426
+				break
427
+			}
428
+		}
429
+		
430
+		if !foundArc {
431
+			searching = false
432
+			break
433
+		}
434
+
435
+		arcSize := numTests*3 // size of arc that came from the search above
436
+
437
+		
438
+		
439
+		var sd float64
440
+		
441
+		mX := []float64{}
442
+		mY := []float64{}
443
+		mR := []float64{}
444
+		
445
+		for i := arcStart; i < len(points)-(numTests*3); i++ {
446
+			x, y, r := circleFromThreePointsXYR(points[arcStart], points[arcStart+(arcSize)/2], points[arcStart+arcSize])
447
+		
448
+			x *= scale
449
+			y *= scale
450
+			r *= scale
451
+				
452
+			mX = append(mX, x)
453
+			mY = append(mY, y)
454
+			mR = append(mR, r)
455
+		
456
+		
457
+			sd = math.Sqrt(math.Pow(avg(mX)-x, 2) + math.Pow(avg(mY)-y, 2) + math.Pow(avg(mR)-r, 2))
458
+			sd = math.Sqrt(math.Pow(cX-x, 2) + math.Pow(cY-y, 2) + math.Pow(radius-r, 2))
459
+			fmt.Printf("testing %d-%d-%d (SD=%0.2f\n", arcStart, arcStart+(arcSize)/2, arcStart+arcSize, sd)
460
+			fmt.Printf("   center 1 X=%0.2f, Y=%0.2f\n", points[arcStart].x, points[arcStart].y)
461
+			fmt.Printf("   center 2 X=%0.2f, Y=%0.2f\n", points[arcStart+(arcSize)/2].x, points[arcStart+(arcSize)/2].y)
462
+			fmt.Printf("   center 3 X=%0.2f, Y=%0.2f\n", points[arcStart+arcSize].x, points[arcStart+arcSize].y)
463
+			
464
+			if sd >= .4 {
465
+				break
466
+			}
467
+			
468
+			arcSize++
469
+		}
470
+		
471
+		cX = avg(mX[:len(mX)-1])
472
+		cY = avg(mY[:len(mY)-1])
473
+		radius = avg(mR[:len(mR)-1])
474
+		
475
+		arcStart++
476
+		
477
+
478
+		if arcStart+arcSize > len(points) {
479
+			searching = false
480
+			break
481
+		}
482
+		
483
+		// 	if arcStart+arcSize == len(points) {
484
+// 				// end condition
485
+		if arcSize > 0 {
486
+			fmt.Printf("Found arc: %d to %d, refining (line %d to %d)...\n", arcStart, arcStart+arcSize-1, points[arcStart].lineNumber+1, points[arcStart+arcSize-1].lineNumber+1)
487
+			bestStart, bestEnd, _, bestRadius, bestCenter := refineArc(points, sd, arcStart, arcSize-1, cX, cY, radius)
488
+			arc := arc{
489
+				startPt:   points[bestStart-1],
490
+				secondPt:  points[bestStart],
491
+				endPt:     points[bestEnd],
492
+				startLine: points[bestStart].lineNumber,
493
+				endLine:   points[bestEnd].lineNumber,
494
+				centre:    point{x:bestCenter.x/scale,y:bestCenter.y/scale},
495
+				radius:    bestRadius/scale,
496
+			}
497
+			d := (arc.centre.x-arc.startPt.x)*(arc.secondPt.y-arc.startPt.y) - (arc.centre.y-arc.startPt.y)*(arc.secondPt.x-arc.startPt.x)
498
+			fmt.Println("d=",d)
499
+			arc.clockwise = d > 0
500
+			
501
+			arcs = append(arcs, &arc)
502
+			
503
+			offset = bestEnd 
504
+			fmt.Printf("Refined arc: %d to %d (Radius=%0.2f)\n", bestStart, bestEnd, bestRadius)
505
+			if arcStart+arcSize == len(points)-1 {
506
+				searching = false
507
+			}
508
+		}
509
+				
510
+// 			}
511
+
512
+		//}
513
+	}
514
+
515
+	for _, arc := range arcs {
516
+		d := (arc.centre.x-arc.startPt.x)*(arc.secondPt.y-arc.startPt.y) - (arc.centre.y-arc.startPt.y)*(arc.secondPt.x-arc.startPt.x)
517
+		arc.clockwise = d > 1
518
+	}
519
+
520
+	return arcs
521
+}
522
+
523
+
524
+// func foo(points []point) {
525
+// 		
526
+// 		
527
+// 	arcStart := 0
528
+// 	arcEnd  := 0
529
+// 	centre := point{}
530
+// 	
531
+// 	// find start of a circle
532
+// 	for ; arcStart < len(points)-9; arcStart++ {
533
+// 		
534
+// 		vX := make([]float64, 3)
535
+// 		vY := make([]float64, 3)
536
+// 		vR := make([]float64, 3)
537
+// 		for j := 0; j < 3; j++ {
538
+// 			vX[j], vY[j], vR[j] = circleFromThreePointsXYR(points[arcStart+j], points[arcStart+j+3], points[arcStart+j+6])
539
+// 		}
540
+// 
541
+// 		sd := math.Sqrt(math.Pow(stdDev(vX), 2) + math.Pow(stdDev(vY), 2) + math.Pow(stdDev(vR), 2))
542
+// 
543
+// 		if sd < 1 {
544
+// 			// looks like a circle
545
+// 			fmt.Printf("Found candidate arc starting at %d (SD=%0.4f)\n", arcStart, sd)
546
+// 			centre = point{x:avg(vX), y: avg(vY)}
547
+// 			arcEnd = arcStart+9
548
+// 		//	radius = avg(vR)
549
+// 			break
550
+// 		}
551
+// 	}
552
+// 		
553
+// 		
554
+// 		
555
+// 		
556
+// 		
557
+// 	for ;arcEnd<len(points)-9; arcEnd++ {
558
+// 	
559
+// 
560
+// 		centre2, chi2 := refineCenter(points[arcStart:arcEnd+1], centre)
561
+// 		fmt.Println("foo", chi2, centre, centre2, len(points[arcStart:arcEnd+1])	)
562
+// 		
563
+// 	}	
564
+// // 		
565
+// // 		arcEnd := arcStart+9
566
+// // 		for ; arcEnd < len(points)-9; arcEnd++ {
567
+// // 			centre, chi2 := refineCenter(points[arcStart:arcEnd+1], point{x:cX,y:cY})
568
+// // 			fmt.Println(centre, chi2)
569
+// // 			if chi2 > 1 {
570
+// // 				break
571
+// // 			}
572
+// // 		}
573
+// // 		arcEnd--
574
+// // 		
575
+// // 		centre, chi2 := refineCenter(points[arcStart:arcEnd+1], point{x:cX,y:cY})
576
+// // 
577
+// // 		fmt.Printf("Found arc: %d to %d (SD=%0.4f)\n", arcStart, arcEnd, chi2)
578
+// // 		arcs = append(arcs, &arc{
579
+// // 			startPt:   points[arcStart],
580
+// // 			secondPt:  points[arcStart+1],
581
+// // 			endPt:     points[arcEnd],
582
+// // 			startLine: points[arcStart].lineNumber,
583
+// // 			endLine:   points[arcEnd].lineNumber,
584
+// // 			centre:    centre,
585
+// // 			//radius:    bestRadius,
586
+// // 		})
587
+// // 		offset = arcEnd + 1		
588
+// }
589
+
590
+
591
+func refineArc(points []point, sd float64, arcStart, arcSize int, cX, cY, cR float64) (int, int, float64, float64, point) {
592
+
593
+
594
+	return arcStart, arcStart+arcSize-1, sd, cR, point{x:cX,y:cY}
595
+
596
+
597
+	var ax, ay, ar float64
598
+	n := 0
599
+	
600
+	for i:=arcStart+1; i<arcStart+(arcSize-1)/3; i++ {
601
+		//fmt.Println("offsets", i, i+(arcSize-1)/3, i+((arcSize-1)/3)*2)
602
+		x, y, r := circleFromThreePointsXYR(points[i], points[i+(arcSize-1)/3], points[i+((arcSize-1)/3)*2])
603
+	//	fmt.Println(x,y,r)
604
+		ax += x
605
+		ay += y
606
+		ar += r 
607
+		n++
608
+	}
609
+	
610
+	// fmt.Println("before", cX, cY, cR)
611
+// 	fmt.Println("average", ax/float64(n), ay/float64(n), ar/float64(n))
612
+// 	
613
+	return arcStart, arcStart+arcSize-1, sd, ar/float64(n), point{x:ax/float64(n),y:ay/float64(n)}
614
+	
615
+	//return arcStart, arcStart+arcSize-1, sd, cR, point{x:cX,y:cY}
616
+
617
+
618
+	bestSD := sd
619
+	bestEnd := 0
620
+	bestStart := 0
621
+	bestRadius := 0.0
622
+	bestCenter := point{}
623
+
624
+	refineDistance := 5
625
+
626
+	for startOffset := 0; startOffset < refineDistance; startOffset++ {
627
+		for endOffset := 0; endOffset < refineDistance; endOffset++ {
628
+
629
+			//fmt.Println("testing end:", arcStart+arcSize-q)
630
+
631
+			sd2, r2, c2, ok := checkArc(points[arcStart+startOffset], points[arcStart+(arcSize-1)/2], points[arcStart+(arcSize-1)-endOffset], cX, cY, cR)
632
+			if ok {
633
+				//	fmt.Println("GGG",sd2,r2,(arcSize-endOffset)-startOffset)
634
+			}
635
+			if ok && sd2 < bestSD {
636
+				bestSD = sd2
637
+				bestEnd = arcStart + arcSize - 1 - endOffset
638
+				bestStart = arcStart + startOffset
639
+				bestRadius = r2
640
+				bestCenter = c2
641
+			}
642
+		}
643
+	}
644
+	return bestStart, bestEnd, bestSD, bestRadius, bestCenter
645
+}
646
+
647
+func checkArc(p1, p2, p3 point, cX, cY, cR float64) (float64, float64, point, bool) {
648
+	if p1 == p2 || p1 == p3 || p2 == p3 {
649
+		return 0, 0, point{}, false
650
+	}
651
+	x2, y2, r2 := circleFromThreePointsXYR(p1, p2, p3)
652
+	sd2 := math.Sqrt(math.Pow(cX-x2, 2) + math.Pow(cY-y2, 2) + math.Pow(cR-r2, 2))
653
+	return sd2, r2, point{x: x2, y: y2}, true
654
+}
655
+
656
+// func checkPoints(points []point) {
657
+//
658
+// 	if len(points) < 3 {
659
+// 		return
660
+// 	}
661
+//
662
+// 	fmt.Println("LEN",len(points))
663
+//
664
+// 	crs := make([]cr,0)
665
+//
666
+//
667
+// 	for i:=0;i<len(points)-3;i++ {
668
+// 		crs = append(crs, circleFromThreePointsCr(points[i], points[i+1], points[i+2]))
669
+// 	}
670
+//
671
+// // 	for _, v := range crs {
672
+// // 		fmt.Print("source x: ", v.x)
673
+// // 		fmt.Print(" y: ", v.y)
674
+// // 		fmt.Println(" r: ", v.r)
675
+// // 	}
676
+// //
677
+//
678
+// //
679
+// // 	circlePoints := make([]cr,3) // start a new circle
680
+// // 	copy(circlePoints, crs[:3])
681
+// //
682
+// //
683
+// 	arcStart := 0
684
+// 	circleEnd := 3
685
+//
686
+// 	for i:=circleEnd;i<len(crs); {
687
+//
688
+// 		np := crs[i]
689
+//
690
+// 	// 	if circleEnd-arcStart < 3 {
691
+// // 			fmt.Printf("circle is too small (%d-%d)\n", arcStart, circleEnd)
692
+// // 			i++
693
+// // 			circleEnd = i
694
+// //
695
+// // 			continue
696
+// // 		}
697
+//
698
+// 		fmt.Println("====================")
699
+//
700
+// 		circlePoints := crs[arcStart:circleEnd]
701
+//
702
+// 		// for _, v := range circlePoints {
703
+// // 			fmt.Print("existing x: ", v.x)
704
+// // 			fmt.Print(" y: ", v.y)
705
+// // 			fmt.Println(" r: ", v.r)
706
+// // 		}
707
+//
708
+//
709
+// 		yVals := make([]float64, len(circlePoints))
710
+// 		for j :=0; j<len(circlePoints)-1;j++ {
711
+// 			yVals[j] = np.dist(circlePoints[j])
712
+// 		}
713
+// 		sd := stdDev(yVals)
714
+//
715
+// 		fmt.Print("testing x: ", np.x)
716
+// 		fmt.Print(" y: ", np.y)
717
+// 		fmt.Println(" r: ", np.r)
718
+//
719
+// 		fmt.Println("STDDEV:",sd)
720
+//
721
+//
722
+// 		if sd <  7 {
723
+// 			i++
724
+// 			circleEnd++
725
+// 			fmt.Printf("  growing to %d-%d\n", arcStart, circleEnd)
726
+// 			// fits the existing circle, so add it
727
+//
728
+// 		} else {
729
+//
730
+// 			dumpPoints (crs[arcStart:circleEnd])
731
+//
732
+// 			fmt.Printf("  discarding %d-%d\n", arcStart, circleEnd)
733
+// 			i++
734
+// 			arcStart=i
735
+// 			circleEnd=arcStart+3
736
+//
737
+// 			fmt.Printf("  starting new circle at %d-%d\n", arcStart, circleEnd)
738
+// 			// no fit, start a new circle
739
+// 			// crs[i+1].dump()
740
+// // 			crs[i].dump()
741
+// // 			crs[i-1].dump()
742
+// // 			crs[i-2].dump()
743
+//
744
+//
745
+// 		}
746
+//
747
+//
748
+// 	}
749
+//
750
+// 				dumpPoints (crs[arcStart:circleEnd])
751
+//
752
+// 	fmt.Printf("  discarding final %d-%d\n", arcStart, circleEnd)
753
+//
754
+// }
755
+//
756
+// func dumpPoints (circlePoints []cr) {
757
+// 	for _, v := range circlePoints {
758
+// 		fmt.Print("existing x: ", v.x)
759
+// 		fmt.Print(" y: ", v.y)
760
+// 		fmt.Println(" r: ", v.r)
761
+// 	}
762
+// }
763
+//
764
+// func (cr *cr) dump() {
765
+// 	fmt.Println("xxx OVER",cr.p1, cr.p2, cr.p3)
766
+// 	// fmt.Println("xxx OVER p1-p2",cr.p1.distance(&cr.p2))
767
+// // 	fmt.Println("xxx OVER p2-p3",cr.p2.distance(&cr.p3))
768
+// }
769
+//
770
+//
771
+//
772
+func refineCenter(points []point, centre point) (point, float64) {
773
+	
774
+//	fmt.Println("LEN",len(points), points)
775
+	
776
+	xVals := &fit.CartesianModel{}
777
+	
778
+	for _, pt := range points {
779
+		xVals.Points = append(xVals.Points, fit.Point{X:pt.x, Y:pt.y})
780
+	}
781
+	
782
+	yVals := []float64{}
783
+	for _, pt := range points {
784
+		yVals = append(yVals, pt.distance(&centre))
785
+	}
786
+	
787
+// 	//fmt.Println(yVals)
788
+	initialParams := make([]float64, 2)
789
+	for _, point := range xVals.Points {
790
+		initialParams[0] += point.X
791
+		initialParams[1] += point.Y
792
+	}
793
+	initialParams[0] /= float64(xVals.Len())
794
+	initialParams[1] /= float64(xVals.Len())
795
+// 	initialParams[0] = centre.x
796
+// 	initialParams[1] = centre.y
797
+	
798
+	
799
+	f, err := fit.NewFitter(xVals, yVals, nil, initialParams, fit.Options{fit.OptionDebug: false})
800
+	if err != nil {
801
+		panic(err)
802
+	}
803
+// 	stopReason := f.Run()
804
+// 	fmt.Println("foo Done! Stop reason: ", stopReason)
805
+	out := f.Params()
806
+
807
+	// fmt.Println(" x: ", out[0])
808
+// 	fmt.Println(" y: ", out[1])
809
+// 	fmt.Println(" cX: ", centre.x)
810
+// 	fmt.Println(" cY: ", centre.y)
811
+// 	fmt.Println(" chi2: ", f.Chi2())
812
+//	fmt.Println(" Rersiduals: ", f.Residuals())
813
+ 	return point{x:out[0],y:out[1]}, f.Chi2()
814
+
815
+}
816
+
817
+
818
+
819
+func stdDev(num []float64) float64 {
820
+	sum := 0.0
821
+	for _, n := range num {
822
+		sum += n
823
+	}
824
+	mean := sum / float64(len(num))
825
+	sd := 0.0
826
+	for _, n := range num {
827
+		sd += math.Pow(n-mean, 2)
828
+	}
829
+	sd = math.Sqrt(sd / float64(len(num)))
830
+	return sd
831
+}
832
+
833
+func avg(num []float64) float64 {
834
+	sum := 0.0
835
+	for _, n := range num {
836
+		sum += n
837
+	}
838
+	return sum / float64(len(num))
839
+}
840
+
841
+func circleFromThreePointsXYR(p1 point, p2 point, p3 point) (float64, float64, float64) {
842
+	A := p1.x*(p2.y-p3.y) - p1.y*(p2.x-p3.x) + p2.x*p3.y - p3.x*p2.y
843
+	B := (math.Pow(p1.x, 2)+math.Pow(p1.y, 2))*(p3.y-p2.y) + (math.Pow(p2.x, 2)+math.Pow(p2.y, 2))*(p1.y-p3.y) + (math.Pow(p3.x, 2)+math.Pow(p3.y, 2))*(p2.y-p1.y)
844
+	C := (math.Pow(p1.x, 2)+math.Pow(p1.y, 2))*(p2.x-p3.x) + (math.Pow(p2.x, 2)+math.Pow(p2.y, 2))*(p3.x-p1.x) + (math.Pow(p3.x, 2)+math.Pow(p3.y, 2))*(p1.x-p2.x)
845
+	x := -1 * B / (2.0 * A)
846
+	y := -1 * C / (2.0 * A)
847
+	r := math.Sqrt(math.Pow((x-p1.x), 2) + math.Pow((y-p1.y), 2))
848
+	return x, y, r
849
+}
850
+
851
+//
852
+// func circleFromThreePointsCr(p1 point, p2 point, p3 point) cr {
853
+// 	A := p1.x*(p2.y-p3.y) - p1.y*(p2.x-p3.x) + p2.x*p3.y - p3.x*p2.y
854
+// 	B := (math.Pow(p1.x, 2)+math.Pow(p1.y, 2))*(p3.y-p2.y) + (math.Pow(p2.x, 2)+math.Pow(p2.y, 2))*(p1.y-p3.y) + (math.Pow(p3.x, 2)+math.Pow(p3.y, 2))*(p2.y-p1.y)
855
+// 	C := (math.Pow(p1.x, 2)+math.Pow(p1.y, 2))*(p2.x-p3.x) + (math.Pow(p2.x, 2)+math.Pow(p2.y, 2))*(p3.x-p1.x) + (math.Pow(p3.x, 2)+math.Pow(p3.y, 2))*(p1.x-p2.x)
856
+// 	x := -1 * B / (2.0 * A)
857
+// 	y := -1 * C / (2.0 * A)
858
+// 	r := math.Sqrt(math.Pow((x-p1.x), 2) + math.Pow((y-p1.y), 2))
859
+// 	return cr{p1: p1, p2:p2, p3:p3, x: x, y: y, r: r}
860
+// }
861
+//
862
+//
863
+//
864
+//
865
+// func circleFromThreePoints(p1 point, p2 point, p3 point) (point, float64) {
866
+// 	A := p1.x*(p2.y-p3.y) - p1.y*(p2.x-p3.x) + p2.x*p3.y - p3.x*p2.y
867
+// 	B := (math.Pow(p1.x, 2)+math.Pow(p1.y, 2))*(p3.y-p2.y) + (math.Pow(p2.x, 2)+math.Pow(p2.y, 2))*(p1.y-p3.y) + (math.Pow(p3.x, 2)+math.Pow(p3.y, 2))*(p2.y-p1.y)
868
+// 	C := (math.Pow(p1.x, 2)+math.Pow(p1.y, 2))*(p2.x-p3.x) + (math.Pow(p2.x, 2)+math.Pow(p2.y, 2))*(p3.x-p1.x) + (math.Pow(p3.x, 2)+math.Pow(p3.y, 2))*(p1.x-p2.x)
869
+// 	x := -1 * B / (2.0 * A)
870
+// 	y := -1 * C / (2.0 * A)
871
+// 	r := math.Sqrt(math.Pow((x-p1.x), 2) + math.Pow((y-p1.y), 2))
872
+// 	return point{x: x, y: y}, r
873
+// }
874
+//
875
+//
876
+//
877
+//
878
+
879
+func (p *point) distance(p2 *point) float64 {
880
+	return math.Sqrt(math.Pow(p.x-p2.x, 2) + math.Pow(p.y-p2.y, 2))
881
+}
882
+
883
+func (p *point) bearing(p2 *point) float64 {
884
+	return math.Atan2(p.y-p2.y, p.x-p2.x)
885
+}
886
+
887
+// type cr struct {
888
+// 	p1, p2, p3 point
889
+// 	x, y, r    float64
890
+// }
891
+// 
892
+// var _ fit.Model = &CircleModel{}
893
+// 
894
+// type CircleModel struct {
895
+// 	points []cr
896
+// }
897
+// 
898
+// func NewCircleModel() *CircleModel {
899
+// 	return &CircleModel{
900
+// 		points: make([]cr, 0),
901
+// 	}
902
+// }
903
+// func (c *CircleModel) Len() int {
904
+// 	return len(c.points)
905
+// }
906
+// 
907
+// func (c *CircleModel) Model(i int, params *mat.VecDense) float64 {
908
+// 	p := c.points[i]
909
+// 	return math.Sqrt(math.Pow((p.x-params.AtVec(0)), 2) + math.Pow((p.y-params.AtVec(1)), 2) + math.Pow((p.r-params.AtVec(2)), 2))
910
+// }
911
+// 
912
+// func (c *CircleModel) InitialParams() []float64 {
913
+// 	initialParams := make([]float64, 3)
914
+// 	if len(c.points) > 0 {
915
+// 		for _, p := range c.points {
916
+// 			initialParams[0] += p.x
917
+// 			initialParams[1] += p.y
918
+// 			initialParams[2] += p.r
919
+// 		}
920
+// 		initialParams[0] /= float64(len(c.points))
921
+// 		initialParams[1] /= float64(len(c.points))
922
+// 		initialParams[2] /= float64(len(c.points))
923
+// 	}
924
+// 	return initialParams
925
+// }