0

I want to create an algorithm that can take a dataset and check how each image is represented and change the scale to 0-255 or 0-1 automatically. I know how this is done manually if I know how my full dataset is represented. Lets say my dataset is 16 bit I can convert it to 8 bit by:

img = (img/256).astype('uint8')

Or convert it between 0-1 by:

img = (img/65536)

However, lets say I have a dataset that is 8 bit represented (I dont know this yet) and I loop through each image individually it may happen that some of my images max is 240,250 and 251. Then if I want to rescale to 0-1 it will not be correct:

img = img/240 # first image
img = img/250 # second image
img = img/251 # third image

I could also check for the maximum value of the full dataset and then get the correct max value. But is there a way to rescale "correctly" even if the max value for the individual image is 240?

Kalle
  • 364
  • 1
  • 2
  • 14
  • I could be misunderstanding, but is it enough to check `img.dtype` and divide by 255 if it's np.uint8 or divide by 65535 if it's np.uint16? – Pascal Getreuer Jan 24 '21 at 06:30
  • img = (img/65536) would give 1 only when img=65536 ? wouldnt be better img = (img/(65536/2)) ? – pippo1980 Jan 26 '21 at 18:01
  • @PascalGetreuer the problem with checking img.dtype is that the dataset can be int32 even if its 16 bit represented. Therefore, it will give an incorrect answer if that is the case. – Kalle Jan 27 '21 at 07:22
  • @pippo1980 (img/65536) will give 1 yes and that is exactly the point by doing it this way. If the images is 16 bit and you divide by 65536 your images will be rescaled to 0-1 and represented as floating points instead. – Kalle Jan 27 '21 at 07:24
  • @kalle so all the greys from 0 to 65535 will be = 0(zeros) and only the upper limit will be 1(ones) ? so you are assignig 1 to something could be over (or under, depends how the image is recorded ) the detection limit of the instrument used to get the image ? I was tring to understand the purpose of your greyscale reduction, thanks for your answer – pippo1980 Jan 27 '21 at 09:10
  • ok I got 0=black and 1 = white – pippo1980 Jan 27 '21 at 09:42
  • ok I got 0=black and 1 = white, but how do I move from 8bit dept to 1bit dept both in numpy and PILgiven that 'The byte is the smallest addressable unit in x86' – pippo1980 Jan 27 '21 at 09:58
  • ok, I am staying as 'uint8' and changing 0 -> to 0 and 1 -< to 255 to display images with pillow. Still not sure how to use pilllow mode ='1' (1-bit pixels, black and white, stored with one pixel per byte) – pippo1980 Jan 27 '21 at 10:17
  • @pippo1980 if you have an image that is represented in 16 bit an example would be: img = [204, 50043, 65535, ... ] if you divide these by 65535 it will be img = [0.00311, 0.76360, 1, ...]. since 204/65535 = 0.00311 etc. So the image is now represented between 0-1 in floating points. See? – Kalle Jan 27 '21 at 10:47
  • yep I am starting from img = (np.random.random((500, 500)) * 2**16).astype(np.uint16), now I've problems showing and saving a PIL Image type created with img8_unpack_arr = Image.fromarray(img8_unpack, mode='1') 1bit mode Image (0,1), Image.show() doesnt work and img8_unpack_arr.save('8bit_2_1bit.tif') gives display-im6.q16: no decode delegate for this image format `' @ error/constitute.c/ReadImage/560. – pippo1980 Jan 27 '21 at 11:45
  • I started trying to learn numpy,pil and tiff with np.random.randint(0, 256, (5, 2, 3), 'uint8') – pippo1980 Jan 27 '21 at 11:47
  • img = (np.random.random((500, 500)) * 2**16).astype(np.uint16) -> img8 = ((img/65535)).astype('uint8') ->img8 has: for i in np.ndindex(img8.shape): if img8[i] == 0: img8[i] = 0 if img8[i] == 1: print('CHANGING TO 255 @@@@@@@@@@@@@@@@@@@@@@@@@@') print(i) img8[i] = 255 -> img8_unpack=np.unpackbits(img8) -> img8_unpack_arr = Image.fromarray(img8_unpack, mode='1'), img8 has – pippo1980 Jan 27 '21 at 12:10
  • think problem is here : img8_unpack=np.unpackbits(img8) img8 now contains only 0 and 255 in uint8 – pippo1980 Jan 27 '21 at 12:38
  • np.unpackbits() doesnt do what I toght was doing, numpy arrays: NumPy has 1-byte atomicity and so a 8-bit is the smallest unit. Even bool dtype uses a single byte. – pippo1980 Jan 27 '21 at 14:45
  • and among PIL modes: 1 (1-bit pixels, black and white, stored with one pixel per byte) still uses a byte for a bit info – pippo1980 Jan 27 '21 at 18:35

1 Answers1

0

As I understand, you have a dataset of uint8 and uint16 images, however, the dtype does not necessarily correspond, e.g. a dtype of int32 used to represent uint16 data. The question is how to rescale each image "properly" to floats in [0, 1], i.e. divide by 255 or 65535 according to the original type.

I'll first say that this problem has no perfect solution. It's conceivable a very dark uint16 image would have all values 255 or below, making it falsely appear to be uint8 data. Ideally, avoid getting into this problem in the first place by saving the ditdepth as metadata for each image in the dataset.

That said, consider the following heuristic: Given an image, compute its max value (image.max()). Then,

  • if the max value is ≤ 255, assume uint8 data and divide the image by 255.

  • otherwise, assume uint16 and divide the image by 65535.

Or extend this logic as needed if you have additional representations besides uint8 and uint16.

Pascal Getreuer
  • 2,906
  • 1
  • 5
  • 14