44

I'm generating some 3D models of planetary nebulae and supernova remnants for Celestia, a free OpenGL astronomy software.

Currently, I know how to do it with random points inside a spherical shell. However, I'm still unable to generate filaments like those in the Crab nebula, and I need some help on this. You can see some of the models I've created there : Nebulae models for Celestia.

Someone has a suggestion about how to generate random filaments with random points only ?

Here's a small part of the code I'm currently using to generate a shell nebula :

X[r_, u_, phi_] := Oblateness r Sqrt[1 - u^2] Cos[phi]
Y[r_, u_, phi_] := Oblateness r Sqrt[1 - u^2] Sin[phi]
Z[r_, u_] :=  r u

Shell[r_, u_, phi_] := {X[r, u, phi], Y[r, u, phi], Z[r, u]}

Coords[n_] := SetPrecision[Flatten[Table[Shell[#1, #2, #3] &[
    Random[BetaDistribution[alpha, beta]],
    Random[Real, {-1, 1}],
    Random[Real, {0, 2Pi}]
], {n}], 0], CoordinatesPrecision]

This code defines "n" points inside an oblate sphere. If "n" is large, I get an uniform distribution of points, without any internal filaments-like structures.

How can I distribute the "n" points so they form "N" filaments inside the sphere, of random lenght and randomly oriented ? There should be some parameters which specify the mean number "p" of points for each filament, so approximately n = p * N.

A picture of the Crab nebula (Messier 1)

EDIT 1

Just some precision : I would like to reproduce very qualitatively the Crab nebula as a 3D object made of points, with definite cartesian coordinates X, Y, Z. The code should be compatible with Mathematica 7.0.

The features which are desired are the long filaments structures inside the nebula.

Ideally, I would like to define a statistical distribution of variables X, Y, Z that could generate some random filaments with voids between them.


EDIT 2

