SciVis 2017 Project 4: "vectr"
Assigned Mon Feb 20; Due Wed March 2 at 11:59pm
For this project, you will implement 2-D vector field convolution, streamline integration, and Line Integral Convolution (LIC). This is a shorter and simpler project than others; the reference implementation has only about 250 lines inserted into the give code. Also, detailed instructions are included in lic.c for doing the LIC.
Logistics
Your CNETID-scivis-2017 directory should now be populated with a p4vectr directory which contains all the files for this project. All other aspects of logistics are similar to previous projects: make in p4vectr should make vectr executable, which you will compare with reference executable rvectr. Work individually or in pairs.One small novelty (based on feedback at the previous lab) is that if you want to compile your code without the -O2 optimization, you can set something else:
Compiling without optimization may produce an executable that is easier to debug.export DASHO=-O0 make clean; makeLike in Project 3, there is a vcrVerbose global variable that you should use to control the printing of any debugging messages. vcrVerbose is set from the value of environment variable VCR_VERBOSE by the vectr as soon as it starts. Grading will be done with VCR_VERBOSE unset.
What to do
Run "./rvectr" to review the commands available, and run "./rvectr about" to see what needs to be implemented. The convolution code (for vcrConvoEval) will be nearly identical to what was done for Project 2: we again need 2-D convolution, but now in a vector field rather than a scalar image. Reading through and understanding the header file vcr.h is essential. The vcrMath.h macro collection will be familiar.The handling of these lines
is the same as in previous assignments. There are detailed notes in the comments preceeding these blocks detailing what has to be done./* v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v begin student code */ /* ^'^'^'^'^'^'^'^'^'^'^'^'^'^'^'^'^'^'^ end student code */33710 students have two additional things to implement:
- Runge-Kutta Fourth-order integration along streamlines: the "-iintg rk4" option for "vectr sline" and "vectr lic", which determines the value of the intg variable passed to vcrSlineTrace() and vcrLIC, respectively.
- In vcrLIC, if the rndLinterp argument is nonzero, do bilinear interpolation into the given noise texture, instead of nearest-neighbor interplation.
Example vectr/rvectr commands to try
The commands here use "./rrendr"; you should make sure that you get the exactly same results by running "./rendr". To an unfortunate extent the commands are intended to be executed in the order shown: later commands assume the presence of files created by running earlier commands.
- Always make sure your code builds cleanly with "make"
- Make some little vector datasets, on two different sampling grids (the *0.nrrd grids are isotropic axis-aligned, the *1.nrrd grids are not), and then learn about their domain geometry,
The given code for rvectr sdg allows you to create datasets according to the first two terms of a Taylor expansion around the origin, with info given to the "-p" flag: first a constant term (the first two values to -p), and then the Jacobian, in order dVx/dx dVx/dy dVy/dx dVy/dy. So right0.nrrd is just the constant vector (2,0), and in out0.nrrd the Jacobian is the identity, so vector value is the same as the world-space position. rotate0.nrrd is pure rotation, while shearA0.nrrd and shearB0.nrrd are the two possible orientations of pure shear../rvectr sdg -w 0 -p 2 0 0 0 0 0 -o right0.nrrd ./rvectr sdg -w 0 -p 0 0 1 0 0 1 -o out0.nrrd ./rvectr sdg -w 0 -p 0 0 1 0 0 -1 -o shearA0.nrrd ./rvectr sdg -w 0 -p 0 0 0 1 1 0 -o shearB0.nrrd ./rvectr sdg -w 0 -p 0 0 0 -1 1 0 -o rotate0.nrrd ./rvectr sdg -w 0 -p 2 0 0 0 0 0 -sz 40 19 -l 1 2 -r 30 -s -0.2 -o right1.nrrd ./rvectr sdg -w 0 -p 0 0 1 0 0 1 -sz 40 19 -l 1 2 -r 30 -s -0.2 -o out1.nrrd ./rvectr sdg -w 0 -p 0 0 1 0 0 -1 -sz 40 19 -l 1 2 -r 30 -s -0.2 -o shearA1.nrrd ./rvectr sdg -w 0 -p 0 0 0 1 1 0 -sz 40 19 -l 1 2 -r 30 -s -0.2 -o shearB1.nrrd ./rvectr sdg -w 0 -p 0 0 0 -1 1 0 -sz 40 19 -l 1 2 -r 30 -s -0.2 -o rotate1.nrrd ./rvectr info rotate0.nrrd rotate1.nrrd- Test your vector field convolution from vcrConvoEval (try other kernels! they are listed with vectr klist), as well as vcrPropCalc:
You should be able to make sense of these outputs given the how the fields were defined.for D in {right,out,shearA,shearB,rotate}{0,1}.nrrd; do echo "============" ./rvectr ceval -i $D -k bspln2 -w 0.0 0.0 -cp echo "" ./rvectr ceval -i $D -k bspln2 -w 0.111 0.222 -cp done- Next, you need to be able to trace streamlines by either Euler (the default), midpoint-method, or RK4 (for 33710 students) integration.
(The -intg rk4 example is only for 33710 students). In the last example, note that the streamline is approximating a circle; it starts and ends at roughly the same place. You can also save the coordinates out to a file if you want to inspect them by some other means:./rvectr sline -i right0.nrrd -k tent -s -0.4 0 -h 0.1 ./rvectr sline -i right0.nrrd -k tent -s -0.4 0 -h 0.1 -n ./rvectr sline -i out0.nrrd -k tent -s 0.2 0.3 -h 0.1 ./rvectr sline -i out0.nrrd -k tent -s 0.2 0.3 -h 0.1 -n ./rvectr sline -i rotate0.nrrd -k tent -l 10 -s 0.5 0 -h 0.3 ./rvectr sline -i rotate0.nrrd -k tent -l 10 -s 0.5 0 -h 0.3 -intg rk4 ./rvectr sline -i rotate0.nrrd -k tent -l 10 -s 0.5 0 -h 0.3 -intg rk2The "unu project -m l2" finds the length of each of the coordinate 2-vectors, and "unu minmax" looks at the range of those lengths. We can see that RK2 integration is better than Euler at creating a circular streamline, because the range of distances to the origin with RK2 is smaller. RK4 is even better. We can also show this visually:./rvectr sline -i rotate0.nrrd -k tent -l 50 -s 0.5 0 -h 0.2 -intg euler -o circ-eul.txt ./rvectr sline -i rotate0.nrrd -k tent -l 50 -s 0.5 0 -h 0.2 -intg rk2 -o circ-rk2.txt ./rvectr sline -i rotate0.nrrd -k tent -l 50 -s 0.5 0 -h 0.2 -intg rk4 -o circ-rk4.txt unu project -i circ-eul.txt -a 0 -m l2 | unu minmax - unu project -i circ-rk2.txt -a 0 -m l2 | unu minmax - unu project -i circ-rk4.txt -a 0 -m l2 | unu minmax -In the euler-vs-rk2.eps output, the RK2 integration cleanly traces (multiple times) a single circle, while the Euler integration spirals outwards, in both the forward (shown with the arrow head) and backwards directions.unu slice -i rotate0.nrrd -a 0 -p 0 | unu 1op exists | unu quantize -b 8 -min 0 -max 1 | unu pad -min -1 -1 -max M+1 M+1 -b pad -v 128 -o background.png ./vectr eps -i background.png -s circ-{eul,rk2}.txt -th 0.01 -o euler-vs-rk2.eps open euler-vs-rk2.eps rm -f background.png- With streamlines working, you can do LIC. First you need a noise texture:
As created above, the noise texture noise.nrrd fits inside the original vector field (look at where the corners are). You can play with the "-s" options to "unu resample" to upsample to a higher resolution noise texture. To test LIC when the vector field is a constant vector, as it is in right0.nrrdunu slice -i out0.nrrd -a 0 -p 0 | # get scalar image with same orientation unu crop -min 3 3 -max M-3 M-3 | # crop to get away from edges of vector field unu resample -s x4 x4 | # noise is 4x resolution of vector data unu 1op nrand -s 42 | # generate noise unu resample -s x1 x1 -k hann:1.6,7 | # blur a bit (to help anti-alias LIC) unu flip -a 1 -o noise.nrrd # so standard raster display looks correct ./vectr info noise.nrrd unu quantize -b 8 -i noise.nrrd -min 0.1% -max 0.1% -o noise.png open noise.pngThe "bad" one is bad because the integration stepsize is too large: note the horizontally repeating texture pattern: that is the visual indication of the too-large step size. That texture is less visible in the "good", so we can better see the consistent directionality of the underlying vector field. In either case, though, the total length (in world-space) of the streamline is about the same (0.03*20 == 0.003*200). In the third example (for 33710 students), with the "-rlin" option generating right-lic-rlin.png, the texture is further reduced by using linear interpolation instead of nearest-neighbor to sample the noise texture.LPARM="-i right0.nrrd -r noise.nrrd -k tent " QPARM="-b 8 -min 0.1% -max 0.1%" ./rvectr lic $LPARM -h 0.03 -n -l 15 -o - | unu quantize $QPARM -o right-lic-bad.png ./rvectr lic $LPARM -h 0.003 -n -l 150 -o - | unu quantize $QPARM -o right-lic-good.png ./rvectr lic $LPARM -h 0.003 -n -l 150 -rlin -o - | unu quantize $QPARM -o right-lic-rlin.png open right-lic-{bad,good,rlin}.png- For 33710 students: For vectr lic -rlin, we want a way to check that the texture sampling is really by linear interpolation (as if by the "tent" kernel) rather than nearest neighbor (as if by the "box"). Note that you don't need to (and shoudl not) use the vcrCtx to do this convolution: your code should directly compute bilinear interpolation with the correct weighting of the four noise samples at the corners of the square within which the streamline vertex falls. To test this, we contrive a "noise" texture lines.nrrd that will highlight the difference between nearest neighbor and linear: alternating values of 0 and 1.
And then compute the LIC results:unu slice -i out0.nrrd -a 0 -p 0 | # scalar image w/ same orientation unu crop -min 3 3 -max M-3 M-3 | # some cropping unu resample -s x2 x2 | # twice resolution unu 2op x - 0 | # zero out values unu flip -a 1 -o grating.nrrd echo 0 1 | unu 2op + grating.nrrd - -o grating.nrrd # create pattern ./vectr info grating.nrrd unu quantize -b 8 -i grating.nrrd -o grating.png open grating.pngThe step spacing 0.0171875 is 11/10 of the pixel spacing 0.015625 in grating.nrrd, so the 10 steps in each direction evenly sample the possible values of alpha (the fractional offset in index space) in the convolution sum. The interior of grating-rl.png should thus be uniform.LPARM="-i right0.nrrd -r grating.nrrd -k tent -h 0.0171875 -n -l 10" ./rvectr lic $LPARM -o - | unu quantize -b 8 -o grating-nn.png ./rvectr lic $LPARM -rlin -o - | unu quantize -b 8 -o grating-rl.png open grating-{nn,rl}.png- The examples above use a constant weighting for all points along the streamline, but this can be varied: other kernels can be used for the convolution along the streamline (i.e. to realize the flexibility of line integral
convolution ). To visualize the differences, we want something other than noise for the "noise" image:The cos and sin kernels are one period of cos() and sin(), fit within and weighted by the bspln2 kernel. The kernels are orthogonal (in the sense that their dot product is zero), and provide a basis for making LIC animations.HZ0=$[$(unu head noise.nrrd | grep sizes | cut -d' ' -f 3)/2] unu 2op x noise.nrrd 0 -o line.nrrd unu slice -i noise.nrrd -a 0 -p 0 | unu 1op exists | unu axinsert -a 0 -s 1 | unu inset -i line.nrrd -s - -min $HZ0 0 -o line.nrrd unu quantize -i line.nrrd -b 8 -o line.png SPC=$(./vectr info noise.nrrd | grep spacings | cut -d' ' -f 1) LPARM="-i right0.nrrd -r line.nrrd -k tent -h $SPC -n -l 60" QPARM="-b 8 -min 0.1% -max 0.1%" for LK in box tent tooth bspln2 cos sin; do echo "rvector lic -lk $LK" ./rvectr lic $LPARM -lk $LK -o - | unu quantize $QPARM -o right-lic-$LK.png done open line.png right-lic-{box,tent,tooth,bspln2,cos,sin}.png- We'd like to get some look at the four vector fields (out, shearA, shearB, rotate) created above:
The images on the left are the basic LIC result, the images on the right modulate the contrast of the LIC result by the length of the vector field, so that the LIC result is a constant gray in regions where the field is zero. 33710 students should experiment with adding "-rlin" to the LPARM definition.LPARM="-k tent -r noise.nrrd -h 0.01 -intg euler -n -l 30" for D in out shearA shearB rotate; do echo "lic -i ${D}0.nrrd ..." ./rvectr lic $LPARM -i ${D}0.nrrd -lk box -o $D-lic.nrrd -p len -po $D-len.nrrd unu quantize -b 8 -i $D-lic.nrrd -min 0.1% -max 0.1% -o $D-lic.png unu 2op x $D-lic.nrrd $D-len.nrrd | unu quantize -b 8 -min 0.1% -max 0.1% -o $D-lic-mbl.png done unu join -i {out,shearA,shearB,rotate}-lic{,-mbl}.png -a 2 | unu tile -a 2 0 1 -s 2 4 -o all-lic.png open all-lic.png rm -f {out,shearA,shearB,rotate}-len.nrrd- To make an animation of the flow, you use the cos and sin kernels with LIC, and then reweight them according to a rotating phase angle. This uses the {out0,shearA0,shearB0,rotate0}.nrrd datasets, as well as the noise.nrrd created above from out0.nrrd.
LPARM="-k tent -r noise.nrrd -h 0.005 -intg rk2 -n -l 60" for D in out0 shearA0 shearB0 rotate0; do echo "lic $LPARM -i $D.nrrd ..." ./rvectr lic $LPARM -i $D.nrrd -lk cos -o anim-$D-cos.nrrd ./rvectr lic $LPARM -i $D.nrrd -lk sin -p len -o anim-$D-sin.nrrd -po anim-$D-len.nrrd for T in cos sin; do unu 2op x anim-$D-$T.nrrd anim-$D-len.nrrd -o anim-$D-$T.nrrd done done for T in cos sin; do unu join -i anim-{out0,shearA0,shearB0,rotate0}-$T.nrrd -a 2 | unu tile -a 2 0 1 -s 2 2 -o anim-$T.nrrd done N=24 # how many frames to make in animation rm -f anim-??.nrrd for T in $(seq 0 $N); do TT=$(printf %02d $T) PH=$(unu affine 0 $T $[$N+1] -pi pi) CC=$(echo $PH | unu 1op cos | unu save -f text) SS=$(echo $PH | unu 1op sin | unu save -f text) echo $CC $SS > wght.txt unu join -i anim-{cos,sin}.nrrd -a 0 -incr | unu 2op x - wght.txt | unu project -a 0 -m sum -o anim-$TT.nrrd done unu join -i anim-??.nrrd -a 2 -incr | unu quantize -b 8 -min 0.1% -max 0.1% | unu dice -a 2 -o ./ -ff anim-%02d.png convert -delay 1 -loop 0 anim-??.png anim-lic.gif open -a Safari anim-lic.gif rm -f wght.txt anim-*.{png,nrrd}- March 2: see Piazza post @262 about making an animated gif of the turbulence data.
- March 2: Here's a better example to demonstrate what happens with streamlines at the boundary of the field. The generated uright.nrrd dataset is simpler than others in that its ItoW matrix is the identity (except for a translation in the last column). The "noise" texture edge.nrrd (visualized in edge.png) is actually a step function that slides across the vector domain, creating a controlled observation of how valued are added up along the streamlines (which should actually just be stepping through the index space of edge.nrrd).
./rvectr sdg -sz 151 201 -l 151 201 -p 1 0 0 0 0 0 -o uright.nrrd ./rvectr sdg -sz 151 201 -l 151 201 -p 0 0 1 0 0 1 -o - | unu dice -a 0 -o ./ unu 2op gt 0.nrrd 1.nrrd | unu crop -min 1 1 -max M-1 M-1 -o edge.nrrd unu quantize -b 8 -i edge.nrrd -o edge.png ./vectr lic -i uright.nrrd -k tent -r edge.nrrd -h 1 -l 51 -lk box -o edge-lic.nrrd unu minmax edge-lic.nrrd # should range from 0 to 1 unu quantize -b 8 -i edge-lic.nrrd -min 0 -max 1 -o edge-lic.png open edge.png edge-lic.png rm -f 0.nrrd 1.nrrd- March 2: A related configuration provides a more predictable way of seeing of linear interpolation into the "noise" texture is working (for 33710 students), or to make sure that doing LIC without vector field normalization also works.
All students should get the same url.png and url-n.png. 33710 students should get the same url-rlin.png (url-rlin-n.png is less interesting), because there should be two bands of constant gray in url-rlin.png corresponding to vector lengths 2/3 and 4/3: these are the lengths for which three samples (with that separation) into the linearly interpolated grating pattern will give the same value, regardless of whether the middle sample is on top of a 0 or a 1.# create vector data uramp.nrrd and noise ugrate.nrrd ./rvectr sdg -sz 151 201 -l 151 201 -p 1 0 0 0 0 0 -o tmp.nrrd echo 0 0 0 0 2 2 2 2 | unu reshape -s 2 2 2 | unu 2op + - 0.00001 | unu resample -s 2 151 201 -k tent -c node | unu 2op x tmp.nrrd - -o uramp.nrrd unu slice -i uramp.nrrd -a 2 -p 0 -o tmp.nrrd echo 0 0 1 1 | unu 2op + tmp.nrrd - | unu 2op + uramp.nrrd - | unu slice -a 0 -p 1 -o ugrate.nrrd unu quantize -b 8 -i ugrate.nrrd -o ugrate.png rm -f tmp.nrrd open ugrate.png # run LIC, without and with -n, and without and with -rlin ./vectr lic -i uramp.nrrd -k tent -r ugrate.nrrd -h 1 -l 1 -o url.nrrd ./vectr lic -i uramp.nrrd -k tent -r ugrate.nrrd -h 1 -l 1 -n -o url-n.nrrd ./vectr lic -i uramp.nrrd -k tent -r ugrate.nrrd -h 1 -l 1 -rlin -o url-rlin.nrrd ./vectr lic -i uramp.nrrd -k tent -r ugrate.nrrd -h 1 -l 1 -rlin -n -o url-rlin-n.nrrd unu quantize -b 8 -i url.nrrd -min 0 -max 1 -o url.png unu quantize -b 8 -i url-n.nrrd -min 0 -max 1 -o url-n.png unu quantize -b 8 -i url-rlin.nrrd -min 0 -max 1 -o url-rlin.png unu quantize -b 8 -i url-rlin-n.nrrd -min 0 -max 1 -o url-rlin-n.png open url.png url-n.png open url-rlin.png url-rlin-n.pngGrading
The grade will be based on style (10%) and correctness (90%), similar to previous projects. The style points will be the same as for Project 2: new functions are "static", convolution uses 2*S not S^2 kernel evaluations, the code compiles without warnings, and there are no memory leaks (though how this is tested may be more rigorous than it was for Project 2). The correctness points will be based on the commands above or some slight modifications.