"Light Makes Right"
July 15, 1991
Volume 4, Number 2
Compiled by
All contents are copyright (c) 1991, all rights reserved by the individual authors
Archive locations: anonymous FTP at
ftp://ftp-graphics.stanford.edu/pub/Graphics/RTNews/,
wuarchive.wustl.edu:/graphics/graphics/RTNews, and many others.
You may also want to check out the Ray Tracing News issue guide and the ray tracing FAQ.
If you want to know how we did the beams of light effect, get the "Frontiers in Rendering" course notes (or even go to the course). There should be some good & weird topics at this course, such as Charlie Gunn's animations of hyperbolic space (where dodecahedra meet 8 at a corner) and Peter Kochevar's shading computations via cellular automata ("the lunatic fringe of rendering", as he puts it). Me, I'm going to talk about ray casting for radiosity, and also all the things that drive me crazy about radiosity (with lots of dirty laundry pictures showing where and how radiosity falls apart, and some solutions).
As usual, there will be a ray tracing researchers get-together at SIGGRAPH, open to anyone. On Thursday from 5:15 to 6:30 at room N223 in the convention center we'll meet and gab about this and that. No planned activities, just a place and time to connect names to faces.
When our son Ryan was born, Zap Andersson sent a nice little GIF from his ray tracer of a cigar in an ashtray as congratulations; the texture mapped smoke was particularly well done. Zap was just married, so I was able to send two interlinked rings with his wife's & his names inscribed in them. Definitely a future trend: graphical greeting cards/presents by e-mail.
The wedding rings picture was also my first worthwhile test image using the two-pass software we've developed to blend radiosity and ray-tracing. The nice thing about two-pass algorithms is that you generally get soft shadows cheaply, as the radiosity mesh picks up the shadows adaptively and so saves the ray tracer much shadow-testing time. You also get all the nice features of ray tracing, including exact geometry: spheres are truly spherical, instead of straight radiosity's polygonalized representation. Being able to combine the sampling mesh from radiosity and the geometry from ray tracing is usually a great combination.
Just so it is not buried in the bits: note that Greg Ward's Radiance ray tracer with radiosity effects built in (see his SIGGRAPH paper) is now available via FTP. As important, he has also begun a directory of "test" radiosity scenes which researchers can use to attempt to compare radiosities generated for these scenes. For those of you trying to write your own radiosity system this is a valuable tool for checking if you're getting anything near the right answer. Finally, Greg has also collected various object models and made them available.
Books of note: _Digital Image Warping_ by George Wolberg (IEEE Computer Society Press Monograph, 1990) is very handy if you're involved in texture mapping. Many different topics are covered, with good illustrations and sample code. It's not the ultimate book from a theory standpoint, but is very practical and understandable. Recommended.
Last issue someone mentioned the book _Fractal Programming and Ray Tracing with C++_ by Roger Stevens (M&T Books for $30, $20 more for the disk of code). I bought it, and cannot recommend it to the general reader. If you are getting your feet wet with C++ it might be of some interest, and beginning PC programmers might find bits of it useful. The author takes Steve Koren's QRT input language and develops a C++ ray tracer for it. One major problem with such a ray tracer is that there is no definition for a general polygon; only triangles and parallelograms are provided. There's a lot of padding in the book, with 20 or 30 page stretches of nothing but code listings, and 70 pages of data file listings (42 pages for his version of "sphereflake" alone - he could have printed the listings for the whole SPD package in that space!). The index is somewhat dysfunctional, e.g. minor variations on the words "bounding boxes" are given separate listings, some with wrong page numbers. References to almost all other work in ray tracing is missing, and the interested reader is given almost no help on where to go for more information. I wish it had been better.
Coming out at SIGGRAPH is "Graphics Gems II", edited by Jim Arvo this time around. Does anyone else know of good books to look for there? The other SIGGRAPH question: any guesses on how many times humorous references to "Monte Carlo" techniques will be made?
Get a job: one subscriber pointed out an interesting relationship between public domain ray tracers and future employment. The authors of two of the more popular public domain ray tracers (MTV by Mark VandeWettering and RayShade by Craig Kolb) are currently employed by PIXAR. Now if David Buck of DKBtrace gets a job there (no, I have no idea if he's even looking for work) this relationship can become a firmly established principle...
Finally: I've pretty much stopped culling USENET for news in comp.graphics, figuring that most everyone plows through this stuff by now. What convinced me was looking at my file of comp.graphics clippings and seeing that the accumulation surpassed half a megabyte! Updating the ray tracing and radiosity bibliographies, mailing list, and the FTP site list are time consuming enough; also running a clipping service was too much.
back to contents
# Janusz Kalinowski - density clouds (metaballs), parallelism, textures # Technical University of Wroclaw, Computing Center # Wybrzeze Wyspianskiego 27 # Wroclaw, Poland alias janusz_kalinowski kalinows@plwrtu11
I am a lecturer at Technical University of Wroclaw. I am mainly involved in CG-related subjects, but also in system software. We are preparing a metaballs system (modeller, renderer). I made my MS in DataFlow (I wrote an emulator of Manchester Prototype Dataflow System), but I prefer CG now. Maybe I will marry both domains in the future?
________
# Chris Green - efficiency, fancy primitives, radiosity, textures # Commodore Business Machines # 1200 Wilson Drive # West Chester, PA 19380 #<PHONE_NUMBER> alias chris_green njin!cbmvax.cbm.commodore.com!chrisg
When I've got spare time away from working on Amiga graphics, and doing contract work on 3d games, I spend it working on my ray tracer. My ray tracer is extremely fast, due to being written completely in 680x0 assembly with all fixed point math. I support spheres, triangles, hypertexture (!), and general implicit functions. It also has depth of field, penumbras, procedural textures, and more. The efficiency scheme used is my own invention and is especially fast for radiosity ray tracing (some day) and penumbras (now).
________
Russ Tuck<EMAIL_ADDRESS>MasPar Computer Corporation, 749 N. Mary Ave, Sunnyvale, CA 94086
(Old<EMAIL_ADDRESS>________
# Billy Ferrer - ray tracing on the Atari STe, efficiency. # University of California, Irvine # 2520 Golden Ave. # Long Beach, CA 90806 #<PHONE_NUMBER> alias bill_ferrer<EMAIL_ADDRESS>I am a sophomore at University of California, Irvine studying computer science. My interest in ray tracing stemmed from seeing impressive displays from Amiga, NCGA and Siggraph shows. I am just a beginner in the ray tracing programming department, but during my spare, spare, spare time I am writing a ray tracing program for the Atari STe because first there isn't a good and reliable ray tracing program on the ST/STe and second to write a ray tracing program for learning purposes. Currently my program traces checkered floors, spheres, and reflections. Right now, my effort is going towards texture mapping the floor by mapping an image on the floor. Hopefully my program will support refractions and other various 3d object formats.
________
Name: Thomas Michael Burgey Goals: modelling of objects, modelling of textures, RT art Company: Cadlab - Kooperation Uni-GH Paderborn / Siemens Nixdorf AG Bahnhofstrasse 32 W-4790 Paderborn Tel: +49 5251 284 151 Mail<EMAIL_ADDRESS>back to contents
Some highlights:
RayShade - a great ray tracer for workstations on up. DKBtrace - another good ray tracer, from all reports; works on PCs. Radiance - a ray tracer w/radiosity effects, a la Greg Ward (who wrote it). VORT,QRT,MTV,DBW - yet more ray tracers, some with interesting features. prt, VM_pRAY - parallel ray tracers. SIPP - scanline Z-buffer renderer. VOGLE - graphics learning environment (device portable).
SPD - a set of procedural databases for testing ray tracers. NFF - simplistic file format used by SPD. OFF - another file format.
RT News - collections of articles on ray tracing. RT bib - all known (by me) articles on ray tracing, in "refer" format. RT abstracts - collection of abstracts of many many RT articles.
Utah Raster Toolkit - nice image manipulation tools. FBM - another set of image manipulation tools. Graphics Gems - code from the ever so useful book.
(*) means site is an "official" distributor, so is most up to date.
weedeater.math.yale.edu [<IP_ADDRESS>]: /pub - *Rayshade 3.0 ray tracer*, *color quantization code*, *SPD*, *RT News*, *Wilson's RT abstracts*, "RT bib*, *new Utah raster toolkit*, newer FBM, *Graphics Gems code*. Craig Kolb<EMAIL_ADDRESS>rascal.ics.utexas.edu [<IP_ADDRESS>]: /misc/mac/inqueue - VISION-3D facet based modeller, can output RayShade files.
ccu1.aukuni.ac.nz [<IP_ADDRESS>]: ftp/mac/architec - *VISION-3D facet based modeller, can output RayShade files*. P.D. Bourke<EMAIL_ADDRESS>alfred.ccs.carleton.ca [<IP_ADDRESS>]: /pub/dkbtrace - *DKB ray tracer*. David Buck<EMAIL_ADDRESS>hobbes.lbl.gov [<IP_ADDRESS>]: Radiance ray trace/radiosity package. Greg Ward<EMAIL_ADDRESS>nic.funet.fi [<IP_ADDRESS>]: pub/graphics/papers - *Paper bank project, including Pete Shirley's entire thesis (with pics)*, *Wilson's RT abstracts in PostScript*, Kouhia Juhana Krister<EMAIL_ADDRESS>isy.liu.se [<IP_ADDRESS>]: pub/sipp-2.0.tar.Z scan line z-buffer and Phong shading renderer. Jonas Yngvesson<EMAIL_ADDRESS>calpe.psc.edu [<IP_ADDRESS>]: pub/p3d - p3d_2_0.tar P3D lispy scene language & renderers. Joel Welling<EMAIL_ADDRESS>ftp.ee.lbl.gov [<IP_ADDRESS>]: *pbmplus.tar.Z*, RayShade data files. Jef Poskanzer<EMAIL_ADDRESS>irisa.fr [<IP_ADDRESS>]: */iPSC2/VM_pRAY ray tracer*, SPD, /NFF - many non-SPD NFF format scenes, RayShade data files (Americans: check ftp.ee.lbl.gov first). Didier Badouel<EMAIL_ADDRESS>wuarchive.wustl.edu [<IP_ADDRESS>]: /mirrors/unix-c/graphics - Rayshade ray tracer, MTV ray tracer, Vort ray tracer, FBM, PBM, popi, Utah raster toolkit. /mirrors/msdos/graphics - DKB ray tracer, FLI RayTracker demos. Tracey Bernath<EMAIL_ADDRESS>tolsun.oulu.fi [<IP_ADDRESS>]: *FLI RayTracker animation files (PC VGA)*, *RayScene demos* (Americans: check wustl first). Jari Kahkonen<EMAIL_ADDRESS>cs.uoregon.edu [<IP_ADDRESS>]: /pub - *MTV ray tracer*, *RT News*, *RT bibliography*, other raytracers (including RayShade, QRT, VM_pRAY), SPD/NFF, OFF objects, musgrave papers, some Netlib polyhedra, Roy Hall book source code, Hershey fonts, old FBM. Mark VandeWettering<EMAIL_ADDRESS>hanauma.stanford.edu [<IP_ADDRESS>]: /pub/graphics/Comp.graphics - best of comp.graphics (very extensive), ray-tracers - DBW, MTV, QRT, and more. Joe Dellinger<EMAIL_ADDRESS>freedom.graphics.cornell.edu [<IP_ADDRESS>]: *RT News back issues*, *source code from Roy Hall's book "Illumination and Color in Computer Generated Imagery"*, SPD package, *Heckbert/Haines ray tracing article bibliography*, Muuss timing papers.
uunet.uu.net [<IP_ADDRESS>]: /graphics - RT News back issues (not complete), NURBS models, other graphics related material.
iear.arts.rpi.edu [<IP_ADDRESS>]: /pub - *Kyriazis stochastic Ray Tracer*. qrt, ohta's ray tracer, prt, other RT's (including one for the AT&T Pixel Machine), RT News, *Wilson's RT abstracts*, Graphics Gems, wave ray tracing using digital filter method. George Kyriazis<EMAIL_ADDRESS>jyu.fi [<IP_ADDRESS>]: /pub/graphics/ray-traces - many ray tracers, including VM_pRAY, DBW, DKB, MTV, QRT, RayShade, some RT News, NFF files. Jari Toivanen<EMAIL_ADDRESS>life.pawl.rpi.edu [<IP_ADDRESS>]: /pub/ray - *Kyriazis stochastic Ray Tracer*. George Kyriazis<EMAIL_ADDRESS>ab20.larc.nasa.gov [<IP_ADDRESS>]: /amiga - DBW, /usenet/comp.{sources|binaries}.amiga/volume90/applications - DKBTrace 2.01<EMAIL_ADDRESS>munnari.oz.au [<IP_ADDRESS>]: pub/graphics/vort.tar.Z - *VORT CSG and algebraic surface ray tracer*, *VOGLE*, /pub - DBW, pbmplus. David Hook<EMAIL_ADDRESS>gondwana.ecr.mu.oz.au [<IP_ADDRESS>]: pub - *VORT ray tracer*, *VOGLE*, Wilson's ray tracing abstracts. Bernie Kirby<EMAIL_ADDRESS>freebie.engin.umich.edu [<IP_ADDRESS>]: *Utah Raster Toolkit*, Spencer Thomas<EMAIL_ADDRESS>or Rod Bogart (rgb@caen.engin.umich.edu).
cs.utah.edu [<IP_ADDRESS>]: /pub - Utah raster toolkit, *NURBS databases*. Jamie Painter<EMAIL_ADDRESS>gatekeeper.dec.com [<IP_ADDRESS>]: /pub/DEC/off.tar.Z - *OFF objects*, /pub/misc/graf-bib - *graphics bibliographies (incomplete)*. Randi Rost<EMAIL_ADDRESS>expo.lcs.mit.edu [<IP_ADDRESS>]: contrib - *pbm.tar.Z portable bitmap package*, *poskbitmaptars bitmap collection*, *Raveling Img*, xloadimage. Jef Poskanzer<EMAIL_ADDRESS>venera.isi.edu [<IP_ADDRESS>]: */pub/Img.tar.z and img.tar.z - some image manipulation*, /pub/images - RGB separation photos. Paul Raveling<EMAIL_ADDRESS>wuarchive.wustl.edu [<IP_ADDRESS>]: /mirrors/unix-c/graphics - Rayshade ray tracer, MTV ray tracer, Vort ray tracer, FBM, PBM, popi, Utah raster toolkit. /mirrors/msdos/graphics - DKB ray tracer.
ucsd.edu [<IP_ADDRESS>]: /graphics - utah rle toolkit, pbmplus, fbm, databases, MTV, DBW and other ray tracers, world map, other stuff. Not updated much recently.
okeeffe.berkeley.edu [<IP_ADDRESS>]: /pub - TIFF software and pics. Sam Leffler<EMAIL_ADDRESS>surya.waterloo.edu [<IP_ADDRESS>]: /graphics - FBM, ray tracers
vega.hut.fi [<IP_ADDRESS>]: /graphics - RTN archive, ray tracers (MTV, QRT, others), NFF, some models
gondwana.ecr.mu.oz.au [<IP_ADDRESS>]: SPD, NFF & OFF databases, Graphics Gems code. Bernie Kirby<EMAIL_ADDRESS>hp4nl.nluug.nl [<IP_ADDRESS>]: /pub/graphics/raytrace - DBW.microray, MTV, etc.
ftp.brl.mil [<IP_ADDRESS>]: /old/brl-cad - information on how to get the BRL CAD package & ray tracer.
karazm.math.uh.edu [<IP_ADDRESS>]: pub/Graphics/rtabs.shar.12.90.Z - *Wilson's RT abstracts*, VM_pRAY. J. Eric Townsend<EMAIL_ADDRESS>maeglin.mt.luth.se [<IP_ADDRESS>]: graphics/raytracing/Doc - *Wilson's RT abstracts*.
ftp.fu-berlin.de []: /pub/unix/graphics/rayshade4.0/inputs - aq.tar.Z is RayShade aquarium (Americans: check ftp.ee.lbl.gov first). Heiko Schlichting<EMAIL_ADDRESS>apple.apple.com [<IP_ADDRESS>?]: /pub/ArchiveVol2/prt.
netlib automatic mail replier: UUCP - research!netlib, Internet -<EMAIL_ADDRESS>*SPD package*, *polyhedra databases*. Send one line message "send index" for more info, "send haines from graphics" to get the SPD.
UUCP archive: avatar - RT News back issues. For details, write Kory Hamzeh<EMAIL_ADDRESS>back to contents
* TIGHT screen space bounding. Some people neglect screen space bounding, and don't use it. MANY people use it, but they project the 3D bounding box onto screen space, potenti- ally getting a lot of 'slack' around 'em. Gain: Speedier bounding box generation Loss: Slack around objects = more wasteful intersections. My Way: Do a vector rendering and get minmax screen coordinates from that. Yes, a vector rendering might take time, but hey, what's that compared to the tracing time, eh? (Yes I know about the method of doing a Z buffer rendering first... but then you have to write a Z buffer renderer first, right?) Comments: Think about the standard axis-aligned bounding box around an object. Seen from the vector 1,1,1 you will have the corners 'sticking out' maximally. Also, bounding box intersection takes this 'n that amount of FP operation. A screen space bounding box is done with four integer compares. For eye rays I NEVER intersect the actual bounding boxes at all, ONLY use the screenspace bounding, plus a stored minimum distance to the eye for each bounding box. So my bounding box intersection for eye rays is 4 integer and one FP compare. * Self sorting list Most objects in my structure has the minimum distance to the eye recorded. When intersection the object, the first thing you do is to see if the currently valid 't' is smaller than this distance. If so we have already hit a closer object, and can never hit this object. Also, when the ray- intersection checker has found the closest object, this is put first in the list and will be checked first next time. Since the intersection check function is called recursively when we enter a bounding box, objects INSIDE the bounding box is also sorted, so for each 'level' in the ray tree we always have the object we hit last first. * Light buffer Similar to the 2d bounding boxes on screen for eye rays, shadow rays have their 2d bounding boxes, but since a light source can shine all around and is not limited to one direction, this is done in polar coordinates (a spherical coordinate system). So, for shadow rays I don't intersect any real bounding boxes either, but do some more compare operations. And since the ranges for a spherical coordinate system is fixed (i.e. 2 pi * pi) there is no point in using floating point, so fixed point integers can be used instead. The minimum distance to each object is also constant for each light, and may then be calculated and stored when we set up things and do the wire frame rendering. * Reflected/fracted rays This is the only case where I actually intersect bounding boxes. And now to the next weird issue: I do NOT use axis-aligned bounding boxes, they are transformed together with the rest of things, since my bounding boxes actually work as "transformers" for my objects. But FIRST, for each box, I check the MINIMUM distance with the current 't' and if bigger, just discard. To find the minimum distance, I am helped by the fact that my bounding boxes are stored as a center point and x, y and z extends from that centre. So I can use a rough (distance to center) - (x_size + y_size + z_size), and I get a value that is guaranteed to be SMALLER than the actual distance. And to avoid square roots, my actual comparisons is of course done on the distances squared, i.e. from the ray origin, I take the x distance to the bbox centre squared, plus y distance squared, plus z distance squared. Now I have the distance from ray origin to the center of the bbox squared. Now subtract from that (x_size + y_size + z_size) squared, and compare that to 't' squared. If 't' is smaller, we can never hit that bounding box. * Transformed bounding boxes I've always been in love with tight bounding volumes, because they avoid unnecessary TRUE intersections. Thus, my bounding boxes are transformed with everything else. Actually, it works like this (pseudo code-ish): trace_ray(first_object,ray) { object = first_object; while (object) { . . (( do the 2d intersection checking, either eye-ray 2d or polar 2d for lamps. only do rest of code if 2d hit, and distance is below current 't' )) . (( Ok, we hit 2D-wise, OR this was a reflected/fracted ray then: transform ray to object's coordinate system )) . switch(object->type) { case SPHERE: /* Intersect sphere(oids) */ . . case BOUNDING_BOX: ((check boundbox intersection, if refl/frac ray, if not refl/frac ray, set hit to true.)) if (hit) { /* Ok, let's trace rays in this new coordinate system we are in now */ trace_ray(object->bbox->contents_of_box,transformed_ray); } } object = object->next; } } What this gives me, besides the ability to twiddle my bounding boxes, is local coordinate systems WITHIN those boxes. So if I have a HUNDRED objects that would feel happy if they got their bounding boxes tilted 30 degrees to the right, I could create ONE bounding box that transforms the ray 30 degrees to the right, and then use axis-aligned boxes (which of course are faster) INSIDE the box for each of the hundred objects, making them happy. Also, it helps animating stuff a lot. Move the bounding box, ans wham you have an aggregate object. Move a box within the box, and Wham you have hierarchical motion control without sweating too much. * Filtering texture maps The way I filter texture maps may be considered "nasty" but it works OK for me. I know the resolution, and I also know (from reverse engineering my perspective ray creator ;-) how "big" the screen is in units, I can easily calculate how "big" one pixel is in the screen plane. Deeper into the model, the pixel covers a larger area, increasing linearly. So, the distance an object is from the eye, guide the "pixel size" at that point in a very simple and linear way. Now the first error you do when you want to sample from your texture map is to sample an area that is pixel_size * pixel_size large. Well, that is correct for planes that are facing you. But if we are looking almost para- llell to the surface? No, the actual area covered by the pixel is roughly pixel_size / cos(view_angle), and since cos() is simply a dot product (that you'll need later anyway) it's easily calculated. Now I simply grab a square area that is this size large, and average it. Nasty, but looks quite OK without overwhelming calculations. Oh yes, there is the pathetic case where you are supposed to sample 10000 x 10000 pixels and average. Well, I never sample more than 8 x 8, then I start to pick out 64 pixels at random within an n x n grid, and I don't bother if I get the same one twice, since the impact of that on an average of 64 pixels is mostly killed by the dithering noise anyway ;-) Also, I use the eye-distance calculation ALL THE WAY DOWN THE RAY TREE, which looks very OK as soon as the mirroring surfaces are flat, or there is no heavy refraction going on. But now let's move on to other anti- aliasing. * Hashing anti aliasing I never use color difference as my subdivision criteria for supersampling. What I do is this: Before each sample for a pixel, set hash_number to 0. The trace_ray() procedure does the following: If it hits an object Is it a smoothed patch mesh or similar? Add its "smoothing group" or anything else that is same for all faces smoothed together (I use the vertexlist pointer) to the hashnumber else Add something unique for the object, its "number" or the objectpointer to the hashnumber. Set shadow_count to zero For each light source: Check shadows. If in shadow, shadow_count++. hash_number += shadow_count < 5 (or whatever); If object is reflecting/fracting, call trace_ray() recursively as always in a raytracer.... So what is all this? Well the hashnumber we get we compare with the hashnumber for last pixel and the pixel in the last scanline. If it is different then: * We have hit different objects than last pixel/line * We are in/out of different number than shadows than last pixel/line AND THIS IS FOR THE ENTIRE RAY TREE! So if we hit anything else, ANYWHERE down the tree, the hashnumber is bound to be different. Oh yes, there are a number of weird cases where you just HAPPEN to miss a supersample just because we get out of shadow the same time we move from object 0 to object 32, and thereby by mistake get the same hash number. But who cares? Don't worry, be happy. * Surface acne [NEW! NEW! NEW! NEW! NEW?] ??? Or is it? You tell me... How to avoid it. Well, somebody proposed normal vector testing, and that is OK for the system with well behaved users. The rest of us do a small epsilon to avoid it. But someone said that this epsilon might be too big or small for a given scene. And the funniest of them all (I laughed quite a while), he proposed scaling it to the "diameter of the scene" or something else. Why on earth do things like this? The epsilon for displacing the ray for any object, is of course related to the pixel_size as described above! I use pixel_size / 4 and it works like a charm even if I model molecules or the solar system (which I have done both, actually!) even in the same scene!!
back to contents
I was also troubled by the fact that my ray-tracer was anti-aliasing across textures (causing massive thrashing on my machine), and took the problem to the local gurus.
I got back the suggestion of building a hash code, and putting all the things I wanted to detect for in the hash calculation.
So I hashed together, object pointers, and light sources (when they weren't shadowed, or outside the cone of a spotlight...). So first I'd check the hash code, skipping color changes due purely to textures. Only if the hash differed, would I check the colors, just so I didn't SSamp a smooth edge also.
However, this didn't fix sampling across a soft shadow edge.
back to contents
...Mind you, my thesis is more to do with the navigation of oct-trees AFTER they have been constructed with interval analysis. This navigation seems more efficient than all that ARTS nonsense (navigating only half the vertical steps), naturally runs under integer addition and bit-shifting in a Bresenham- type way BUT WITHOUT the concept of a global driving axis, and being completely immune to division by zero requires no exception handling. I called the SMART method (Spatial Measures for Accelerated Ray Tracing) - perhaps my jocularity has back-fired & no-ones taking me seriously. I can ray-trace 20,000+ triangles with two light sources & shadows at 512x512 in under 8 minutes on a Sun SparcStation - in fact (outrageous claim time) SMART has been observed to achieve constant time ray tracing INDEPENDENT of object count. Each ray simply navigates a few empty voxels, whose number is independent of the global object count, until reaching the first non-empty voxel where generally only one object need be intersected & accepted as the nearest struck. For example, I rendered a single torus in 7 mins 23 secs and 80 tori in 7 mins 20 secs. This is all AFTER constructing the octtree - O(N) but very efficient with interval analysis. Interval analysis allows one to decompose right down to the surface of a primitive or CSG object (none of this bounding box nonsense propounded in RTNews a couple of years back). You'll be able to see some of the pictures of the Octtrees in my thesis - at a fine resolution they look great!
[...]
I think the work of Adrian Bowyer & John Woodwark at Bath would interest you - they're attacking things from a CAD/CAM angle (Adrian is a Mechanical Engineer) - email<EMAIL_ADDRESS>Another pocket of isolated English ray-tracing is established at Leeds university (which I only stumbled across recently). They also have a CAD/CAM bent, & are particularly into multi- processors. If you're interested, email Professor Peter Dew at<EMAIL_ADDRESS>A Dr Stuart Green also did some multi-processor work (using your SPD data base) at Bristol University, but has now moved out to industry & I don't have his new email address. I've recently moved to Edinburgh to take advantage of a 420 Meiko transputer surface - there's quite a lot of knowledge in ray tracing accumulating here (Fractal planets etc).
... Arvo & Kirk's formation of candidate lists for their 5D ray tracing efficiency scheme? I'm not a great fan of this I'm afraid - the old problem, too much over-approximation for concave objects. Consider a hoop with large major radius, small minor radius (e.g. bicycle tyre tube). A view frustrum can easily intersect the hoops bounding box by passing through the hoop's central hole WITHOUT striking the tyre anywhere!
Lazy Oct-tree construction is the one for me, with nice tight decompositions right down to object surfaces, allowing rays to pass through the centre of such hoops whilst ignoring them, (or indeed lazy Hex-tree construction for animated scenes...), and reduced storage & construction costs. It's a bit difficult to convey the efficiency of the scheme without recourse to a black board, but the long and the short of it is that most rays missing all objects end up querying none (hoorah!) whilst those hitting an object end up querying only that (ie just one) object. One can't do much better than that on a ray by ray basis, and I gave up on ray coherence ages ago (not that it's not got great potential, but I got awful headaches trying to work out the action of a complex CSG object e.g. reflective engine block on a single incoming pyramid of rays, never mind refraction - perhaps you've got further ... ?). Oh, and you get free adaptive anti-aliasing with octtrees ..
back to contents
Summaries of papers presented at the second Eurographics Workshop on Rendering Barcelona, Spain, 13-15 May 1991
Gregory Ward<EMAIL_ADDRESS>Adaptive Shadow Testing for Ray Tracing
Method for reducing the number of shadow rays for scenes with a large number of light sources. The sources are sorted on their contribution, and only for the most important sources rays are cast. The influence of the other sources is estimated statistically. Tests are done with different tolerances (threshold to determine whether sources are important) and certainties (rate of accuracy). The method gives good reduction and is able to find the most important shadows because it selects contrast as criterion.
Christophe Schlick<EMAIL_ADDRESS>An Adaptive Sampling Technique for Multidimensional Integration by Ray- Tracing
Describes a sampling method that includes the following characteristics: adaptivity, irregularity, complete stratification, importance sampling and uncorrelation. It allows a fast reconstruction. Implementation is done using look-up tables.
J.P. Jessel, M. Paulin, R. Caubet An Extended Radiosity Using Parallel Ray-Traced Specular Transfers
Describes a parallel extended radiosity method. The method is implemented on a parallel architecture dedicated to ray-tracing (based on transputers).
Veysi Isler, Cevdet Aykanat, Bulent Ozguc<EMAIL_ADDRESS>Subdivision of 3D Space Based on the Graph Partitioning for Parallel Ray Tracing
Describes a heuristic algorithm to subdivide the 3D space by converting the problem into a graph partitioning problem.
back to contents
Green, Stuart. "Parallel Processing for Computer Graphics", 1991, in the series "Research Monographs in Parallel and Distributed Computing".
MIT Press, Cambridge, Massachusetts, 02142. ISSN 0953-7767, ISBN 0-262-57087-4.
Pitman Publishing, 12-14 Slaidburn Crescent, Southport PR9 9YF. ISBN 0-273-08834-3.
I don't have the US$ price, but in the UK it costs 27.95 Sterling.
back to contents
There are two loops: one which creates SIZE levels and the other that creates 2^SIZE balls on each level. The balls on each level almost form a circle. the entire structure makes a spiral. The thing I am most displeased with is the texture ("f" line). I created it so that a scene would have a very nonuniform distribution of objects.
#include <stdio.h> #include <math.h> #define PI 3.1415926 #define DEFAULTSIZE 10
main(argc,argv) int argc; char *argv[]; {double r, x, y, z, frac, angle, tmp, invs2; int s, s2, w, SIZE, col;
if (argc > 1) sscanf(argv[1],"%d",&SIZE); else SIZE = DEFAULTSIZE; s2 = (int) pow(2.0,(double)SIZE); x = (double) SIZE / 2.0; printf("v\nfrom 40 20 -40\n"); printf("at 0 0 0\n"); printf("up 0 1 0\nangle 45\nhither 1\nresolution 512 512\n"); printf("b 0 0 0.3\n"); printf("l 90 90 0\n"); printf("l 0 90 -90\n"); printf("f 0.3 0.3 0.3 0.5 0.5 3 0 0\n"); printf("p 4\n"); printf("50 0 50\n"); printf("50 0 -50\n"); printf("-50 0 -50\n"); printf("-50 0 50\n"); for (s = col = 0; s < SIZE; s++) {s2 = (int) pow(2.0,(double) s); for (w = 0; w < s2; w++) {frac = (double)w / (double)s2; r = (double)s + frac; angle = 2.0 * PI * frac; x = 2*r*cos(angle); z = 2*r*sin(angle); tmp = (double)(SIZE - s) + (double)(s2 - w - 1) / (double)s2; y = tmp*tmp*tmp / (double)(SIZE*SIZE); col = (col + 1) & 7; printf("f %d %d %d 0.8 0.2 3 0 0\n",(col&4)>0,(col&2)>0,col&1); printf("s %g %g %g %g\n",x,y,z,2.0/(double)(s*s+1)); } } }
back to contents
Eric A. Haines and John R. Wallace Shaft Culling for Efficient Ray-Traced Radiosity May 1991, Barcelona File: hain91.ps.Z [about 70 kBytes]
If anonymous FTP is not available to you I will mail it if requested.
Please contact me for a full list of the papers.
[We have recently sent Juhana the last version of our paper (with thinko's removed) and so you might want to get this improved version. The new version is called "Shaft Culling for Efficient Ray-Cast Radiosity". It's a nice little algorithm for finding what objects potentially block light between any two given boxes in space. Similar in some ways to Arvo & Kirk 5D but with hierarchy, it has some interesting potential uses and generally speeds up hierarchical bounding volume ray tracers. - EAH]
back to contents
To pick up release 1.4 from anonymous ftp, connect to hobbes.lbl.gov (<IP_ADDRESS>) with ftp using the "anonymous" account and enter your e-mail address as the password. Everything is in the directory pub, and the main distribution is called "Radiance1R4.tar.Z". This file is about 3.5 Megabytes, so please do your transfers in binary mode the first time! Also, you will probably experience less network traffic in the morning, when most computer scientists are asleep.
________
Information on Models, Test Environments, etc for Radiance:
I've just set up an anonymous ftp archive site at hobbes.lbl.gov (<IP_ADDRESS>) for sharing Radiance models and programs. In addition to the standard source distribution, this archive contains the following:
pub/generators - Programs for generating specialized objects & shapes pub/libraries - Libraries of patterns, textures, fonts, etc. pub/mac - MacIntosh applications and utilities (for Radiance) pub/models - Complete Radiance scene descriptions pub/objects - Objects for including in Radiance scenes pub/programs - Miscellaneous programs and utilities pub/tests - Test scenes for validating global illumination programs pub/translators - CAD file translators and image format converters
For those of you who I haven't dragged aside and told already, Radiance is free ray tracing software for lighting simulation and rendering that does a lot of neat stuff and tries very hard to do it accurately.
If you are interested in picking up or leaving off some nice environment models, this is a good place to do it. Most of the scene descriptions are in Radiance format, but writing a translator shouldn't be too much work, and I'm willing to offer whatever help I can.
If you are working on your own radiosity or ray tracing program and want to compare results, please check out the pub/tests directory. There is not much there now, but with your help we can make this archive into a valuable resource for researchers in global illumination.
Please send any questions or comments to<EMAIL_ADDRESS>________
I finally finished putting together a library of objects from the various models I've created for Radiance. It is in pub/objects/gjward.tar.Z. I hope people find it useful. It took me quite some time to get the my miscellany into a usable form.
If you have objects you are willing to submit, please take a look at the way this initial library is set up first.
________
>From Paul D. Bourke<EMAIL_ADDRESS>[<IP_ADDRESS>]
The public domain modeller Vision-3D for the Mac II family is about to support Radiance data files as an export option. This has already been done but the copy on our FTP site hasn't yet been updated (I want to put some more features in the next release) If anyone is interested however the current version of Vision-3D with Radiance file export can be made available.
back to contents
In the USA: Morgan Kaufmann Publishers Order Fulfillment Center P.O. Box 50490 Palo Alto, Ca 94303 USA, phone<PHONE_NUMBER>
The proceedings should be available by now, and they can take your order in advance.
back to contents
There is an NFF previewer available for anonymous FTP on gondwana.ecr.mu.oz.au [<IP_ADDRESS>] pub/preview.c. It uses the vogle library which is also available as pub/vogle.tar.Z
>From Patrick Flynn (flynn@cse.nd.edu):
If anyone on this side of the pond wants the preview.c program, it's now available for anonymous FTP from shillelagh.cse.nd.edu (<IP_ADDRESS>). You can get the VOGLE library from uunet. I tried the previewer; it does what I need.
>From Mike Gigante (mg@godzilla.cgl.rmit.oz.au):
There is a NFF previewer for Silicon Graphics workstations available on godzilla.cgl.rmit.oz.au (<IP_ADDRESS>). Make sure you grab the readme file also. It uses the hardware lighting and Zbuffer on the SGI machines to give a very fast preview.
>From Eric Haines:
I also have two previewers for NFF files which work on HP workstations (one is static, the other uses the mouse for rotating & viewing the object). I can send them to anyone who wants them (no FTP right now).
back to contents
Because I'm sure that raytracer-fans have lotsa questions about RayTracker, after they have seen these animations, please mail all questions to the author, i.e. to Haakan Andersson<EMAIL_ADDRESS>If you have problems with animations (they don't unpack etc...) flame or hate-mail me...
You can find animations from /pub/rayscene/zap, though it has nothing to do with Rayscene. /pub/rayscene/anim.PC contains raytraced animations made with Rayscene and DKBtracer (my animations...). /pub/rayscene/anim.AMIGA contains raytraced animations for Amiga made with Rayscene and DKBTracer (animations by Panu Hassi).
Animations are packed with Lharc 2.05. All animations are self-extracting archives (= .exe-files). So, "run" them and they will unpack themselves. For examples: If you have "car.exe"-animation, just type "car". Animation-arc- hives includes short README.TXT and animation (.fli). If you want to give these animations to your friends, *do not* separate these parts. Give .exe, since README.TXT contains contact-information to Haakan Andersson. Also, every animation has little text for contact address. (except "mesh", I didn't wanna "spoil" it...it moves so smoothly...:). Remember to set "type binary" when downloading files...
These animations need Autodesk Animation Player, and if you don't have it, you can pick it from /pub/rayscene/anim.PC. That directory contains my demos made with Rayscene. Filename is "aaplay.exe".
Animations run faster if you are running them from RAM-disk. If you have mouse it's easier to control aaplay.exe. Adjust animation speed to your machine.
Some animations are not full-screen because of their original size.
Little description of animations:
BIKE.FLI: Fly-by over a bike in a brick basement.
HP-RIP2.FLI: The 'famous' one of a Camshaft emerging from its own drawing. Created for the 'Mechslide' demo video, available from Emt Inc in USA. The name 'HP-RIP' comes from the fact that the 'idea' of a camshaft came from Hewlett-Packard's well known raytraced 'Camshaft on bed of ravioli' by Eric Haines, a friend of mine.
ART.FLI: Fly-by over a pen, a few bolts, a drawing and a book placed on a wooden table under a green metal table lamp.
BLAHBLAH.FLI: Here's a (relatively) new one: Demo of 1.55 Beta's transparent, animated texturemaps, using Autodesks 'BOSSTALK'. Even funnier is to use animated bump-maps. To see them, watch out for 'Spaceman Spiff' at a theatre near you.
BOING.FLI: Newer version of BOUNCE. A Red, bouncing chrome sphere on top of a texture map from Imagetects(tm). Rendered and Animated in RayTracker 1.4 Beta.
BOUNCE.FLI: Very very simple animated bouncing ball. The first RayTracker animation EVER!
CAR.FLI: Looking at a red car in sunset. Image Rendered and Animated in RayTracker 0.6 Beta. Model built in AutoCAD R10 by: Bertil Heden, Autodesk, Sweden. Thanks, Bert!
MESH.FLI: Fly-round a bowling pin of glass and a bowling ball on a plate suspended in space. New effect in this version of RayTracker is the ability create a feel of space via 'space mapping' a background to the universe.
ALARMCLK.FLI: An image of an alarmclock, a matchbox, a table, and a wooden table lamp. Suddenly, without warning, we fly past the clock and up the lamp. Why? Beats me.
________
Tracey Bernath<EMAIL_ADDRESS>notes:
I recently uploaded the raytraced animations from tolsun.oulu.fi to wuarchive.wustl.edu to try and cut down the trans-ocean net travel. I can't upload to SIMTEL because we have a lousy connection, and I always get the usual "Too many anonymous users, Try Again" 8-}
in /pub/zap are the raytraced and animated images, some are really good, some, well... in /pub/raytracker are the original animations, including aaplay.lzh (I don't know if aaplay.exe works, but I know the aaplay.lzh does )
Enjoy!
back to contents
Current version (1.71) Date: 24 april 1991
RayTracker is a commercial raytracing program, especially created to render geometries from the well-known CAD software AutoCAD. RayTracker reads models in the AutoCAD DXF format, or in Autodesk 3D Studio 'ASCII' format.
RayTracker runs on MS-DOS PC computers, with a math co-processor, but porting to other platforms (mainly Sparc, Amiga and Mac) are being considered. It utilizes Expanded (EMS) memory if it is found in the system.
RayTracker has a graphical user interface (GUI) with dialog boxes, pull-down menus, and a built in hypertext help-system. It runs in two modes, a graphical mode (which requires an EGA/VGA display) and a text mode fore those lacking graphics. On a VGA you may also see the image as it is being rendered. You may view the finished image with a supplied program on your VGA, Super-VGA or on your CAD display, if it has an AutoSHADE compatible real mode ADI driver with version 4.0 or higher and at least 256 colors.
Materials are easily assigned to objects by simply loading them from the provided material library, containing many useful materials. You may also create your own materials by setting parameters for color, ambient, diffuse and specular reflection, mirroring, transparency, index of refraction, trans- lucency, shadow-casting and many many other things.
The mapping functions in RayTracker use a very generalized model of a map. A 'map' can be any number of patterns, mixed or overlaid, placed on different parts of one single surface, or repeated all over. Each pattern can be both a bitmapped graphic image (GIF, Targa 16/24/32, Animator CEL, Animator FLI and Amiga IFF format is accepted), or one of the built in mathematically defined functions (wood, marble, random noise, wavy, checkered etc.).
Each 'map' can be applied in many ways:
* Texture map - Changing the color of the surface. This is the only thing that more primitive renderers, such as 'Big D' can do. Certain parts of a texturemap can also have a transparent color allowing one single surface to depict a complex object such as a tree or a person as a 'coulisse'. Naturally, the shadow has the contour of the tree or person also.
* Bump map - The 'brightness' at each spot in the map guides the surface 'altitude', allowing you to create dented, scraped, wrinkled, engraved or in any other minor surface deviation.
* Mix map - Allows you to mix between two totally different surface descriptions on one surface, such as a checkered pattern where some squares are of red glass and others of wood.
* Reflect. map- Allows you to simulate reflections without the increased rendering time using true mirroring. Curved objects looks just as convincing with a reflection map. You may also add amusing effects, such as 'window reflections' like in the film 'Tin Toy' from Pixar.
Aside from this, a map can also be applied as the 'slide' in a slide projector light source, or as the screen fore- or background.
Lightsources include point sources, directed, spotlights and slide- projectors. Light falloff can be none, linear or quadratic. All types of lights can cast shadows of two types: Raytraced shadows, that are sharp and accurate but requires one extra step in the ray tracing algo- rithm, and shadows using a "shadow map", that can have 'fuzzy edges' and work very quickly.
To ease the production of images, RayTracker renders every 16:th pixel first, then every 8:th and so on, allowing you to quickly determine if there is something wrong with the light, or a material. You may also create 'test renderings' on parts of your model by simply marking two points on the initial wireframe.
Three modes are available, Quick Track, ignoring shadows and reflections, Medium Track, accounting for shadows, but not reflection and refraction, and finally Ray Track, performing the full raytracing calculation.
RayTracker can generate walkthrough animations from just a small set of 'key' views, using a 7 dimensional spline interpolation technique. The images can be automatically concatenated to an Animator compatible FLI file.
The output format from RayTracker is GIF or Targa 16/24/32 (with alpha channel in the 32 bit format) in any resolution (up to your diskspace limit). A conversion utility is also provided to convert a Targa file to a Amiga HAM format.
RayTracker is *NOT* a PD or ShareWare program, it is a commercial product, available from the addresses supplied below. If you have any technical questions about its capabilities, you can contact me, the program author on email address:
<EMAIL_ADDRESS>....or you can send ordinary mail to the LAST (Scandinavian) address listed below, and attach my name: Hakan 'Zap' Andersson
U.S.A. Europe: Scandinavia: ================= ================= =================== EMT Inc. #250 EMT Ltd. EMT Ab 199 N. Commercial st. PO Box 103 Box 40 Bellingham, WA Rickmansworth S-178 21 Ekeroe 98255 USA Herts WD3 5RF SWEDEN U.K. Phone: (USA)-206 647 2426 (UK)-923 285 496 (SW)-756 320 20 Fax: (USA)-206 647 2890 (UK)-923 285 496 (SW)-756 346 50
back to contents


VIPS from PythonUsing VIPS — How to use the VIPS library from Python |
VIPS comes with a convenient, high-level Python API built on
on gobject-introspection
. As long as you can get GOI
for your platform, you should be able to use libvips.
To test the binding, start up Python and at the console enter:
>>> from gi.repository import Vips >>> x = Vips.Image.new_from_file("/path/to/some/image/file.jpg") >>> x.width 1450 >>>
If import fails, check you have the Python
gobject-introspection packages installed, that you have the
libvips typelib installed, and that the typelib is either
in the system area or on your GI_TYPELIB_PATH
.
If .new_from_file()
fails, the vips overrides
have not been found. Make sure Vips.py
is in
your system overrides area.
Here's a complete example program:
#!/usr/bin/python import sys from gi.repository import Vips im = Vips.Image.new_from_file(sys.argv[1]) im = im.extract_area(100, 100, im.width - 200, im.height - 200) im = im.similarity(scale = 0.9) mask = Vips.Image.new_from_array([[-1, -1, -1], [-1, 16, -1], [-1, -1, -1]], scale = 8) im = im.conv(mask) im.write_to_file(sys.argv[2])
Reading this code, the first interesting line is:
from gi.repository import Vips
When Python executes the import line it performs the following steps:
It searches for a file called Vips-x.y.typelib
. This
is a binary file generated automatically during libvips build
by introspection of the libvips shared library plus scanning
of the C headers. It lists all the API entry points, all the
types the library uses, and has an extra set of hints for object
ownership and reference counting. The typelib is searched for
in /usr/lib/gi-repository-1.0
and along the path
in the environment variable GI_TYPELIB_PATH
.
It uses the typelib to make a basic binding for libvips. It's
just the C API with a little very light mangling, so for
example the enum member VIPS_FORMAT_UCHAR
of the enum VipsBandFormat
becomes
Vips.BandFormat.UCHAR
.
The binding you get can be rather unfriendly, so it also
loads a set of overrides from Vips.py
in
/usr/lib/python2.7/dist-packages/gi/overrides
(on my system at least). If you're using python3, it's
/usr/lib/python3/dist-packages/gi/overrides
.
Unfortunately, as far as I know, there is no way to extend
this search using environment variables. You MUST have
Vips.py
in exactly this directory. If you install
vips via a package manager this will happen automatically,
since vips and pygobject will have been built to the same
prefix, but if you are installing vips from source and the
prefix does not match, it will not be installed for you,
you will need to copy it.
Finally, Vips.py
makes the rest of the binding. In
fact, Vips.py
makes almost all the binding: it
defines __getattr__
on Vips.Image
and binds at runtime by searching libvips for an operation of
that name.
The next line is:
im = Vips.Image.new_from_file(sys.argv[1])
This loads the input image. You can append load options to the argument list as keyword arguments, for example:
im = Vips.Image.new_from_file(sys.argv[1], access = Vips.Access.SEQUENTIAL)
See the various loaders for a list of the available options
for each file format. The C equivalent to this function,
vips_image_new_from_file()
, has more extensive documentation. Try
help(Vips.Image)
to see a list of all the image
constructors --- you can load from memory, or create from an array,
for example.
The next line is:
im = im.extract_area(100, 100, im.width - 200, im.height - 200)
The arguments are left, top, width, height, so this crops 100 pixels off
every edge. Try help(im.extract_area)
and the C API docs
for vips_extract_area()
for details. You can use .crop()
as a synonym, if you like.
im.width
gets the image width
in pixels, see help(Vips.Image)
and vips_image_get_width()
and friends for a list of the other getters.
The next line:
im = im.similarity(scale = 0.9)
shrinks by 10%. By default it uses
bilinear interpolation, use interpolate
to pick another
interpolator, for example:
im = im.similarity(scale = 0.9, interpolate = Vips.Interpolate.new("bicubic"))
see vips_similarity()
for full documentation. The similarity operator
will not give good results for large resizes (more than a factor of
two). See vips_resize()
if you need to make a large change.
Next:
mask = Vips.Image.new_from_array([[-1, -1, -1], [-1, 16, -1], [-1, -1, -1]], scale = 8) im = im.conv(mask)
makes an image from a 2D array, then convolves with that. The
scale
keyword argument lets you set a divisor for
convolution, handy for integer convolutions. You can set
offset
as well. See vips_conv()
for details on the vips
convolution operator.
Finally,
im.write_to_file(sys.argv[2])
sends the image back to the
filesystem. There's also .write_to_buffer()
to make a
string containing the formatted image, and .write()
to
write to another image.
As with .new_from_file()
you can append save options as
keyword arguments. For example:
im.write_to_file("test.jpg", Q = 90)
will write a JPEG image with quality set to 90. See the various save
operations for a list of all the save options, for example
vips_jpegsave()
.
Try help(Vips)
for everything,
help(Vips.Image)
for something slightly more digestible, or
something like help(Vips.Image.black)
for help on a
specific class member.
You can't get help on dynamically bound member functions like
.add()
this way. Instead, make an image and get help
from that, for example:
image = Vips.Image.black(1, 1) help(image.add)
And you'll get a summary of the operator's behaviour and how the arguments are represented in Python.
The API docs have a handy table of all vips operations, if you want to find out how to do something, try searching that.
The vips command can be useful too. For example, in a terminal you can type vips jpegsave to get a summary of an operation:
$ vips jpegsave save image to jpeg file usage: jpegsave in filename where: in - Image to save, input VipsImage filename - Filename to save to, input gchararray optional arguments: Q - Q factor, input gint default: 75 min: 1, max: 100 profile - ICC profile to embed, input gchararray optimize-coding - Compute optimal Huffman coding tables, input gboolean default: false interlace - Generate an interlaced (progressive) jpeg, input gboolean default: false no-subsample - Disable chroma subsample, input gboolean default: false trellis-quant - Apply trellis quantisation to each 8x8 block, input gboolean default: false overshoot-deringing - Apply overshooting to samples with extreme values, input gboolean default: false optimize-scans - Split the spectrum of DCT coefficients into separate scans, input gboolean default: false strip - Strip all metadata from image, input gboolean default: false background - Background value, input VipsArrayDouble operation flags: sequential-unbuffered nocache
pyvips8
basicsAs noted above, the Python interface comes in two main parts, an automatically generated binding based on the vips typelib, plus a set of extra features provided by overrides. The rest of this chapter runs through the features provided by the overrides.
The overrides intercept member lookup
on the Vips.Image
class and look for vips operations
with that name. So the vips operation "add", which appears in the
C API as vips_add()
, appears in Python as
image.add()
.
The first input image argument becomes the self
argument. If there are no input image arguments, the operation
appears as a class member. Optional input arguments become
keyword arguments. The result is a list of all the output
arguments, or a single output if there is only one.
Optional output arguments are enabled with a boolean keyword
argument of that name. For example, "min" (the operation which
appears in the C API as vips_min()
), can be called like this:
min_value = im.min()
and min_value
will be a floating point value giving
the minimum value in the image. "min" can also find the position
of the minimum value with the x
and y
optional output arguments. Call it like this:
min_value, opts = im.min(x = True, y = True) x = opts['x'] y = opts['y']
In other words, if optional output args are requested, an extra
dictionary is returned containing those objects.
Of course in this case, the .minpos()
convenience
function would be simpler, see below.
Because operations are member functions and return the result image, you can chain them. For example, you can write:
result_image = image.sin().pow(2)
to calculate the square of the sine for each pixel. There is also a full set of arithmetic operator overloads, see below.
VIPS types are also automatically wrapped. The override looks at the type of argument required by the operation and converts the value you supply, when it can. For example, "linear" takes a VipsArrayDouble as an argument for the set of constants to use for multiplication. You can supply this value as an integer, a float, or some kind of compound object and it will be converted for you. You can write:
result_image = image.linear(1, 3) result_image = image.linear(12.4, 13.9) result_image = image.linear([1, 2, 3], [4, 5, 6]) result_image = image.linear(1, [4, 5, 6])
And so on. A set of overloads are defined for .linear()
,
see below.
It does a couple of more ambitious conversions. It will automatically convert to and from the various vips types, like VipsBlob and VipsArrayImage. For example, you can read the ICC profile out of an image like this:
profile = im.get_value("icc-profile-data")
and profile
will be a string.
You can use array constants instead of images. A 2D array is simply
changed into a one-band double image. This is handy for things like
.erode()
, for example:
im = im.erode([[128, 255, 128], [255, 255, 255], [128, 255, 128]])
will erode an image with a 4-connected structuring element.
If an operation takes several input images, you can use a 1D array
constant or a number constant
for all but one of them and the wrapper will expand it
to an image for you. For example, .ifthenelse()
uses
a condition image to pick pixels between a then and an else image:
result_image = condition_image.ifthenelse(then_image, else_image)
You can use a constant instead of either the then or the else parts, and it will be expanded to an image for you. If you use a constant for both then and else, it will be expanded to match the condition image. For example:
result_image = condition_image.ifthenelse([0, 255, 0], [255, 0, 0])
Will make an image where true pixels are green and false pixels are red.
This is also useful for .bandjoin()
, the thing to join
two or more images up bandwise. You can write:
rgba = rgb.bandjoin(255)
to add a constant 255 band to an image, perhaps to add an alpha channel. Of course you can also write:
result_image = image1.bandjoin(image2) result_image = image1.bandjoin([image2, image3]) result_image = image1.bandjoin([image2, 255])
and so on.
The wrapper spots errors from vips operations and raises the
Vips.Error
exception. You can catch it in the
usual way. The .detail
member gives the detailed
error message.
You can use the C API functions vips_image_new_from_memory()
and
vips_image_write_to_memory()
directly from Python to read and write
areas of memory. This can be useful if you need to get images to and
from other other image processing libraries, like PIL or numpy.
Use them from Python like this:
image = Vips.Image.new_from_file("/path/to/some/image/file.jpg") memory_area = image.write_to_memory()
memory_area
is now a string containing uncompressed binary
image data. For an RGB image, it will have bytes
RGBRGBRGB...
, being
the first three pixels of the first scanline of the image. You can pass
this string to the numpy or PIL constructors and make an image there.
Note that .write_to_memory()
will make a copy of the image.
It would
be better to use a Python buffer to pass the data, but sadly this isn't
possible with gobject-introspection, as far as I know.
Going the other way, you can construct a vips image from a string of binary data. For example:
image = Vips.Image.new_from_file("/path/to/some/image/file.jpg") memory_area = image.write_to_memory() image2 = Vips.Image.new_from_memory(memory_area, image.width, image.height, image.bands, Vips.BandFormat.UCHAR)
Now image2
should be an identical copy of image
.
Be careful: in this direction, vips does not make a copy of the memory
area, so if memory_area
is freed by the Python garbage
collector and
you later try to use image2
, you'll get a crash.
Make sure you keep a reference to memory_area
around
for as long as you need it.
Paint operations like draw_circle
and draw_line
modify their input image. This makes them hard to use with the rest of
libvips: you need to be very careful about the order in which operations
execute or you can get nasty crashes.
The wrapper spots operations of this type and makes a private copy of the image in memory before calling the operation. This stops crashes, but it does make it inefficient. If you draw 100 lines on an image, for example, you'll copy the image 100 times. The wrapper does make sure that memory is recycled where possible, so you won't have 100 copies in memory. At least you can execute these operations.
If you want to avoid the copies, you'll need to call drawing operations yourself.
The wrapper defines the usual set of arithmetic, boolean and
relational overloads on
image
. You can mix images, constants and lists of
constants (almost) freely. For example, you can write:
result_image = ((image * [1, 2, 3]).abs() < 128) | 4
The wrapper overloads []
to be vips_extract_band()
. You
can write:
result_image = image[2]
to extract the third band of the image. It implements the usual slicing and negative indexes, so you can write:
result_image = image[1:] result_image = image[:3] result_image = image[-2:] result_image = [x.avg() for x in image]
and so on.
The wrapper overloads ()
to be vips_getpoint()
. You can
write:
r, g, b = image(10, 10)
to read out the value of the pixel at coordinates (10, 10) from an RGB image.
Some vips operators take an enum to select an action, for example
.math()
can be used to calculate sine of every pixel
like this:
result_image = image.math(Vips.OperationMath.SIN)
This is annoying, so the wrapper expands all these enums into separate members named after the enum. So you can write:
result_image = image.sin()
See help(Vips.Image)
for a list.
The wrapper defines a few extra useful utility functions:
.get_value()
,
.set_value()
,
.bandsplit()
,
.maxpos()
,
.minpos()
,
.median()
.
Again, see help(Vips.Image)
for a list.
GLib includes a command-line option parser, and Vips defines a set of standard flags you can use with it. For example:
import sys from gi.repository import GLib, Vips context = GLib.OptionContext(" - test stuff") main_group = GLib.OptionGroup("main", "Main options", "Main options for this program", None) context.set_main_group(main_group) Vips.add_option_entries(main_group) context.parse(sys.argv)


Validation in scala
This article is about possible solutions for validation in scala. Validation is the process of checking input data in order to provide its correctness and requirements compliance.
Implementations
There are several libraries in scala which can be used for validation:
- Accord - Accord is a validation library written in and for Scala. Docs here
- Skinny validator - skinny-validator is a portable library, so it is possible to use skinny-validator with Play2, Scalatra and any other web app frameworks. Docs here
- DValidation - A little, opinionated Scala domain object validation toolkit
- io.underscore.validation - Work-in-progress library demonstrating a functional programming approach to data validation in Scala
The source code for this article is here
General
Let’s define domain classes which should be validated with the libraries above.
//Phone.scala
//Phone.scala
package io.koff.validation.domain
/**
* User phone
*/
case class Phone(value: String)
//Address.scala
package io.koff.validation.domain
/**
* User address
* @param street street
* @param house house number
*/
case class Address(street: String, house: Int)
//User.scala
package io.koff.validation.domain
/**
* User - the main example domain class for validation
* @param login user login
* @param email user email
* @param password user password
* @param age user age
* @param phone user phone
* @param addresses list of user addresses - for showing a sequence validation
* @param userInfo recursive data for showing a recursive validation
*/
case class User(login: String,
email: String,
password: String,
age: Int,
phone: Option[Phone],
addresses: Seq[Address],
userInfo: InfoNode[String] = InfoNode.default)
After we defined our domain we can implement validation rules for it using the listed libraries.
Accord
Accord is a standalone scala library which is developed and used in WiX.
The full example of using Accord can be found here
First what you need to do is to define validation rules using validator(...)
method:
import io.koff.validation.domain.{Address, User, Phone}
import com.wix.accord._
import dsl._
/**
* Define Accord validator for Address class
*/
implicit val addressValidator = validator[Address] { address =>
address.street is notEmpty
address.house is >(0)
}
/**
* Define Accord validator for Phone class
*/
implicit val phoneValidator = validator[Phone]{ phone =>
phone.value is notEmpty and startWith("+7")
}
/**
* Define Accord validator for User class
*/
implicit val userValidator = validator[User] { user =>
//It is not possible to make a negative predicate
//for example there is no way to forbid using "admin" in user name except for creation of your own validator
user.login is notEmpty and startWith("super_") and endWith("!")
//just as a sample :)
//don`t user regex to validate emails:
// - http://davidcel.is/posts/stop-validating-email-addresses-with-regex/
//otherwise your users can face with problems using their emails on your service
user.email is matchRegex("""\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z""")
// all the definitions below are equal
user.password.length is between(6, 12)
user.password have size >= 6
user.password has size <= 12
//you can validate number with range
user.age.must(between(13, 99))
//can use >, <, >=, <= etc
user.age must <=(99)
user.age must >=(13)
//can`t write it like this
//user.age must >= 13
//it will be a compilation error
//check if a number field is equal or not equal to some value
//user.age must equalTo(1000)
//user.age must notEqualTo(1)
//checks if an option field is defined with Some(value)
//user.phone is notEmpty
//make sure that if the phone field is defined then it is defined with correct value
user.phone.each is valid
user.addresses should notEmpty
user.addresses.each is valid
}
Then you can check an object using validate[T](...)
method. The validator for T
must be accessed as an implicit value.
def main(args: Array[String]) {
val correctUser = User(
login = "super_user_!",
email =<EMAIL_ADDRESS>password = "1234567",
age = 14,
phone = Some(Phone("+78889993322")),
addresses = Seq(Address("Baker st.", 221))
)
println("correct result: " + validate(correctUser))
//prints: 'correct result: Success'
val withWrongPhone = correctUser.copy(phone = Some(Phone("8889993322")))
println("withWrongPhone: " + validate(withWrongPhone))
//prints: 'withWrongPhone: Failure(Set(GroupViolation(Phone(8889993322),is invalid,Some(phone),Set(RuleViolation(8889993322,must start with '+7',Some(value))))))'
//More examples are in the code....
}
In summary this library is quite good but there is one issue: an error message is represented as a sentence with a lexical description(must start with '+7'
) instead of an error code like User.phone.invalid
. It can be a problem when you want to localize error messages.
Skinny validator
Skinny validator is a part of Skinny Framework - a full-stack web app framework built on Skinny Micro.
The full example of using Skinny validator can be found here
At first let’s define a new trait EntityValidator
which helps organising validations rules for domain classes.
trait EntityValidator[T] {
def getValidator(entity: T): Validator
}
And define the validator for User objects
implicit val userValidator = new EntityValidator[User] {
override def getValidator(entity: User): Validator = {
import skinny.validator.{email => correctEmail}
import entity._
Validator(
param("login" -> login) is notEmpty & startWith("super_") & endWith("!"),
// skinny.validator.email using regex for checking email
param("email" -> email) is correctEmail,
//these definitions are equal
param("password" -> password) is minMaxLength(6, 12),
param("password" -> password) is minLength(6),
param("password" -> password) is maxLength(12),
//these definitions are equal
param("age" -> age) is intMinMaxValue(13, 99),
param("age" -> age) is intMinValue(13), // >=
param("age" -> age) is intMaxValue(99), // <=
//this is how to check equation of values
param("age" -> (age, 14)) is same
//there is no build-in way to check options(Some|None) and elements of collections
)
}
}
If we want to add new validation rule we should extend ValidationRule
trait like this:
/**
* For `startWith` we should create custom ValidationRule
*/
case class startWith(value: String) extends ValidationRule {
def name = "startWith"
def isValid(v: Any) = Option(v) match {
case Some(x) => x.toString.startsWith(value)
case None => true
}
}
/**
* For `endWith` we should create custom ValidationRule
*/
case class endWith(value: String) extends ValidationRule {
def name = "endWith"
def isValid(v: Any) = Option(v) match {
case Some(x) => x.toString.endsWith(value)
case None => true
}
}
Let’s add utility method validate[T](...)
in order to make easier using of skinny-validator.
def validate[T](entity: T)(implicit entityValidator: EntityValidator[T]): Either[Errors, _] = {
val validator = entityValidator.getValidator(entity)
val isOk = validator.validate()
if(isOk){
Right(())
} else {
Left(validator.errors)
}
}
def main(args: Array[String]) {
val correctUser = User(
login = "super_user_!",
email =<EMAIL_ADDRESS>password = "1234567",
age = 14,
phone = Some(Phone("+78889993322")),
addresses = Seq(Address("Baker st.", 221))
)
println("correctUser: " + validate(correctUser))
//prints: 'correctUser: Right(())'
val allWrong = correctUser.copy(
login = "not_super_user",
email = """"Look at all these<EMAIL_ADDRESS>//it is still a valid email address
password = "short",
age = 101
)
println("correctUser: " + validate(allWrong))
//prints:
//Left(
// Errors(
// Map(
// age -> List(
// Error(name = intMinMaxValue, messageParams = List(13, 99)),
// Error(name = intMaxValue, messageParams = List(99)),
// Error(name = same, messageParams = List())
// ),
// password -> List(
// Error(name = minMaxLength, messageParams = List(6, 12)),
// Error(name = minLength, messageParams = List(6))
// ),
// email -> List(Error(name = email, messageParams = List())),
// login -> List(Error(name = startWith, messageParams = List()))
// )
// )
//)'
}
Skinny validator has a basic functionality for validation of simple objects. But if you want to validate more complex structures you have to write your own validation rules. It can be a problem to do it for sequences, options and other generic types.
DValidation
This library is a small validation toolkit on top of scalaz.
The full example of using DValidation can be found here
In the first place we have to define instances of DValidator[T]
for our domain classes. The important notice - it is necessary to import scalaz.Order[Int] for isInRange
and other number checkers.
val phoneValidator = Validator.template[Phone] { phone =>
phone.validateWith(
startWith(phone.value, "+7")
)
}
val addressValidator = Validator.template[Address] { address =>
address.validateWith(
notBlank(address.street) forAttribute 'street,
address.house.is_>(0) forAttribute 'house
)
}
val userValidator = Validator.template[User]{ user =>
user.validateWith(
notBlank(user.login) forAttribute 'login,
startWith(user.login, "super_") forAttribute 'login,
ensure(user.login)("error.dvalidation.end_with")(_.endsWith("!")) forAttribute 'login,
regex(user.email, """\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z""") forAttribute 'email,
//these definitions are equal
isInRange(user.password.length, 6, 12) forAttribute Symbol("User.password.length.between"),
user.password.length.is_>=(6) forAttribute Symbol("User.password.length.lessThan"),
user.password.length.is_<=(12) forAttribute Symbol("User.password.length.greaterThan"),
user.age.is_>=(13) forAttribute 'age,
user.age.is_<=(99) forAttribute 'age,
user.age.is_==(14) forAttribute 'age, // or you can use `is_===` for a strict check
validOpt(user.phone)(phoneValidator),
hasElements(user.addresses) forAttribute 'addresses
).withValidations(
// You also can user validSequence(...) to validate elements of a collection
validSequence(user.addresses, addressValidator)
)
}
You can defile own error codes using scala.Symbol
and forAttribute(...)
method
Also we need additional validation rules like startWith
:
// We need to import scalaz.Order[Int] for `isInRange` and other number checkers
import scalaz.std.AllInstances._
/**
* Example of a custom validator for DValidation
* @param toCheck value that should be checked
* @param startWith value that `toCheck` should start with
*/
def startWith(toCheck: String, startWith: String): DValidation[String] =
ensure(toCheck)("error.dvalidation.start_with")(a => a.startsWith(startWith))
/**
* Simple regex checker
*/
def regex(toCheck: String, regexStr: String): DValidation[String] = {
val regex = regexStr.r
ensure(toCheck)("error.dvalidation.regex")(a => regex.pattern.matcher(a).find())
}
Correctness can be checked in this way:
def main(args: Array[String]) {
val correctUser = User(
login = "super_user_!",
email =<EMAIL_ADDRESS>password = "1234567",
age = 14,
//it will also work if phone = None
phone = Some(Phone("+78889993322")),
addresses = Seq(Address("Baker st.", 221))
)
println("correct result: " + userValidator(correctUser))
//prints: 'correct result:
// Success(
// User(
// super_user_!,
//<EMAIL_ADDRESS>// 1234567,
// 14,
// Some(Phone(+78889993322)),
// List(Address(Baker st.,221)),
// InfoNode(1,None,List())
// )
// )
//'
val allWrong = correctUser.copy(
login = "not_super_user",
email = """"Look at all these<EMAIL_ADDRESS>//it is still a valid email address
password = "short",
age = 101,
phone = Some(Phone("invalid_phone"))
)
println("allWrong: " + userValidator(allWrong))
//prints: 'allWrong:
// Failure(
// DomainError(path: /login, value: not_super_user, msgKey: error.dvalidation.start_with),
// DomainError(path: /login, value: not_super_user, msgKey: error.dvalidation.end_with),
// DomainError(path: /email, value: "Look at all these<EMAIL_ADDRESS>msgKey: error.dvalidation.regex),
// DomainError(path: /User.password.length.between, value: 5, msgKey: error.dvalidation.notGreaterThen, args: 6,false),
// DomainError(path: /User.password.length.lessThan, value: 5, msgKey: error.dvalidation.notGreaterThen, args: 6,true),
// DomainError(path: /age, value: 101, msgKey: error.dvalidation.notSmallerThen, args: 99,true),
// DomainError(path: /age, value: 14, msgKey: error.dvalidation.notEqual, args: 101),
// DomainError(path: /, value: invalid_phone, msgKey: error.dvalidation.start_with)
// )
}
Although there are not many build-in validators in DValidation, it can be considered as a good option for validation of complex structures if it is ok to have dependency on scalaz
in your project.
io.underscore.validation
Despite that this library is named as «Work-in-progress» it has quite a good functionality and it is pretty simple in use.
The full example of using io.underscore.validation can be found here
You can define validators for our domain classes using validate[T](...)
method and define custom validation methods like startWith
and endWith
extending Validator[T]
trait
/**
* Define custom validators for io.underscore.validation
*/
def startWith(value: => String, msg: => String): Validator[String] = Validator[String] { in =>
if(in.startsWith(value)) pass else fail(msg)
}
def endWith(value: => String, msg: => String): Validator[String] = Validator[String] { in =>
if(in.endsWith(value)) pass else fail(msg)
}
// implicit val infoNodeValidator = validate[InfoNode]
// .field(_.index)(gte(1, "InfoNode.index.gte"))
implicit val phoneValidator = validate[Phone].field(_.value)(startWith("+7", "Phone.value.startWith"))
implicit val addressValidator = validate[Address]
.field(_.street)(nonEmpty("Address.street.notEmpty"))
.field(_.house)(gt(0, "Address.house.gt"))
implicit val userValidator = validate[User]
.field(_.login)(nonEmpty ("User.login.notEmpty"))
.field(_.login)(startWith ("super_", "User.login.startWith") and endWith("!", "User.login.endWith"))
.field(_.email)(matchesRegex("""\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z""".r, "User.login.invalid"))
.field(_.password)(lengthGt (6, "User.password.lengthGt"))
.field(_.password)(lengthLte (12, "User.password.lengthLte"))
.field(_.age)(gte(13, "User.age.gte"))
.field(_.age)(lte(99, "User.age.lte"))
.field(_.age)(eql(14, "User.age.eql"))
.field(_.phone)(optional(phoneValidator))
// also you can use `required(...)` if you want to have a defined optional field
//.field(_.phone)(required(phoneValidator))
//and `.seqField(...)` for validate Seq[T]
.field(_.addresses)(nonEmpty("User.address.notEmpty"))
.field(_.userInfo)(infoNode(nonEmpty("User.userInfo.value.notEmpty"))) //using recursion
.field(_.userInfo)(infoNode(startWith("correct_", "User.userInfo.value.startWith")))//using recursion
.seqField(_.addresses)
Lines //using recursion
will be discussed in the next section.
Object validation can be done by using implicit method validate
in io.underscore.validation
def main(args: Array[String]) {
val correctUser = User(
login = "super_user_!",
email =<EMAIL_ADDRESS>password = "1234567",
age = 14,
phone = Some(Phone("+78889993322")),
addresses = Seq(Address("Baker st.", 221)),
userInfo = InfoNode(
index = 1,
value = Some("correct_parent"),
children = Seq(
InfoNode(10, Some("correct_child"), Seq.empty),
InfoNode(20, None, Seq.empty)
)
)
)
println("correctUser: " + correctUser.validate.errors)
// prints: 'correctUser: List()'
val allWrong = correctUser.copy(
login = "not_super_user",
email = """"Look at all these<EMAIL_ADDRESS>//it is still a valid email address
password = "short",
age = 101,
addresses = Seq(Address("", -1))
)
println("allWrong: " + allWrong.validate)
//prints: 'allWrong:
// Validated(
// User(
// not_super_user,
// "Look at all these<EMAIL_ADDRESS>// short,
// 101,
// Some(Phone(+78889993322)),
// List(Address(,-1)),
// InfoNode(/* doesn't right now */),
// List(
// ValidationError(User.login.startWith,ValidationPath(login)),
// ValidationError(User.login.endWith,ValidationPath(login)),
// ValidationError(User.login.invalid,ValidationPath(email)),
// ValidationError(User.password.lengthGt,ValidationPath(password)),
// ValidationError(User.age.lte,ValidationPath(age)),
// ValidationError(User.age.eql,ValidationPath(age)),
// ValidationError(Address.street.notEmpty,ValidationPath(addresses[0].street)),
// ValidationError(Address.house.gt,ValidationPath(addresses[0].house))
// )
// )
//'
}
So davegurnell/validation can be considered as a great lib for validation of very complex structures like generics and recursions if you are not bothered concerning dependence on scala.language.experimental.macros
and scala.language.higherKinds
which are used in this lib.
Bonus: Recursive validation
And here is a little bonus - validation of recursive structures. Recursive validation was implemented for io.underscore.validation
but I think it can be easily ported at least to DValidation
.
In our domains classes we have this generic recursive structure:
//InfoNode.scala
package io.koff.validation.domain
/**
* Generic recursive example of domain class
* @param index here it is just a number without additional meaning
* @param value some value which we want to store in a node and validate
* @param children child nodes which should also be validated
* @tparam T value type
*/
case class InfoNode[T](index: Int, value: Option[T], children: Seq[InfoNode[T]])
object InfoNode{
/**
* Just a default value for InfoNode
*/
def default[T]: InfoNode[T] = InfoNode(1, None, Seq.empty)
}
In order to validate it infoNode[T](rule: Validator[T])
method has been created:
/**
* Recursive validator for InfoNode
*/
def infoNode[T](rule: Validator[T]): Validator[InfoNode[T]] = Validator[InfoNode[T]] { in =>
val generalValidator = validate[InfoNode[T]]
.field(_.index)(gte(1, "InfoNode.index.gte"))
.field(_.children)(lengthLte(2, "InfoNode.children.lengthLte"))
.field(_.value)(optional(rule))
val resultValidator = if(in.children.isEmpty) {
generalValidator
} else {
generalValidator.seqField(_.children)(infoNode(rule))
}
resultValidator(in)
}
As the argument this method receives a validation rule for T
type - Validator[T]
. So you can use other rules or validators in order to check all children in this recursive structure:
.field(_.userInfo)(infoNode(nonEmpty("User.userInfo.value.notEmpty"))) //using recursion
.field(_.userInfo)(infoNode(startWith("correct_", "User.userInfo.value.startWith"))) //using recursion