I have a multiband satellite image stored in the band interleaved pixel (BIP) format along with a separate header file. The header file provides the details such as the number of rows and columns in the image, and the number of bands (can be more than the standard 3).
The image itself is stored like this (assume a 5 band image):
[B1][B2][B3][B4][B5][B1][B2][B3][B4][B5] ... and so on (basically 5 bytes - one for each band - for each pixel starting from the top left corner of the image).
I need to separate out each of these bands as PIL images in Python 3.2 (on Windows 7 64 bit), and currently I think I'm approaching the problem incorrectly. My current code is as follows:
def OpenBIPImage(file, width, height, numberOfBands):
"""
Opens a raw image file in the BIP format and returns a list
comprising each band as a separate PIL image.
"""
bandArrays = []
with open(file, 'rb') as imageFile:
data = imageFile.read()
currentPosition = 0
for i in range(height * width):
for j in range(numberOfBands):
if i == 0:
bandArrays.append(bytearray(data[currentPosition : currentPosition + 1]))
else:
bandArrays[j].extend(data[currentPosition : currentPosition + 1])
currentPosition += 1
bands = [Image.frombytes('L', (width, height), bytes(bandArray)) for bandArray in bandArrays]
return bands
This code takes way too long to open a BIP file, surely there must be a better way to do this. I do have the numpy and scipy libraries as well, but I'm not sure how I can use them, or if they'll even help in any way.
Since the number of bands in the image are also variable, I'm finding it hard to figure out a way to read the file quickly and separate the image into its component bands.
And just for the record, I have tried messing with the list methods in the loops (using slices, not using slices, using only append, using only extend etc), it doesn't particularly make a difference as the major time is lost because of the number of iterations involved - (width * height * numberOfBands).
Any suggestions or advice would be really helpful. Thanks.
If you can find a fast function to load the binary data in a big python list (or numpy array), you can de-interleave the data using the slicing notation:
band0 = biglist[::nbands]
band1 = biglist[1::nbands]
....
Does that help?
Standard PIL
To load an image from a file, use the open function in the Image module.
>>> import Image
>>> im = Image.open("lena.ppm")
If successful, this function returns an Image object. You can now use instance attributes to examine the file contents.
>>> print im.format, im.size, im.mode
PPM (512, 512) RGB
The format attribute identifies the source of an image. If the image was not read from a file, it is set to None. The size attribute is a 2-tuple containing width and height (in pixels). The mode attribute defines the number and names of the bands in the image, and also the pixel type and depth. Common modes are "L" (luminance) for greyscale images, "RGB" for true colour images, and "CMYK" for pre-press images.
The Python Imaging Library also allows you to work with the individual bands of an multi-band image, such as an RGB image. The split method creates a set of new images, each containing one band from the original multi-band image. The merge function takes a mode and a tuple of images, and combines them into a new image. The following sample swaps the three bands of an RGB image:
Splitting and merging bands
r, g, b = im.split()
im = Image.merge("RGB", (b, g, r))
So I think you should simply derive the mode and then split accordingly.
PIL with Spectral Python (SPy python module)
However, as you pointed out in your comments below, you are not dealing with a normal RGB image with 3 bands. So to deal with that, SpectralPython (a pure python module which requires PIL) might just be what you are looking for.
Specifically - http://spectralpython.sourceforge.net/class_func_ref.html#spectral.io.bipfile.BipFile
spectral.io.bipfile.BipFile deals with Image files with Band Interleaved Pixel (BIP) format.
Hope this helps.
I suspect that the repetition of extend is not good better allocate all first
def OpenBIPImage(file, width, height, numberOfBands):
"""
Opens a raw image file in the BIP format and returns a list
comprising each band as a separate PIL image.
"""
bandArrays = []
with open(file, 'rb') as imageFile:
data = imageFile.read()
currentPosition = 0
for j in range(numberOfBands):
bandArrays[j]= bytearray(b"\0"*(height * width)):
for i in xrange(height * width):
for j in xrange(numberOfBands):
bandArrays[j][i]=data[currentPosition])
currentPosition += 1
bands = [Image.frombytes('L', (width, height), bytes(bandArray)) for bandArray in bandArrays]
return bands
my measurements doesn't show nsuch a slow down
def x():
height,width,numberOfBands=1401,801,6
before = time.time()
for i in range(height * width):
for j in range(numberOfBands):
pass
print (time.time()-before)
>>> x()
0.937999963760376
EDITED
Related
I was wondering if this was possible.
I'm currently drafting a simple project that would transform my text files into images by using the values of the characters to determine the RGB values of the outputted image.
I know it sounds counterintuitive and no, I don't want to print a string into an image file, I want the text itself to determine the RGB values of each pixel. This is just a rough idea and is far from refined.
I just want a simple program that will work as a proof of concept.
Code so far:
#first contact
from ctypes import sizeof
from PIL import Image
import math as m
def test():
f='qran.txt'
file = open(f)
text = file.read()
file.close() # this is dumb, should just read from file instead of dumping it into a
text = list(text) #rudimentary fix, turn text into list so we can manage the characters
size = m.floor(m.sqrt(len(text)//3)) #round the value for a square image
print(size)
# for elem in text:
# print(ord(elem))
img = Image.new('RGB', (size,size))
pixels = img.load() # create the pixel map
c = 0
for i in range(img.size[0]): # for every col:
for j in range(img.size[1]): # For every row
pixels[i,j] = (ord(text[c]), ord(text[c+1]), ord(text[c+2])) # set the colour accordingly
c+=1
c+=1
img.show()
img.save('qran.png')
test()
As you can see right now my idea is working as a rough concept. You can copy the quran in plaintext and paste it in the same folder as this simple py program to see this output
The image comes out as dull, since characters are converted into integers and their values are too high, and so most colors come off as light-dark gray.
Are there some libraries that could help with exaggerating the values so that they would come off as more representative? I've thought of multiplying by 10 and truncating the result of inverting the values then applying some filters.
I know its pretty much trial and error by this point (as well as polishing the actual code to provide usable functions that allow tweaking images without editing the function over and over again) but I'd like some outside input from people that have dwelved into image processing and such in python.
I apologize in advance if this post was too wordy or contained some unnecessary tidbits, it's my first post in this community.
Just implementing Christoph's idea in the comments:
#!/usr/bin/env python3
from PIL import Image
import math as m
import pathlib
import numpy as np
# Load document as bytes
qran = pathlib.Path('qran.txt').read_bytes()
size = m.floor(m.sqrt(len(qran))) #round the value for a square image
# Make palette image from bytes
img = Image.frombuffer('P', (size,size), qran, "raw", 'P', 0, 1)
# Add random palette of 256 RGB triplets to image
palette = np.random.randint(0,256, 768, np.uint8)
img.putpalette(palette)
img.save('qran.png')
Background: I have images I need to compare for differences. The images are large (on the order of 1400x9000 px), machine-generated and highly constrained (screenshots of a particular piece of linear UI), and are expected to be nearly identical, with differences being one of the following three possibilities:
Image 1 has a section image 2 is missing
Image 1 is missing a section image 2 has
Both images have the given section, but its contents differ
I'm trying to build a tool that highlights the differences for a human reviewer, essentially an image version of line-oriented diff. To that end, I'm trying to scan the images line by line and compare them to decide if the lines are identical. My ultimate goal is an actual diff-like output, where it can detect that sections are missing/added/different, and sync the images up as soon as possible for the remaining parts of identical content, but for the first cut, I'm going with a simpler approach where the two images are overlaid (alpha blended), and the lines which were different highlighted with a particular colour (ie. alpha-blended with a third line of solid colour). At first I tried using Python Imaging Library, but that was far several orders of magnitude too slow, so I decided to try using vips, which should be way faster. However, I have absolutely no idea how to express what I'm after using vips operations. The pseudocode for the simpler version would be essentially:
out = []
# image1 and image2 are expected, but not guaranteed to have the same height
# they are likely to have different heights if different
# most lines are entirely white pixels
for line1, line2 in zip(image1, image2):
if line1 == line2:
out.append(line1)
else:
# ALL_RED is a line composed of solid red pixels
out.append(line1.blend(line2, 0.5).blend(ALL_RED, 0.5))
I'm using pyvips in my project, but I'm also interested in code using plain vips or any other bindings, since the operations are shared and easily translated across dialects.
Edit: adding sample images as requested
Edit 2: full size images with missing/added/changed sections:
reference
comparison
How about just using diff? It's pretty quick. All you need to do is turn your PNGs into text a scanline a time, then parse the diff output.
For example:
#!/usr/bin/env python3
import sys
import os
import re
import pyvips
# calculate a checksum for each scanline and write to name_out
def scanline_checksum(name_in, name_out):
a = pyvips.Image.new_from_file(name_in, access="sequential")
# unfold colour channels to make a wider 1-band image
a = a.bandunfold()
# xyz makes an index image, where the value of each pixel is its coordinate
b = pyvips.Image.xyz(a.width, a.height)
# make a pow gradient image ... each pixel is some power of the x coordinate
b = b[0] ** 0.5
# now multiply and sum to make a checksum for each scanline
# "project" returns sum of columns, sum of rows
sum_of_columns, sum_of_rows = (a * b).project()
sum_of_rows.write_to_file(name_out)
to_csv(sys.argv[1], "1.csv")
to_csv(sys.argv[2], "2.csv")
os.system("diff 1.csv 2.csv > diff.csv")
for line in open("diff.csv", "r"):
match = re.match("(\\d+),(\\d+)c(\\d+),(\\d+)", line)
if not match:
continue
print(line)
For your two test images I see:
$ time ./diff.py 1.png 2.png
264,272c264,272
351,359c351,359
real 0m0.346s
user 0m0.445s
sys 0m0.033s
On this elderly laptop. All you need to do is use those "change" commands to mark up your images.
If OpenCV and NumPy are options to you, then there would be a quite simple solution at least for finding and coloring different rows.
In my approach, I just calculate pixel-wise differences using np.abs, and find non-zero row indices with np.nonzero. With these found row indices, I set up an additional black image and draw red lines for each row. The final blending is just some linear mixing:
0.5 * image1 + 0.5 * image2
for all equal rows, or
0.333 * image1 + 0.333 * image2 + 0.333 * red
for all different rows.
Here's the final code:
import cv2
import numpy as np
# Load images
first = cv2.imread('9gOlq.png', cv2.IMREAD_COLOR)
second = cv2.imread('1Hdx4.png', cv2.IMREAD_COLOR)
# Calcluate absolute differences between images
diff = np.abs(np.float32(first) - np.float32(second))
# Find all non-zero rows
nz_rows = np.unique(np.nonzero(diff)[0])
# Set up image with red lines
red = np.zeros(first.shape, np.uint8)
red[nz_rows, :, :] = [0, 0, 255]
# Set up output image
output = np.uint8(0.5 * first + 0.5 * second)
output[nz_rows, :, :] = 0.333 * first[nz_rows, :, :] + 0.333 * second[nz_rows, :, :] + 0.333 * red[nz_rows, :, :]
# Show results
cv2.imshow("diff", np.array(diff, dtype=np.uint8))
cv2.imshow("output", output)
cv2.waitKey()
cv2.destroyAllWindows()
The difference image diff looks like this:
The final output looke like this:
It would be interesting to see two input images with omitted sections as you described in your question. Also, testing this approach using original sized images would be necessary, since you mentioned time is crucial.
Anyway - hope that helps!
I need to split an RGBA image into an arbitrary number of boxes that are as equally sized as possible
I have attempted to use numpy.array_split, but am unsure of how to do so while preserving the RGBA channels
I have looked the following questions, none of them detail how to split an image into n boxes, they reference splitting the image into boxes of predetermined pixel size, or how to split the image into some shape.
While it seems that it would be some simple math to get number of boxes from box size and image size, I am unsure of how to do so.
How to Split Image Into Multiple Pieces in Python
Cutting one image into multiple images using the Python Image Library
Divide image into rectangles information in Python
While attempting to determine the number of boxes from pixel box size, I used the formula
num_boxes = (img_size[0]*img_size[1])/ (box_size_x * box_size_y)
but that did not result in the image being split up properly
To clarify, I would like to be able to input an image that is a numpy array of size (a,b,4) and a number of boxes and output the images in some form (np array preferred, but whatever works)
I appreciate any help, even if you aren't able to provide the full method, I would appreciate some direction.
I have tried
def split_image(image, n_boxes):
return numpy.array_split(image,n_boxes)
#doesn't work with colors
def split_image(image, n_boxes):
box_size = factor_int(n_boxes)
M = im.shape[0]//box_size[0]
N = im.shape[1]//box_size[1]
return [im[x:x+M,y:y+N] for x in range(0,im.shape[0],M) for y in range(0,im.shape[1],N)]
factor_int returns integer as close to a square as possible from Factor an integer to something as close to a square as possible
I am still not sure if your inputs are actually the image and the dimensions of the boxes or the image and the number of boxes. Nor am I sure if your problem is deciding where to chop the image or knowing how to chop a 4-channel image, but maybe something in here will get you started.
I started with this RGBA image - the circles are transparent, not white:
#!/usr/bin/env python3
from PIL import Image
import numpy as np
import math
# Open image and get dimensions
im = Image.open('start.png').convert('RGBA')
# Make Numpy array from image and get height and width
ni = np.array(im)
h ,w = ni.shape[:2]
print(f'Height: {h}, width: {w}')
BOXES = 4
for i in range(BOXES):
this = ni[:, i*w//BOXES:(i+1)*w//BOXES, :]
Image.fromarray(this).save(f'box-{i}.png')
You can change BOXES but leaving it at 4 gets you these 4 output images:
[] []4
I have a program to break an image down into 1s and 0s (shown below). My problem is that the arrays are different lengths for different images (taken with the same webcam and no compression).
Here is the code:
from PIL import Image
def read(filename):
image = Image.open(filename, 'r')
basewidth = 300
wpercent = (basewidth/float(image.size[0]))
hsize = int((float(image.size[1])*float(wpercent)))
image = image.resize((basewidth,hsize), Image.ANTIALIAS)
width, height = image.size
print(width)
print(height)
data = list(image.getdata())
binData = []
for i in data:
for j in i:
tempBin = str(bin(j))
for k in tempBin:
if k != "b":
binData.append(int(k))
print(len(binData))
return binData
I am confused because I took the pictures at the same time with the same webcam, same resolution. They are different file sizes, but I don't know why.
Thanks for any solutions you can offer!
You don't show us the images, but could it be because of compression? JPG files are compressed, and many other file formats are.
Even two pictures taken a short while from eachother are a bit different and compress differently, hence the 'same resolution, different files sizes'.
PIL can convert JPEG to BMP. JPEG is compressed (frequency domain and quantised, so even if it’s lossless, two images can be different sizes just because of their content). BMP is just RGB (red, green, blue) values for each pixel plus a header. For images with the same height and width, the .bmp file will be the same size (headers plus height x width x 3 channels). The jpeg files will be smaller, though.
Of course, getdata() will return pixel values regardless.
If you convert bytes to binary to strings, then you need to have all the binary strings the same length, and you may need to manually add preceding 0s to some binary strings.
I'm wanting to work on an idea using images but I can't get it to write pixel values properly, it always just ends up grey with some pattern like artefacts, and no matter what I try, the artefacts change but the image remains grey.
Here's the basic code I have:
from PIL import Image
data = ""
for i in range( 128**2 ):
data += "(255,0,0),"
im = Image.fromstring("RGB", (128,128), data)
im.save("test.png", "PNG")
There is no information in http://effbot.org/imagingbook/pil-index.htm on how to format data, so I've tried using 0-1, 0-255, 00000000-111111111 (binary), brackets, square bracks, no brackets, extra value for alpha and changing RGB to RGBA (which turns it light grey but that's it), comma after, and no comma after, but absolutely nothing has worked.
For the record, I'm not wanting to just store a single colour, I'm just doing this to initially get it working.
The format string should be arranged like:
"RGBRGBRGBRGB..."
Where R is a single character indicating the red value of a particular pixel, and the same for G and B.
"But 255 is three characters long, how can I turn that into a single character?" you ask. Use chr to convert your numbers into byte values.
from PIL import Image
data = ""
for i in range( 128**2 ):
data += chr(255) + chr(0) + chr(0)
im = Image.fromstring("RGB", (128,128), data)
im.save("test.png", "PNG")
Result:
Alternative solution:
Using fromstring is a rather roundabout way to generate an image if your data isn't already in that format. Instead, consider using Image.load to directly manipulate pixel values. Then you won't have to do any string conversion stuff.
from PIL import Image
im = Image.new("RGB", (128, 128))
pix = im.load()
for x in range(128):
for y in range(128):
pix[x,y] = (255,0,0)
im.save("test.png", "PNG")