18
$\begingroup$

I have the following code which draws a graphic, and colors the regions in it in different colors:

g = {{Circle[#, 1 - Sqrt[2]/2] & /@ Tuples[0.5 {-1, 1}, 2]}, 
     {Circle[{0, 0}, 1]}};

graphic = Graphics[GeometricTransformation[g, Tuples[Range[-2, 2], 2]]]

starting image

image = Rasterize[graphic, "Image", ImageSize -> 800];

regions = MorphologicalComponents[image, 0.9];

newimage = ImageResize[
   Colorize[regions, 
      ColorFunction -> (ColorData["SolarColors", Mod[Sin[#^2], 1]] &), 
      ColorFunctionScaling -> False], 
   Scaled[0.5]
   ]

colored version

I'd like the color for the region that is outside all of the circles to be black. What's the best way to do this?

(The ideal solution would still work if I set PlotRangePadding -> None on the original graphic, which will cause the exterior to be several components instead of just one.)

$\endgroup$
4
  • $\begingroup$ I've done something like this as part of the answer linked here - may have time to adapt it for your question in a few minutes... $\endgroup$
    – Jens
    Jul 2, 2012 at 3:18
  • $\begingroup$ The simplest answer is DeleteBorderComponents - see my updated answer. Still, combining it with an alpha channel gives more flexibility, so I defined a new function deleteBorder for that purpose. $\endgroup$
    – Jens
    Jul 2, 2012 at 18:51
  • $\begingroup$ @Jens Interesting, since as far as I can tell DeleteBorderComponents claims to only work for binary images (and matrices.) $\endgroup$ Jul 2, 2012 at 19:35
  • $\begingroup$ Yes indeed - maybe it works for your image because of the black lines bounding the inside. Anyway, it does work for your case, but I think the deleteBorder function I defined is of much more general utility. $\endgroup$
    – Jens
    Jul 2, 2012 at 22:08

4 Answers 4

10
$\begingroup$

Original attempt, experimentation

OK, I have an answer based on an alpha channel but it required more morphological manipulations first.

The good thing is that it should work for other shapes, and requires only the final product newimage. However, to make it work reliably, I needed to increase the image size to avoid some boundary lines bleeding into each other.

newimage = 
 ImageResize[
  Colorize[regions, 
   ColorFunction -> (ColorData["SolarColors", Mod[Sin[#^2], 1]] &), 
   ColorFunctionScaling -> False], Scaled[1]]

Here is the image processing:

imAlpha = ImageCompose[
  newimage,
  SetAlphaChannel[#, ColorNegate[#]] &@Erosion[
    FillingTransform@SelectComponents[
      MorphologicalPerimeter[
       Binarize[newimage],
       CornerNeighbors -> False
       ],
      "EnclosingComponentCount", # == 1 &
      ], .5
    ]
  ]

black border

Edit: streamlined version

Now I combined my approaches from above (which relies on SelectComponents) with the border color selection from this earlier answer (using Binarize with a custom test function) to make a function deleteBorder that can remove a homogeneous border background from an arbitrary image im:

Options[deleteBorder] = {Tolerance -> .005};
deleteBorder[im_, OptionsPattern[]] := Module[{
   rgb = ImageData[ColorConvert[im, "RGB"]][[2, 2]],
   tol = OptionValue[Tolerance]
   },
  SetAlphaChannel[im, ColorNegate[#]] &@
   SelectComponents[Binarize[im, Norm[# - rgb] <= tol &],
    "EnclosingComponentCount", # == 0 &
    ]
  ]

It works by setting the alpha channel to a binarized version of im in which the outermost region is transparent.

Here is another example:

im2 = Rasterize[
  ParametricPlot3D[{Sin[4 Pi t], Cos[3 Pi t], 
    Sin[2 Pi t + Pi/3]}, {t, 0, 4}, 
   PlotStyle -> {Brown, Tube[.03]}, Lighting -> "Neutral", 
   Background -> Green, Boxed -> False, Axes -> False], "Image", 
  ImageSize -> 450]

swirlsGreen

deleteBorder[im2]

swirlsGreenAlpha

You can now superimpose this result on any other image and the border region (appearing white above) will show as transparent. E.g., try Show[%, Background -> Red].

To show that this works independently of the background color, let's do:

im3 = Rasterize[
  ParametricPlot3D[{Sin[4 Pi t], Cos[3 Pi t], 
    Sin[2 Pi t + Pi/3]}, {t, 0, 4}, 
   PlotStyle -> {Yellow, Tube[.03]}, Lighting -> "Neutral", 
   Background -> Magenta, Boxed -> False, Axes -> False], "Image", 
  ImageSize -> 450]

swirlmagenta

Show[deleteBorder[im3], Background -> Red]

swirlRed

Edit 2: combining with DeleteBorderComponents

The question has an image whose border has different uniform colors in different pieces and perhaps these pieces are disjoint, as shown here:

g = {{Circle[#, 1 - Sqrt[2]/2] & /@ 
     Tuples[0.5 {-1, 1}, 2]}, {Circle[{0, 0}, 1]}};
graphic = 
  Graphics[GeometricTransformation[g, Tuples[Range[-2, 2], 2]], 
   PlotRangePadding -> None];
image = Rasterize[graphic, "Image", ImageSize -> 800];
regions = MorphologicalComponents[image, 0.9];
newimage = 
  ImageResize[
   Colorize[regions, 
    ColorFunction -> (ColorData["SolarColors", Mod[Sin[#^2], 1]] &), 
    ColorFunctionScaling -> False], Scaled[1]];

borderTHin

Here is a very simple way to do make the border black:

DeleteBorderComponents[newimage]

thinbackground

Here I use the built-in function DeleteBorderComponents to immediately get rid of the border and set its color to Black.

That's all there is to it - this basically is the simplest answer to the original question! But it's the boring route.

With my function deleteBorder you can go much further than this:

Show[deleteBorder[DeleteBorderComponents[newimage]], 
 Background -> Blue]

blueThinBorder

deleteBorder replaces Black by a transparent region. This actually makes the black lines between the circles transparent too, and we can now still choose an arbitrary background color (Blue), or superimpose the result onto a different image.

$\endgroup$
11
$\begingroup$

You can use the image processing tools to do this. In short, you use FillingTransform to fill the inner parts and create a mask and then use ImageApply to set the pixels in the region given by mask to your desired colour.

mask = ImageResize[FillingTransform[ColorNegate@Binarize@image] // ColorNegate, [email protected]];

Clear[f]
f[__] := {0, 0, 1}; (* RGB values of the chosen colour *)
ImageApply[f, newimage, Masking -> mask]

enter image description here

However in this specific case, with the way MorphologicalComponents works, the space outside all the circles in your figure will be assigned the label 1. So you could simply do something like:

Clear@f
f[1] = Blue;
f[x_] := ColorData["SolarColors", Mod[Sin[x^2], 1]]
Colorize[regions, ColorFunction -> f, ColorFunctionScaling -> False]
$\endgroup$
2
  • $\begingroup$ This is what I'd come up with, although it feels hacky to me. I edited the question slightly (but this is still a valid answer.) $\endgroup$ Jul 2, 2012 at 3:29
  • $\begingroup$ That's so easy. My approach would have used an alpha channel but that causes colors in the middle to be transparent too if they're the same as outside. So +1. $\endgroup$
    – Jens
    Jul 2, 2012 at 3:29
10
$\begingroup$

The function ComponentMeasurements has the option "BorderComponents" which when set to False will ignore components that are connected to the border. You could use this to filter for the internal components only. For example

internal = ComponentMeasurements[regions, "Label", "BorderComponents" -> False];

newimage = 
 ImageResize[
  Colorize[regions /. Append[internal, _Integer -> 0], 
   ColorFunction -> (ColorData["SolarColors", Mod[Sin[#^2], 1]] &), 
   ColorFunctionScaling -> False], Scaled[0.5]]

Mathematica graphics

This also works when multiple regions are touching the border, e.g.

graphic = Graphics[GeometricTransformation[g, Tuples[Range[-2, 2], 2]], 
  PlotRangePadding -> None]

image = Rasterize[graphic, "Image", ImageSize -> 800];
regions = MorphologicalComponents[image, 0.9];
internal = ComponentMeasurements[regions, "Label", "BorderComponents" -> False];

newimage = ImageResize[
  Colorize[regions /. Append[internal, _Integer -> 0], 
   ColorFunction -> (ColorData["SolarColors", Mod[Sin[#^2], 1]] &), 
   ColorFunctionScaling -> False], Scaled[0.5]]

Mathematica graphics

$\endgroup$
8
$\begingroup$

As R.M observed the border region is numbered 1. We can therefore do:

ArrayPlot[regions /. 1 -> Red]

Mathematica graphics

MatrixPlot[regions /. 1 -> Black, Frame -> False]

Mathematica graphics

Version 7 does not have the Colorize function but we can whip up something:

rls = # -> RGBColor @@ RandomReal[1, 3] & /@ Union @@ regions;
PrependTo[rls, 1 -> Black];

MatrixPlot[regions /. rls, Frame -> False]

Mathematica graphics

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.