6

I want a function to return the intensity of one pixel notwithstanding the Color Space of the source image.

I wrote:

getIntensity[i_Image, pos_List] :=
 ColorConvert[Flatten@{PixelValue[i, pos]}, ImageColorSpace[i] -> "Grayscale"]

but it looks clumsy and there is probably a better way that I'm overlooking.

Test cases:

i = ExampleData[{"TestImage", "Lena"}];
ig = ColorConvert[i, "Grayscale"];
getIntensity[i, {1, 1}]
getIntensity[ig, {1, 1}]

(*
  {0.172275}
  {0.172549}
*)

Note: Not very important right now, but it would be nice if the position could be specified as a rectangular region {a ;; b, c ;; d}

Dr. belisarius
  • 115,881
  • 13
  • 203
  • 453
  • {a ;; b, c ;; d} will return the the intensity of every pixel or a mean value? – yode Mar 18 '16 at 06:34
  • @yode A list (matrix) with the intensities for every pixel – Dr. belisarius Mar 18 '16 at 06:47
  • Incredible, how unterse it is to transform a Span specification to a Take specification! Working on this right now. – LLlAMnYP Mar 18 '16 at 09:46
  • Here's a function. Should work in v9, though ColorConvert got updated in v10. f[i_Image, c_List] := ImageTake[i, Sequence @@ (Replace[ c, {h___, n_Integer, t___} :> {h, {n}, t}] /. Span -> List)] // ColorConvert[#, "Grayscale"] & // ImageData I'm afraid, taking a span specification forces me to use a very clumsy replacement rule. – LLlAMnYP Mar 18 '16 at 09:49
  • 1
    The golfed down version that takes a Take kind of part specification looks like this: f[i_, c__] := ImageData@ColorConvert[ImageTake[i, c], "Grayscale"] – LLlAMnYP Mar 18 '16 at 09:51
  • @LLlAMnYP You should post a answer I think.It may be more suitable for our doctor's v9 than my solution. – yode Mar 18 '16 at 10:07

2 Answers2

5

Reposting my comment as an answer.

This has the same caveat, as @yode mentioned: pixel coordinates (from top to bottom, then from left to right) vs "normal coordinates" (left to right, then upwards).

Perhaps this is terser, although it always returns a 2-d array, even if only of one element. The golfed down version doesn't take a Span:

f[i_, c__] := ImageData@ColorConvert[ImageTake[i, c], "Grayscale"]

Usage:

f[image, {row1, row2, step(optional)}, {col1, col2, step(optional)}]

the part specification works exactly like ImageTake.

The ungolfed version accepts Belisarius' {a ;; b, c ;; d} desire (also with the step size, if need be):

