6
$\begingroup$

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}

$\endgroup$
6
  • $\begingroup$ {a ;; b, c ;; d} will return the the intensity of every pixel or a mean value? $\endgroup$
    – yode
    Commented Mar 18, 2016 at 6:34
  • $\begingroup$ @yode A list (matrix) with the intensities for every pixel $\endgroup$ Commented Mar 18, 2016 at 6:47
  • $\begingroup$ Incredible, how unterse it is to transform a Span specification to a Take specification! Working on this right now. $\endgroup$
    – LLlAMnYP
    Commented Mar 18, 2016 at 9:46
  • $\begingroup$ 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. $\endgroup$
    – LLlAMnYP
    Commented Mar 18, 2016 at 9:49
  • 1
    $\begingroup$ The golfed down version that takes a Take kind of part specification looks like this: f[i_, c__] := ImageData@ColorConvert[ImageTake[i, c], "Grayscale"] $\endgroup$
    – LLlAMnYP
    Commented Mar 18, 2016 at 9:51

2 Answers 2

5
$\begingroup$

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}].

$\endgroup$
7
  • $\begingroup$ On the same line ImageData@ColorConvert[ImageTrim[i, {c - .5, c - .5}], "Grayscale"] uses image standard coordinates $\endgroup$ Commented Mar 18, 2016 at 13:56
  • $\begingroup$ 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. $\endgroup$
    – LLlAMnYP
    Commented Mar 18, 2016 at 14:10
  • $\begingroup$ @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. $\endgroup$
    – LLlAMnYP
    Commented Mar 18, 2016 at 14:13
  • $\begingroup$ 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. $\endgroup$ Commented Mar 18, 2016 at 14:20
  • 1
    $\begingroup$ @Dr.belisarius just did, though the answer is a bit cluttered now. No time to tidy up though, maybe some other time. $\endgroup$
    – LLlAMnYP
    Commented Mar 18, 2016 at 14:29
4
$\begingroup$
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}

$\endgroup$
3
  • 3
    $\begingroup$ I meant "terser". Like in more "brief, short, to the point, concise, succinct, crisp" :) $\endgroup$ Commented Mar 18, 2016 at 7:05
  • $\begingroup$ Only these option/function have more letters.but is brief actually I think.aha~ $\endgroup$
    – yode
    Commented Mar 18, 2016 at 7:09
  • $\begingroup$ 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? $\endgroup$ Commented Mar 18, 2016 at 7:17

Your Answer

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

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