Here's a part of the code I'm now using to generate the models (the rest of the code isn't relevant here). Thanks a lot to all who responded, and thanks to Simon Woods, from whom that code was done ! I still have some issues, however (see below) :

InternalColor := RGBColor[0.2, 0.55, 0.8, 0.6]; (* color at the center of the nebula *)
MiddleColor := RGBColor[0.4, 0.6, 0.4, 1]; (* color of transition to the exterior part *)
ExternalColor := RGBColor[0.9, 0.3, 0.4, 0.4]; (* color of the exterior part *)
MinRadius := 0.00; (* min radius of distribution *)
MaxRadius := 1.00; (* max radius of distribution *)
MinSprite := 0.003; (* min radius of sprites *)
MaxSprite := 0.07; (* max radius of sprites *)
Oblateness := 0.8; (* oblateness of the spherical distribution *)
CoordinatesPrecision := 8;
NumberOfVoids := 1000;
NumberOfPoints := 20000;

SpriteSize[r_] := MinSprite + (MaxSprite - MinSprite)(r - MinRadius)/(MaxRadius - MinRadius);

voidpts = Select[RandomReal[{-1, 1}, {NumberOfVoids, 3}], MinRadius <= Norm[#/{1, 1, Oblateness}] <= MaxRadius &];
pts = Select[RandomReal[{-1, 1}, {NumberOfPoints, 3}], MinRadius <= Norm[#/{1, 1, Oblateness}] <= MaxRadius &];
nf = Nearest[voidpts];
DistributeDefinitions[nf];

pts = ParallelMap[Nest[0.9975 (# + 0.01 (# - First@nf[#])) &, #, 100] &, pts];

SpriteColor = Blend[{InternalColor, MiddleColor, ExternalColor}, #] &;
PlotColor = ColorData["SunsetColors"];

Graphics3D[{PointSize[0.005], {PlotColor[Norm[1.3 #]], Sphere[#, 0.005]} & /@ pts}, Boxed -> False, Background -> Black, Lighting -> "Neutral", SphericalRegion -> True]

Filaments = Join[#, {SpriteSize[Norm[#]]}, List@@SpriteColor[1.3 Norm[#]]] &/@pts;
FilamentsData := SetPrecision[Flatten[Filaments, 0], CoordinatesPrecision]

This code is slow. Is there a way to improve it ?

More importantly, I'm having an issue with the number of points generated at the intersection of several filaments : there's too much points accumulated there, and this is a problem for rendering in Celestia (too much sprites at the same location is ugly). Is there a way to reduce or dilute these points ?

Very important : I need to add a constraint on the shortest distance between two points : it shouldn't be smaller than the local sprite size. How can I add this constraint to the iteration process ?

Cham
  • 4,093
  • 21
  • 36
  • Oblateness is just a parameter (a constant) to change the shape of the sphere, so the distribution looks more natural. It shouldn't be a perfect sphere. For a perfect sphere, Oblateness = 1. – Cham Mar 08 '13 at 11:23
  • 3
    What, precisely, should a "filament" be? It sounds like you are implicitly asking us a cosmology question here: until we have a theory of the genesis or morphology of filaments in nebulae, what scientific or mathematical basis can we possibly adduce to answer this question? At best all we can do at this point is offer "solutions" that qualitatively appear to be like some images you have offered, but then the question devolves into one of reproducing an artistic image and scarcely could be said to have an objective answer. – whuber Mar 08 '13 at 15:23
  • The modelisation isn't based on any theory. It's just pure geometry to modelise something which "looks" like filaments. It's more an "artistic" reproduction, as you said. This is much easier than trying to modelise the nebula from theory. So any idea how to generate random "filaments" structures made of points only ? The goal is to get something which looks like the Crab nebula.... – Cham Mar 08 '13 at 15:55
  • See my first message. Nice picture of the Crab ! Notice the oblateness of the global spheroid. The model should be made of points only. To each point, I'll attach a sprite (small blob-like picture), with a small size, like the models I presented on the Celestia forum (see the link on my first message). – Cham Mar 08 '13 at 16:49
  • So the question is: how to generate a point cloud which looks vaguely like the image? – Szabolcs Mar 08 '13 at 17:03
  • @Szabolcs Yes ! My problem is to generate the random filaments. I don't know how to define some statistical distribution with special correlations, which would produce those fibers, lines and filaments. I guess that the distribution must have a memory of the previously generated points. They can't be all independant. – Cham Mar 08 '13 at 17:10
  • 4
    @Cham The problem with your question is that, at this stage, it doesn't have much to do with Mathematica. First you'd need to come up with some ideas on how to do it in theory, and then ask about how to implement that in Mathematica. But I imagine people may get excited by this question so there may be answers. Still, it would be useful to mention some ideas about how to do it first. Have you googled for this? I found this blog post (the title is misleading, it's not physical simulation, just art) – Szabolcs Mar 08 '13 at 17:14
  • @Szabolcs : the problem is also that I don't have any idea how to describe the random filaments in a geometrical way, on paper. It's not obvious to me, while it's very easy to define the global spheroidal shape (oblate shell). I tried to define a straight line, randomly oriented, and generate points on some part of it. But the Mathematica code turns into a mess, and it doesn't work well. I need to generate the lines from some statistical distribution, but I don't know which one and how to define it in Mathematica – Cham Mar 08 '13 at 17:19
  • @Cham I'd say forget about point clouds for now, and just generate density maps. It will be easy to create cloud points from that later. Take a look at the blog post I linked to. There are Mathematica implementations of Perlin noise somewhere in the docs and also on this site. – Szabolcs Mar 08 '13 at 17:21
  • 2
    @whuber Filamentation in a supernova remnant like the Crab would mostly be caused by the Rayleigh-Taylor instability. In fact, the Wikipedia article on the Rayleigh-Taylor instability begins with the exact same picture of the Crab. It probably isn't worth it to do some kind of 3D fluid simulation just to make something that looks nice though - I think that Szabolc's idea of using DLA is a good one. – KAI Mar 08 '13 at 19:52
  • @KAI Thank you very much for the explanation! But why wouldn't a 3D fluid simulation be exactly what one would want to do in order to produce a realistic solution? If the problem is that it would take a lot of effort or computation, at least that would provide a target to aim for and a standard for evaluating the quality of any solutions--neither of which we currently have. – whuber Mar 08 '13 at 19:55
  • 2
    @whuber It would be interesting to see if a fluid dynamical simulation could give anything reasonable, but this is definitely not something easy with instabilities present ... also, for actually modelling a nebula one should use relativistic fluid dynamics, which brings its own very special can of worms and stability problems. But this is just a side comment. – Szabolcs Mar 08 '13 at 20:03
  • @whuber Well yes, a 3D fluid simulation is what you would do if you cared about the details of the solution. The question was just about making something that looked nice though, so doing a full simulation might be overkill. Anyway, a number of people have done that type of simulation before, but I'm not aware of any results that look really nice in 3D (a lot of these simulations were done quite a while ago). – KAI Mar 08 '13 at 20:04
  • 2
    @Szablocs The expansion velocity of supernova remnants generally ranges from a few hundred to maybe around 10,000 km/sec. That's fast, but not quite fast enough that I think you would have to use relativistic fluid dynamics. If you were trying to model the supernova expansion itself, yes, there would be all kinds of weird physics (which is why 3D supernova modeling is a cottage industry now). But the subsequent evolution of the remnant should be non relativistic. – KAI Mar 08 '13 at 20:09
  • 5
    @whuber I think the entire question could be written in shorthand as "how can I produce images similar to this, without numerically solving for the dynamical evolution of the nebulae"... However, I do agree that the question does not clearly state which properties of that image are supposed to be reproduced. It seems underspecified. – acl Mar 08 '13 at 21:59
  • @acl Precisely. – whuber Mar 08 '13 at 22:01
  • 1
    I've edited my first message to make the idea a bit more precise. As I wrote, the model shouldn't be very realistic. It's an artistic rendition of some of the main features of the Crab nebula (long filaments structures with large void between them). The goal is to define a list of cartesian coordinates (X, Y, Z) for the whole distribution in 3D. A small colorfull sprite would then be associated to each point in the list, so as to produce a volumetric effect. The model could then be visited in full 3D glory in Celestia. – Cham Mar 09 '13 at 02:25
  • @Cham, I added a bit more explanation in my answer. Hope that helps. – Simon Woods Mar 10 '13 at 19:58
  • I added some DLA code to my answer. I don't find the result better than the rest but you may still be interested. – Szabolcs Mar 12 '13 at 15:40
  • The "Nebulae models for Celestia" link is broken. – shrx May 06 '15 at 20:11

5 Answers5

37

Here's an attempt in which I start with a set of "void points", which will be the centres of the gaps between filaments. The stars are then created as an initially random distribution, and are repeatedly nudged away from their nearest void point. Or, to look at it another way, they are attracted towards the edges of the Voronoi cells defined by the void points. There is also a contraction at each iteration to prevent stars from escaping towards infinity.

Update: My original code used ParallelMap which turned out to be considerably slower than plain old Map. Thanks to @halmir for pointing that out. I should have checked. Following that observation, a couple of other optimisations emerged. The updated code below includes simplification of the filamentation function using Expand, and performs each iteration step on all the points at once (where the original code did the full set of iterations on each point in turn). Finally, I have used ParallelTable to distribute the calculation across all the CPU cores (and this time I have checked that it is faster that way...) The filementation code now runs in a couple of seconds on my 4-core machine.

A bit more explanation as requested:

In the code below the first two lines create random points in the sphere (actually it would be better to use the OP's Coords function in the question for this bit, as it provides better control of the point density).

The function f embodies the the filamentation. At each step, each point in pts moves 1% further away from its nearest void point (the 0.01) and then 0.25% closer to the origin (the 0.9975). This is repeated 200 times for each point.

The filamentation process reduces the size of the point cloud from the initial radius of 1 to something around 0.75. In the Graphics3D output the points are coloured according to their distance from the origin, the factor of 1.3 in the colour function is simply to there to compensate for the contraction.

voidpts = Select[RandomReal[{-1, 1}, {1000, 3}], Norm[#] <= 1 &];
pts = Select[RandomReal[{-1, 1}, {10000, 3}], Norm[#] <= 1 &];
nf = Nearest[voidpts];

f = Evaluate[Expand[0.9975 (#1 + 0.01 (#1 - #2))]] &;

DistributeDefinitions[nf, f];
pts = Developer`ToPackedArray @ Drop[pts, Length[pts] ~ Mod ~ $KernelCount];

pts = Join @@ ParallelTable[Nest[f[#, (nf /@ #)[[All, 1]]] &, p, 200],
    {p, Partition[pts, Length[pts]/$KernelCount]}];

cf = ColorData["SunsetColors"];
Graphics3D[{PointSize[0.005], {cf[Norm[1.3 #]], Sphere[#, 0.005]} & /@ pts}, 
  Boxed -> False, Background -> Black, Lighting -> "Neutral", 
  SphericalRegion -> True, ViewPoint -> {0.75, -0.75, 0.75}, ViewAngle -> 0.7]

enter image description here

In principle you could muck around with the initial distribution of void points to get larger voids and better defined filaments in the centre, as appears to be the case in the original image. Unfortunately the code is rather slow so it's not much fun to experiment with.

Here's the gratuitous animation:

enter image description here

To create an ouput list of the form {{X1, Y1, Z1, R1, G1, B1}, {X2, Y2, Z2, R2, G2, B2}, {X3, Y3, Z3, R3, G3, B3}, ...} you could do something like:

coords = Join[#, List @@ cf[1.3 Norm[#]]] & /@ pts;
Simon Woods
  • 84,945
  • 8
  • 175
  • 324
  • 2
    that's pretty neat! It s also actually close to how large scale structure filaments are gravitationally generated. – chris Mar 09 '13 at 16:43
  • The end result is very nice (+1) - and it shows you don't have to be physically accurate to get realistic looking output. That's how I understood the question: get something realistic without doing a full ab-initio simulation. – Jens Mar 09 '13 at 16:56
  • MY GOD ! This is what I need ! I'll try the code in the following minutes, trying to understand it ! 8*) – Cham Mar 09 '13 at 17:22
  • I now have tons of questions about this (LOL). The code is working in Mma 7.0, but I don't see any filaments yet. It's just a large sphere of random balls. Also, how can I extract the cartesian coordinates and the color as RGB numbers ? – Cham Mar 09 '13 at 17:27
  • @Simon Woods : Sorry if I'm such a noob, but I need some explanations for the various parameters in the code above. I'm yet unable to see any filaments structures ; I'm getting random balls scatered in a large sphere. A few more questions : how can we make the whole distribution oblate ? It needs an oblateness parameter to squash the sphere like the Crab nebula. Also, I need to extract the data to a text file, into a list of cartesian coordinates followed by the RGB numbers. Is it possible for you to show a complete Mma7.0 code with comments, so we could understand the parameters ? – Cham Mar 09 '13 at 17:43
  • Just to say that this may trigger a "revolution" in the Celestia universe ! 8-) – Cham Mar 09 '13 at 17:48
  • @Cham, did you change any parameters or does the code, as written, not seem to work in version 7? I am on version 8 here. – Simon Woods Mar 09 '13 at 18:27
  • +1, Nice job @Simon. @Cham an easy way to squash the sphere is to use the BoxRatios option. Or else just specify the initial points more carefully. Also, since you want just the colours and coordinates, grab the first list inside that Graphics3D (without the PointSize) and save it as a separate list. A few simple list-editing operations and you're ready to export. – cormullion Mar 09 '13 at 18:28
  • @Simon Woods : I did used your code above directly in Mma 7.0. It's working (i.e. no error messages), but I don't get the same distribution as on your picture above (no filaments). I played with the various parameters, but I'm still getting a random distribution of balls, that looks almost uniform (still no filaments). – Cham Mar 09 '13 at 18:35
  • Maybe that code isn't a proper version for Mma 7.0 ? Is there another way to generate the same kind of distribution, compatible with v7.0 ? It is clear that what Simon showed here, is exactly what I'm looking for. – Cham Mar 09 '13 at 18:46
  • @Cham, I think if you start with an oblate distribution the general shape should be retained. Try e.g. using Norm[# / {1,1,0.5}] in the expressions for voidpts and pts. I guess the bigger problem is that you're not getting the right result in v7. – Simon Woods Mar 09 '13 at 19:03
  • @Cham, you need to add the line DistributeDefinitions[nf] after defining nf for it to work in version 7 (thanks to @rm-rf and @Oleksandr for that) – Simon Woods Mar 09 '13 at 19:49
  • @Simon : It works ! I now see the filaments. I have to play more with this. Oblateness works great too. :-) – Cham Mar 09 '13 at 20:02
  • @Simon, how can we change the color, from the center to the edge of the distribution ? For example, you want to draw the central points in light blue, and have a continuous gradation to red on the edges. – Cham Mar 09 '13 at 20:06
  • @Cham, you need to change the color function. Try something like cf = Blend[{Cyan,Red},#]& Look up ColorData, ColorFunction and Blend in the documentation - you'll get the idea. – Simon Woods Mar 09 '13 at 20:11
  • This is getting very exciting, Simon. I'm not very familiar with this way of coding Mathematica, so I have more questions to adapt your code to my specific needs. Using your code above, how can I define a function which gives the three cartesian coordinates, followed by the RGB numbers, in a simple list like this : Coords = {{X1, Y1, Z1, R1, G1, B1}, {X2, Y2, Z2, R2, G2, B2}, {X3, Y3, Z3, R3, G3, B3}, ...} ? – Cham Mar 09 '13 at 20:30
  • @Cham, see the edit – Simon Woods Mar 09 '13 at 20:43
  • @Simon : My code is improving and almost fully functionnal now. What is the meaning of the three numbers in the following code line ? pts = ParallelMap[ Nest[0.9975 (# + 0.01 (# - First@nf[#])) &, #, 200] &, pts]; – Cham Mar 10 '13 at 13:38
  • @Simon : Thanks a lot for the new explanations ! It's crucial that I understand the filamentation process to built some nice models for Celestia. I have a last issue with the code now : the points tend to accumulate at some intersecting "lines". This is the source of an ugly sprite effect, since there's a sprite attached to each point. too many sprites in one place causes an un-natural rendering. Is there a way to control the density of points, for something more uniform along the filaments ? Or to reduce (dilute ?) the density of points at the intersections only ? – Cham Mar 10 '13 at 23:42
  • @Cham, I suppose the neat way would be to impose a short range repulsive force between the particles during the filamentation process, but that would make it really slow. The easiest fix is probably to post-process the point cloud, deleting points which are too close to other points. If you can't figure something out I'd suggest asking a new question, as this is a quite distinct problem from the original and the community is sure to have some good ideas. – Simon Woods Mar 11 '13 at 09:24
  • @Simon : yes, the code is already very slow. Ok, I'll ask a new question about points rejection. Thanks again for the help. – Cham Mar 11 '13 at 10:54
  • @Simon : I edited my first message (it's now much simpler). While I did started another question on the forum, I still think that the last question (see above) is relevant here. – Cham Mar 11 '13 at 14:53
  • @Simon on my machine with 2 kernal, parallel version is slower than normal Map. Maybe overhead is a larger than actual speed gain. Also you could gain more speed up by setting compiled version of iteration: cNest = Compile[{{x, Real, 1}, {n, _Integer}}, Block[{pt = x}, Do[ pt = 0.9975 (pt + 0.01 (pt - First@nf[pt])), {i, n}]; pt ], {{nf[], _Real, 2}} ]; Map[cNest[#, 200] &, pts] – halmir Mar 11 '13 at 17:25
  • @Simon also, you could just use Point instead of Sphere.cf = ColorData["SunsetColors"]; Graphics3D[{PointSize[0.0042], Point[pts, VertexColors -> (cf[Norm[1.3 #]] & /@ pts)]}, Boxed -> False, SphericalRegion -> True, ViewPoint -> {0.75, -0.75, 0.75}, ViewAngle -> 0.7, Background -> Black] – halmir Mar 11 '13 at 17:30
  • @halmir : is that compatible with Mathematica 7.0 ? I'm getting some error messages and the output looks like a dull oblate sphere (no filaments). – Cham Mar 11 '13 at 20:42
  • @halmir : please, could you post your complete version of Simon's code, as an answer ? I'm getting some troubles in making it to work. I'm getting this error message : Compile::ctyp2:, and I get a dull distribution of points... – Cham Mar 11 '13 at 20:51
  • @halmir : sorry, the error message above is uncomplete. It says Invalid type or rank specification in {x, Real,1} – Cham Mar 11 '13 at 21:14
  • ups, there's typo. it should be cNest = Compile[{{x, _Real, 1}, {n, _Integer}}, Block[{pt = x}, Do[ pt = 0.9975 (pt + 0.01 (pt - First@nf[pt])), {i, n}]; pt ], {{nf[], _Real, 2}} ]; – halmir Mar 12 '13 at 06:25
  • @halmir : your code works partly. I don't get any error messages, but I still get a dull distribution without filaments. Are you sure this is compatible with Mathematica 7.0 ? If not, what should be the code for that version ? – Cham Mar 12 '13 at 12:52
  • When I copy and paste under bar in the code is removed. I don't know why. I will post as answer.. – halmir Mar 12 '13 at 14:59
30

Update 2021

Astronomers have actually done the job for you with the real crab nebula:

http://www.cfht.hawaii.edu/en/news/HeartofTheCrab/out-outreach.mp4

enter image description here

This movie (be a bit patient) allows you to rotate around the observed nebulae as described here


If you use this code take the hessian of it and plot the map of the largest eigenvalues you get a nice filamentary map like the bottom right panel.

Mathematica graphics

see this reference (specifically pp 28 of the phd).

In mathematica it can be coded as follows

 nn = 256;u = GaussianRandomField[nn, 2, Function[k, k^-4]]//GaussianFilter[#, 4] & // Chop;
  Clear[f]; f[x_, y_] = ListInterpolation[u][x, y];
  Clear[d2f]; d2f[x_, y_] = {{D[f[x, y], {x, 2}],
        D[f[x, y], x, y]}, {D[f[x, y], x, y], D[f[x, y], {y, 2}]}};
  h = Table[d2f[x, y], {x, nn}, {y, nn}];
  ee = Table[Eigenvalues[h[[i, j, All, All]]], {i, nn}, {j, nn}];
  ee = Map[Max, Abs[ee], {2}];
  ee[[1 ;; nn - 1, 1 ;; nn - 1]] // Image // ImageAdjust

producing

Mathematica graphics

Removing the Abs for nn=512 yields

Mathematica graphics

Varying the power law and the smoothing would allow you to produce e.g.

Mathematica graphics

and if you mix the result of 3 such images

Transpose[{ee1, ee2, ee3}, {3, 1, 2}] // Image // ImageAdjust

Mathematica graphics

The same applies in 3D

nn = 64; u = GaussianRandomField[nn, 3, Function[k, k^-3]]//GaussianFilter[#, 6] & // Chop; 
Clear[f]; f[x_, y_, z_] = ListInterpolation[u][x, y, z];
Clear[d2f]; d2f[x_, y_, z_] = {
  {D[f[x, y, z], x, x], D[f[x, y, z], x, y], 
   D[f[x, y, z], x, z]}, {D[f[x, y, z], x, y], D[f[x, y, z], y, y], 
   D[f[x, y, z], y, z]},
  {D[f[x, y, z], x, z], D[f[x, y, z], y, z], D[f[x, y, z], z, z]}};
h = Table[d2f[x, y, z], {x, nn}, {y, nn}, {z, nn}];

ee = Table[ Eigenvalues[h[[i, j, k, All, All]]], {i, nn}, {j, nn}, {k, nn}]; ee = Map[Max, ee, {3}];

If we look at a slice in 3D

 ImageAdjust@Image3D[Exp[ee/StandardDeviation[Flatten[ee]]], Background -> Black]

Mathematica graphics

EDIT

Let us explore another venue, just because this is how cosmologist generate initial conditions for simulations. Let us generate a displacement field corresponding to the gradient of a Gaussian Random field:

nn = 256; u = 
 GaussianRandomField[nn, 2, Function[k, k^-4]] // 
   GaussianFilter[#, 4] & // Chop;
Clear[f]; f[x_, y_] = ListInterpolation[u][x, y];
df[x_, y_] = {D[f[x, y], x], D[f[x, y], y]};
g = Table[df[x, y], {x, nn}, {y, nn}]; g /= Max[g];
grid = Outer[{#1, #2} &, Range[nn], Range[nn]];
Map[Point, grid + g*15, {2}] // 
 Graphics[{AbsolutePointSize[0.1], #}] &

Mathematica graphics

and in 3D

nn = 32; u = GaussianRandomField[nn, 3, Function[k, k^-4]] //GaussianFilter[#, 4] & // Chop;
Clear[f]; f[x_, y_, z_] = ListInterpolation[u][x, y, z];
df[x_, y_, z_] = {D[f[x, y, z], x], D[f[x, y, z], y], D[f[x, y, z], z]};
g = Table[df[x, y, z], {x, nn}, {y, nn}, {z, nn}]; g /= Max[g];
grid = Table[{i, j, k}, {i, nn}, {j, nn}, {k, nn}];
Map[Point, grid + g*8, {2}] // Graphics3D[{AbsolutePointSize[0.1], #}] &

Mathematica graphics

Or to make it look good borrowing Simon's graphics primitive

cf = ColorData["SunsetColors"]
Graphics3D[{PointSize[0.1], {cf[#[[2]]/nn], Sphere[#, 0.2]} & /@ pts}, 
Boxed -> False, Background -> Black, Lighting -> "Neutral", SphericalRegion -> True]

Mathematica graphics

Now the above code is fast (and could be made faster while using FFT to compute the gradients).

Note that we can remove the regular low density region points as follows

 pts = MapThread[If[Norm[#1] > 1/3, 15 #1 + #2, {}] &, {g, grid}, 2] //
  Flatten[#, 1] & // Union // Rest;
 Map[Point, pts] // Graphics[{AbsolutePointSize[0.1], #}] &

Mathematica graphics

EDIT 2

If I steal the set of points from Simon above (I hope he doesn't mind!) and trace its 3D skeleton I get this (So the credit to this method lies with Simon Woods and Thierry Sousbie)

Mathematica graphics

or changing the so called level of persistence and increasing the number of drawn points in Simon's code:

Mathematica graphics

And its really 3D :-)

enter image description here

Note that the original 2D image can be analysed directly by the skeleton, channel per channel,

Mathematica graphics

which demonstrates that the filamentary structure is colour dependent, but I guess I am getting over-carried! :-)

COMMENT

To answer the OP, the skeleton is not currently implemented in mathematica. The closest current implementation is watershading:

nn = 512; u0 =  GaussianRandomField[nn, 2, Function[k, k^-3]]//GaussianFilter[#, 3] & // Chop;
u = u0 // GaussianFilter[#, 8] & // Chop;
Clear[f]; f[x_, y_] = ListInterpolation[u][x, y];
df[x_, y_] = {D[f[x, y], x], D[f[x, y], y]};
g = Table[df[x, y] // Sqrt[#.#] &, {x, nn}, {y, nn}];
im0 = u0 // Image;im1 = g // Image // WatershedComponents // Image;
ImageMultiply[im1, im0] // ImageAdjust

Mathematica graphics

May be in version 10 ;-)

chris
  • 22,860
  • 5
  • 60
  • 149
  • Is it going to be filamental in 3D as well? – Szabolcs Mar 08 '13 at 19:58
  • actually not as much as I anticipated ;-) – chris Mar 08 '13 at 22:38
  • Then how can you built a 3D distribution of simple points (i.e. cartesian coordinates) with this ? – Cham Mar 09 '13 at 00:08
  • The fibers density is much too high. There should be plenty of room (voids) between the filaments. From the codes above, is it possible to define a statistical distribution for some random X, Y, Z cartesian coordinates in 3D space ? – Cham Mar 09 '13 at 00:43
  • The last picture is very nice. How can we extract the cartesian coordinates of each points from this ? – Cham Mar 09 '13 at 17:29
  • the skeleton code produces a set of segments in ascii. – chris Mar 09 '13 at 17:41
  • I don't mind at all, particularly as you produced such stunning pictures! – Simon Woods Mar 09 '13 at 18:31
  • This is extremely interesting ! However, I don't think that the code would work on Mma7.0. Could you give a complete code which generates your last distribution in 3D ? – Cham Mar 09 '13 at 18:51
  • I will have to explore this too. It's really awesome ! – Cham Mar 09 '13 at 21:05
  • 2
    @Szabolcs once it is not buggy anymore it is fairly filamentary in 3D. (I had an spurious Abs[ee] in the previous version). It has to be in a way because my colleagues use it to delineate cosmic filaments. – chris Mar 09 '13 at 22:19
  • Skeleton processing of 3D data in mathematica: 61385 – shrx May 07 '15 at 13:25
20

Here is the modified version of Simon Woods answer. In my machine with 2 core, ParallelMap version is slower and you could gain a little bit of speed up by using compiled version of iteration:

voidpts = Select[RandomReal[{-1, 1}, {1000, 3}], Norm[#] <= 1 &];
pts = Select[RandomReal[{-1, 1}, {10000, 3}], Norm[#] <= 1 &];
nf = Nearest[voidpts];
DistributeDefinitions[nf];

cNest = Compile[{{x, _Real, 1}, {n, _Integer}}, 
         Block[{pt = x}, 
             Do[pt = 0.9975 (pt + 0.01 (pt - First@nf[pt])), {i, n}]; 
             pt], {{nf[_], _Real, 2}}];

The following is the timing I got:

In[75]:= pt1 = 
    ParallelMap[Nest[0.9975 (# + 0.01 (# - First@nf[#])) &, #, 100] &, 
     pts]; // AbsoluteTiming
Out[75]= {98.073226, Null}

In[85]:= pt2 = 
   Map[Nest[0.9975 (# + 0.01 (# - First@nf[#])) &, #, 100] &, 
    pts]; // AbsoluteTiming
Out[85]= {7.226970, Null}

In[86]:= pt3 = Map[cNest[#, 100] &, pts]; // AbsoluteTiming
Out[86]= {5.901947, Null}

In[80]:= pt1 == pt2 == pt3
Out[80]= True

I don't see the any reason to use Sphere, so I replace with Point to gain speed:

cf = ColorData["SunsetColors"]; 
Graphics3D[{AbsolutePointSize[2], 
     Point[pt1, VertexColors -> (cf[Norm[1.3 #]] & /@ pt1)]}, 
     Boxed -> False, SphericalRegion -> True, 
     ViewPoint -> {0.75, -0.75, 0.75}, ViewAngle -> 0.7, 
     Background -> Black]

enter image description here

To check how each iteration goes (for fun):

ptlist = Transpose[
      Map[NestList[0.9975 (# + 0.01 (# - First@nf[#])) &, #, 200] &, 
     pts]];

Manipulate[
  Graphics3D[{AbsolutePointSize[2], 
    Point[ptlist[[i]], 
    VertexColors -> (cf[Norm[1.3 #]] & /@ ptlist[[i]])]}, 
    Boxed -> False, SphericalRegion -> True, 
    ViewPoint -> {0.75, -0.75, 0.75}, ViewAngle -> 0.7, 
    Background -> Black, PlotRange -> {-1.5, 1.5}], 
 {i, 1, Length[ptlist], 1}]
halmir
  • 15,082
  • 37
  • 53
  • Nice observation! ParallelMap is slower for me too, I didn't think to check. Weird that Compile gives a speed up even though there is a call back to the kernel for the NearestFunction. – Simon Woods Mar 12 '13 at 19:06
  • @halmir : in your code above, I don't see any definition of "npts". What is it ? I had to change it to "pts" to make the code working. It is MUCH faster now, by the way. – Cham Mar 12 '13 at 19:51
  • It was typo again. It meant to be pt1, pt2, or pt3. I fixed it. Sorry about that. – halmir Mar 12 '13 at 20:33
  • @halmir : It's all good ! Thanks A LOT for your help, it's really appreciated ! The code is MUCH faster than the previous version. – Cham Mar 12 '13 at 20:51
  • Now we just need to get this up to 10 votes, so Simon can get a gold badge. Everyone needs more gold. :) – rcollyer Mar 12 '13 at 20:54
  • I agree ! Simon was extremely helpfull to me. Without him, my project would not have been realized ! – Cham Mar 12 '13 at 21:03
  • @rcollyer one vote left :P – Kuba Jan 17 '14 at 20:22
12

Update: The idea below is not very good because it makes surfaces instead of filaments and does not create a fractal-like structure.

Another idea would be to make use a process called diffusion limited aggregation. It is easy to simulate (though Mathematica will probably be slow for a 3D simulation), and it is often the process behind fractal like filaments you find in nature (such as the patterns you get when you press together two sheets of glass with a viscous liquid inbetween, then pull them apart, ice flowers on the window in the winter, and some tree like thing produced by some precipitation reactions. Unfortunately I must run now, so no time to say more.

This is a 10000-point 3D DLA result with an initial condition of a point in the middle:

Mathematica graphics

Here's a compiled function for a DLA simulation:

cf = Compile[{{in, _Integer, 3}, {steps, _Integer, 
    2}, {start, _Integer, 1}, {count, _Integer}},
  Module[{prevpos = start, pos = start, xmax, ymax, zmax, arr = in},
   {xmax, ymax, zmax} = Dimensions[arr];
   Do[
    pos = start;
    While[
     1 <= pos[[1]] <= xmax && 1 <= pos[[2]] <= ymax && 
      1 <= pos[[3]] <= zmax &&

      arr[[pos[[1]], pos[[2]], pos[[3]]]] == 0,
     prevpos = pos;
     pos += RandomChoice[steps]
     ];
    If[1 <= pos[[1]] <= xmax && 1 <= pos[[2]] <= ymax && 
      1 <= pos[[3]] <= zmax,
     arr[[prevpos[[1]], prevpos[[2]], prevpos[[3]]]] = 1],

    {count}];
   arr
   ],
  {{pos, _Integer, 1}, {prevpos, _Integer, 1}},
  CompilationTarget -> "C", RuntimeOptions -> "Speed"
  ]

steps = Select[Tuples[{-1, 0, 1}, {3}], 0 < Norm[#] < Sqrt[3] &]

(* careful, computation time proportional to between n^5 - n^6 *)
n = 50;

ini = DiskMatrix[n {1, 1, 1}, 2 n + 3] - 
   DiskMatrix[(n - 2) {1, 1, 1}, 2 n + 3];

Dimensions[ini]

AbsoluteTiming[res = cf[ini, steps, {n, n, n} + 1, 300000];]

(* works only in v9: *)
ImageAdjust@
 Image3D[res - ini, ColorFunction -> (GrayLevel[1 - #, .1 #] &), 
  SphericalRegion -> True]

(* from here it works in v7 *)
pts = Position[res - ini, 1];

(* shuffle around the points a bit to get rid of the grid effect *)
rpts = RandomVariate[NormalDistribution[0, .2], Dimensions[pts]] + pts;

max = Max[Norm[# - {n, n, n} - 2] & /@ rpts]

Graphics3D[{Opacity[.2], {ColorData["RedBlueTones"][
      1 - Norm[# - {n, n, n} - 2]/max], Point[#]} & /@ 
   Select[rpts, Norm[# - {n, n, n} - 2] < n - 10 &]}, Boxed -> False, 
 SphericalRegion -> True, Background -> Black]

This is a 2D DLS result with a spherical seed and some colouring:

Mathematica graphics


This might be a start for generating filaments (it's not meant as a final answer):

size = 120;
scale = 7;

ColorNegate@
 ImageAdjust@
  Image3D[(Abs@
      LowpassFilter[RandomReal[{-1, 1}, {size, size, size}], 
       1/scale])[[scale ;; -scale, scale ;; -scale, scale ;; -scale]],
    ColorFunction -> (GrayLevel[#, 0.05 #] &), Background -> Black]

After obtaining the image, I adjusted the gamma a bit interactively (right click, adjust image):

Mathematica graphics

The idea was to create a random image with values between {-1,1}, apply a low-pass filter, then take the absolute value as described here to create filaments.

This has two problems: 1. it appears to have more 2D surfaces than true 1D filaments. 2. it doesn't show the fractal like structure that we see in the Crab nebula image, i.e. that there are filaments at every size scale.

Note to others: feel free to borrow anything from here for your own answer (if you find this of use).

Szabolcs
  • 234,956
  • 30
  • 623
  • 1,263
  • 1
  • 1
    Of course this has nothing to do with astronomy, it's just making pretty pictures! – Szabolcs Mar 08 '13 at 18:12
  • 1
    as an astronomer, I would point out that your crab nebula is a bit squarish to be realistic ;-) – chris Mar 08 '13 at 19:00
  • @chri that was just for the filaments, a merely a sub-problem – Szabolcs Mar 08 '13 at 19:26
  • I'm unable to compile the code above in Mathematica 7.0. – Cham Mar 09 '13 at 00:10
  • @Cham I used v9-only functions. Please update the question and mention that you need a solution that works in v7. – Szabolcs Mar 09 '13 at 01:16
  • @Szabolcs : Is it possible to define a statistical distribution for some cartesian variables X, Y, Z (compatible with Mma 7.0), from your solution ? – Cham Mar 09 '13 at 02:12
  • @Cham The simplest way to draw points from a distribution described by a density (what both I and chris have computed) is probably using RandomChoice[], with weights. It's not the fastest but it will work. I think that for your problem, it's much easier to generate the density first, and then draw points from a distribution with that density. – Szabolcs Mar 09 '13 at 02:54
  • @Szabolcs : Hey ! That first 3D graphics with points above is HIGHLY interesting to me ! 8-) What is the Mma 7.0 code that do this ? I already see that I could generate filaments with this plot. I feel we're close to a proper solution ! Please ? – Cham Mar 09 '13 at 13:33
  • @Cham It's been created using diffusion limited aggregation, but the code I used is extremely inefficient. It's here if you're curious. I meant to rewrite it in C using a cubic lattice to make it much faster, but I was too lazy ... It'd also be better to use a circular seed instead of a point at the centre, like in the 2D figure on page 10 here – Szabolcs Mar 09 '13 at 21:55
12

Since others have tried their luck, I couldn't resist adding this very simple approach:

na = 50;
n = 400;
xmin = -200;
xmax = 200;
ymin = xmin;
ymax = xmax;
k = Map[{Cos[#], Sin[#]} &, RandomReal[{-Pi, Pi}, na]];
\[Phi] = RandomReal[{-Pi, Pi}, na];
grid = Tuples[
   Range[#1, #2, (#2 - #1)/(n - 1)] & @@@ {{xmin, xmax}, {ymin, 
      ymax}}];
field = Plus @@ MapThread[Sin[ grid.# + #2] &, {k, \[Phi]}];
GaussianFilter[
 Colorize[Image[Rescale@Partition[Abs[field], n]], 
  ColorFunction -> GrayLevel], 4]

filaments

A reference for this idea is here. I made it simpler to produce a nice effect while working purely with real-valued functions on a grid. The parameter na sets the number of randomly chosen plane wave directions and phases which are then superimposed and plotted on a grid of size n. The dimensions xmin etc. are chosen to make the wavelength short compared to the plot range, so that a Gaussian filter can average out the short wavelength nodal structure and leave only the filaments.

On my machine AbsoluteTiming yields 0.12 for the above plot.

Jens
  • 97,245
  • 7
  • 213
  • 499
  • If you generalize it to 3D, are the filaments still going to be 1D (not 2D) structures? – Szabolcs Mar 09 '13 at 05:24
  • I don't think you get this much structure in 3D. All the interesting examples for filaments that I recall are 2D types of waves. I don't think I'll try to give an answer for 3D because it's too open-ended. – Jens Mar 09 '13 at 06:11