f[i_Image, c_List] := ImageTake[i,
  Sequence @@ (Replace[ c, {h___, n_Integer, t___} :> {h, {n}, t}] /. Span -> List)] // 
    ColorConvert[#, "Grayscale"] & // ImageData

The replacement rule to accept Span is rather clumsy and can probably be improved. An integer specification will be equivalent to n ;; n ;; 1.

If we extend the code as

f[i_, c__] := ImageData@ColorConvert[ImageTake[
                ImageRotate[i, -Pi/2], c], "Grayscale"]

(or similarly in the ungolfed version), we'll get the "normal" coordinates. In this case the part specification will be first the x-range, then the y-range from bottom to top. It might be counterintuitive though, because the returned array will be essentially the image rotated clockwise.

Adding in the discussion in the comments: the image standard coordinates know nothing about the negative specifications, so I can probably assume that the Span-like argument will take only positive values. Because ImageRotate is horribly inefficient to simply get a coordinate transformation, and @Belisarius hinted at the use of ImageTrim, there's a very simple workaround available (no 3-argument Span specification though, sorry).

f[i_, c:{(_Span|_Integer)..}] := ImageData@ColorConvert[ImageTrim[i, Thread[List@@@c]-.5], "Grayscale"]

I have no idea, why Belisarius' two approaches (convert to grayscale, then get pixel value or vice versa) give different results. Mine is consistent with getIntensity[ig, {1, 1}].

LLlAMnYP
  • 11,486
  • 26
  • 65
  • On the same line ImageData@ColorConvert[ImageTrim[i, {c - .5, c - .5}], "Grayscale"] uses image standard coordinates – Dr. belisarius Mar 18 '16 at 13:56
  • Yes, that is vastly better. As @nikie alerted me over here, ImageRotate is a horribly inefficient way to do the coordinate transformation, although very straighforward. – LLlAMnYP Mar 18 '16 at 14:10
  • @Dr.belisarius by the way, seeing as we're dealing with image standard coordinates, are negative specifications to refer to "{x,y} from last" at all relevant here? Because if not, there's a lot less corner cases to deal with, when implementing the expected behavior of Span. – LLlAMnYP Mar 18 '16 at 14:13
  • I suspected the span thing could be cumbersome, so the final note on the question. I like your answer, let the span rest in peace. – Dr. belisarius Mar 18 '16 at 14:20
  • @Dr.belisarius actually if we don't use negative values for Span, it's super easy: with the coordinates specified as a:{(_Span|_Integer)..} feed them to ImageTrim as Thread[List@@@a]-.5 – LLlAMnYP Mar 18 '16 at 14:22
  • May I suggest you to pack the ImageTrim and that last comment in your answer? – Dr. belisarius Mar 18 '16 at 14:25
  • 1
    @Dr.belisarius just did, though the answer is a bit cluttered now. No time to tidy up though, maybe some other time. – LLlAMnYP Mar 18 '16 at 14:29
4
fun[i_Image, rect_List] := 
 ImageMeasurements[i, "MeanIntensity", 
    Masking -> SparseArray[{# -> 1}, ImageDimensions[i]]] & /@ 
  Catenate@CoordinateBoundingBoxArray[rect]

Usage:

i = ExampleData[{"TestImage", "Lena"}];
fun[i, {{2, 3}, {4, 5}}]

{0.635294, 0.631373, 0.635294, 0.635294, 0.631373, 0.635294, 0.635294, 0.631373, 0.635294}


Update: I'm very sorry I make a mistake in the Image Coordinate Systems,And the code above serve a Index Coordinates.So we cann't use SparseArray as a mask unless you have a need in Index Coordinates.But this time let's face up to a Pixel-aligned Image Coordinates

When your object is a list of pixel:

fun1[i_Image, pos_List] := 
 ImageMeasurements[i, "MeanIntensity", 
    Masking -> 
     ReplacePixelValue[
      ConstantImage[0, ImageDimensions[i]], {# -> 1}]] & /@ pos

Usage:

fun1[i, {{1, 1}(*your test case*), {5, 6}, {10, 10}}]
fun1[ig, {{1, 1}(*your test case*), {5, 6}, {10, 10}}]

{0.172549, 0.211765, 0.247059}

{0.172549, 0.211765, 0.247059}

If your object is a region of pixel rectange:

fun2[i_Image, rect_List] := 
 ImageMeasurements[i, "MeanIntensity", 
    Masking -> 
     ReplacePixelValue[
      ConstantImage[0, ImageDimensions[i]], {# -> 1}]] & /@ 
  Catenate@CoordinateBoundingBoxArray[rect]

Usage:

fun2[i, {{1, 1}, {5, 8}}]

{0.172549, 0.172549, 0.168627, 0.180392, 0.211765, 0.188235, 0.184314, 0.188235, 0.172549, 0.172549, 0.168627, 0.180392, 0.211765, 0.188235, 0.184314, 0.188235, 0.215686, 0.215686, 0.196078, 0.196078, 0.188235, 0.223529, 0.215686, 0.207843, 0.2, 0.2, 0.184314, 0.196078, 0.184314, 0.203922, 0.207843, 0.192157, 0.211765, 0.211765, 0.211765, 0.203922, 0.207843, 0.211765, 0.196078, 0.207843}

yode
  • 26,686
  • 4
  • 62
  • 167
  • 3
    I meant "terser". Like in more "brief, short, to the point, concise, succinct, crisp" :) – Dr. belisarius Mar 18 '16 at 07:05
  • Only these option/function have more letters.but is brief actually I think.aha~ – yode Mar 18 '16 at 07:09
  • In any case, I upvoted without testing it (it is a bad practice!). But I'm in v9 and you're using v10 features, so no way to check. Could you include the results for my two test cases? – Dr. belisarius Mar 18 '16 at 07:17