diff --git a/scripts/run_experiments.jl b/scripts/run_experiments.jl
index 0152e0451923df88494b169b90354513941dd1ad..03dbe37f50cdc0c84ab2555d892ba95e529a7cc0 100644
--- a/scripts/run_experiments.jl
+++ b/scripts/run_experiments.jl
@@ -32,6 +32,13 @@ loadimg(x) = Float64.(FileIO.load(x))
 saveimg(io, x::Array{<:Gray}) = FileIO.save(io, grayclamp.(x))
 saveimg(io, x) = FileIO.save(io, x)
 
+function saveimgdiff(io, f0, f1)
+    n = 2^8 # colors
+    cmap = colormap("RdBu", n)
+    k(v0, v1) = cmap[clamp(ceil(Int, n * ((v1 - v0) / 2 + 0.5)), 1, n)]
+    saveimg(io, k.(f0, f1))
+end
+
 # convert image to/from image coordinate system
 from_img(arr) = permutedims(reverse(arr; dims = 1))
 to_img(arr) = permutedims(reverse(arr; dims = 2))
@@ -1191,6 +1198,7 @@ function optflow(ctx)
     u_sampled = sample(st.u)
     saveimg(joinpath(ctx.outdir, "f0.png"), to_img(imgf0))
     saveimg(joinpath(ctx.outdir, "f1.png"), to_img(imgf1))
+    saveimgdiff(joinpath(ctx.outdir, "g.png"), to_img(imgf0), to_img(imgf1))
     saveimg(joinpath(ctx.outdir, "output.png"), colorflow(to_img(u_sampled); ctx.params.maxflow))
     imgfw = warp_backwards(imgf1, u_sampled)
     saveimg(joinpath(ctx.outdir, "fw.png"), to_img(imgfw))
diff --git a/src/image.jl b/src/image.jl
index e3629c4208f1d7824c02ec963b9feaa975009ca4..eb4486707118feae875d74ce7556d731505a8df4 100644
--- a/src/image.jl
+++ b/src/image.jl
@@ -442,3 +442,15 @@ function project_l2_pixel!(u::FeFunction, img)
     u.data .= A \ b
     return u
 end
+
+function save_csv(path, img::AbstractArray{<:Any,2})
+    df = DataFrame()
+    for ci in CartesianIndices(img)
+        push!(df, (
+            x = ci[1],
+            y = ci[2],
+            f_xy = img[ci],
+        ))
+    end
+    CSV.write(path, df)
+end