Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

plotter: consider modifying Contour to produce reproducible plots #622

Open
sbinet opened this issue May 25, 2020 · 6 comments
Open

plotter: consider modifying Contour to produce reproducible plots #622

sbinet opened this issue May 25, 2020 · 6 comments

Comments

@sbinet
Copy link
Member

sbinet commented May 25, 2020

as noted in #620, plots produced by Contour are not reproducible.
they look visually the same but the vg operations underneath are different.
this leads to plots not being exactly the same at the binary level (except for pdf. probably a feature of the backend)

the "naive" draw strategy does generate reproducible plots.

@kortschak
Copy link
Member

What would be really helpful for this would be a minimal contour plot that is not reproducible. The current failing case has very complex contours, making investigation very difficult.

@kortschak
Copy link
Member

I have looked into what seems to be causing this and it is extremely subtle; the differences are only visible with very high gain on the pixel intensity for the image diff. When you do that, you can see that the difference appears at cross-over points. For example this image shows in blue the original golden output and overlayed with high gain is a yellow (white - blue) pixel.

Screenshot from 2020-05-26 10-33-51

This example is obtained from the example contour plot with some mods to reduce complexity, it is complex enough to reliably fail, and not too complex to not understand.

func ExampleContour() {
	rnd := rand.New(rand.NewSource(1234))

	const stddev = 2
	data := make([]float64, 6400)
	for i := range data {
		r := float64(i/80) - 40
		c := float64(i%80) - 40

		data[i] = rnd.NormFloat64()*stddev + math.Hypot(r, c)
	}

	var (
		grid   = unitGrid{mat.NewDense(80, 80, data)}
		levels = []float64{2}

		c = plotter.NewContour(
			grid,
			levels,
			palette.Rainbow(10, palette.Blue, palette.Red, 1, 1, 1),
		)
	)

	p, err := plot.New()
	if err != nil {
		log.Fatalf("could not create plot: %+v", err)
	}

	p.Title.Text = "Contour"
	p.X.Padding = 0
	p.Y.Padding = 0
	p.X.Max = 79.5
	p.Y.Max = 79.5

	p.Add(c)

	err = p.Save(10*vg.Centimeter, 10*vg.Centimeter, "testdata/contour.png")
	if err != nil {
		log.Fatalf("could not save plot: %+v", err)
	}
}

@kortschak
Copy link
Member

My suspicion is that the path is circularly permuted between tests and that the start and end point of paths get rendered by the back end differently to intermediate steps in a path.

@kortschak
Copy link
Member

The alternative, and now probably more likely is that the implementation of the algorithm for making contours is a little brittle to floating point artifacts. Fixing this would not be trivial; if it's true, the issue is that contour connection is resolved by map look-ups, so if two end points are not identical when they should be, no connection will be made.

@sbinet
Copy link
Member Author

sbinet commented May 26, 2020

ok. I better understand the issue now, thanks.
(I must say that -because of my past days in C++-land- I always cringe a bit when noticing a map[float64]T)

quite interesting detective work :)

@kortschak
Copy link
Member

As you can see through the code there are trepidatious comments about these kinds of effects. It's not clear to me that there is intrinsically a better way of doing it (using some non-zero threshold and having a different kind of container - which we have now in spatial - just means that we open ourselves up for false positives instead of these potentially (yet to be proven) false negatives).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants