# Rendering thousands of points

I am trying to render an image with some 120,000 Points. Technically it's just 10,000 points, plus their reflection, plus 5 rotations of both of those sets, but I'm not sure that's relevant. The code I'm using is something like this:

Graphics[
{
PointSize[1/n],
White,
g = Point /@ Join @@ {points, points.{{1, 0}, {0, -1}}},
Array[Rotate[g, Pi #/3, {0, 0}] &, 6]
},
Background -> Black,
ImageSize -> n*p,
ImageMargins -> n (1 - p)/2
]


where points contains 10,000 (integer) coordinate pairs.

A reasonably representative set of inputs would be

points = RandomInteger[{0, 10000}, {10000, 2}];
n = 600;
p = 0.8;


In reality, the points are created from a Brownian tree for this challenge over on PPCG.SE.

The problem is, I run out of memory. I don't actually need the resulting vector graphic, but wrapping the above in Rasterize doesn't help either (so I guess Mathematica does build the full vector graphic representation first anyway).

Is there a better way to rasterise a large number of points into an image? (Other than rounding all coordinates onto an n-by-n grid and using that as input to Image.)

Yves showed you how to shave off a factor of 10, and in the following I'll shave off another factor of 10.

First, the original code (plus some data of mine to make it work):

points = RandomReal[{0, 10}, {1000, 2}];

n = 500;
p = .99;

g1 = Graphics[{PointSize[1/n], White,
g = Point /@ Join @@ {points, points.{{1, 0}, {0, -1}}},
Array[Rotate[g, Pi #/3, {0, 0}] &, 6]}, Background -> Black,
ImageSize -> n*p, ImageMargins -> n (1 - p)/2] This takes more than 2 MB:

g1 // ByteCount


2468840

Using the trick Yves mentioned (Point@ instead of Point/@):

g2 = Graphics[{PointSize[1/n], White,
g = Point[Join @@ {points, points.{{1, 0}, {0, -1}}}],
Array[Rotate[g, Pi #/3, {0, 0}] &, 6]}, Background -> Black,
ImageSize -> n*p, ImageMargins -> n (1 - p)/2];


It saves a lot indeed:

g2 // ByteCount


232648

Now, since most of the pixels are rotated or reflected pixels ones, we can use the space saving function GeometricTransformation. If you use it with various transformations on a given Graphics object it doesn't store all the various transformed sets, but just the original set plus the transformations themselves, resulting in huge savings:

g3 = Graphics[{PointSize[1/n], White,
GeometricTransformation[
GeometricTransformation[
Point@points,
{
TranslationTransform[{0, 0}],
ReflectionTransform[{0, 1}]
}
],
{TranslationTransform[{0, 0}]}~Join~
Array[RotationTransform[Pi #/3] &, 6]
]
}, Background -> Black, ImageSize -> n*p,
ImageMargins -> n (1 - p)/2]


Indeed, this saves more than a factor of 10:

g3 // ByteCount


22448

Note that I used GeometricTransformation twice, once to get the original set plus its mirror image and once to rotate those two six times.

Obviously, given the different byte counts of g1, g2 and g3, their underlying graphics code must be different, but do we still get the same rasterized image?

Rasterize[g1] == Rasterize[g3]


True

• Very nice indeed! The actual mechanism used by GeometricTransform(esp. the memory-saving part) is not really well described in the docs, is it? Dec 28 '14 at 17:45
• @YvesKlett True. For the close reader it is implied in the Details section: GeometricTransformation[g,…] remains unchanged under evaluation, but affects how g is rendered. Dec 28 '14 at 18:19
• Hehe, you got me there. Dec 28 '14 at 19:04
• @YvesKlett I suspect that GeometricTransformation is off-loaded onto the GPU. Seems silly not to, based on my limited understanding of why there is a GPU in the first place. :) -- Addendum in response to your latest reply: Yes, they don't say much about implementation details. Dec 28 '14 at 19:40
• This worked like a charm, thanks a lot! Dec 28 '14 at 20:42

One way to save memory is to use the multi-primitive syntax for Point (which should also render quite a bit faster):

coords = RandomReal[{-1, 1}, {1000, 2}];
Point /@ coords // ByteCount
Point@coords // ByteCount
%/%% // N

(*176200

16552

0.0939387*)


For your example you simply drop one character to save a whole lot (about 90%) of kernel real estate:

Graphics[{PointSize[1/n], White,
g = Point /@ Join @@ {points, points.{{1, 0}, {0, -1}}},
Array[Rotate[g, Pi #/3, {0, 0}] &, 6]}, Background -> Black,
ImageSize -> n*p, ImageMargins -> n (1 - p)/2] // ByteCount

(*24642656*)

Graphics[{PointSize[1/n], White,
g = Point@Join @@ {points, points.{{1, 0}, {0, -1}}},
Array[Rotate[g, Pi #/3, {0, 0}] &, 6]}, Background -> Black,
ImageSize -> n*p, ImageMargins -> n (1 - p)/2] // ByteCount

(*2244952*)


Also works a treat for Line or Polygon`...

• Ah nice, thank you, I'll give that a go. Dec 28 '14 at 14:42
• It's been a while since I tested it, but the front end handles the multi-primitive forms more quickly, too. (Or the savings may be in how it is passed to the GPU -- I don't know about such things.) Dec 28 '14 at 19:32
• @MichaelE2 yep, sometimes very useful for huge expressions. I think I read a post (mathgroup?) a few years ago which explained that this was due to reduced kernel-frontend communication or some such. Again, no explicit mention in the docs on the performance thing. Dec 28 '14 at 19:38
• I notice the FE improvement most clearly in rotating 3D graphics. I can only speculate how these data structures correspond to GPU data structures and commands. Dec 28 '14 at 19:46