I have an image with that has height/width dimensions of 3837x5126 pixels, respectively.
My goal is to divide this image into separate tiles with the exact dimensions of 1000x1000 pixels. If the image cannot be divided evenly, overlapping tiles should be created so the tiles are all 1000x1000 pixels.
Here's a ROUGH visualization created in PowerPoint to illustrate what I am trying to do:
My thought process was this:
- divide the image by my ideal size (1000)
- note the remainder
- create an offset by dividing that remainder by all divisions
- for each division, subtract the offset
- overlapping tiles are created with 1000x1000 dimensions.
Here is the code that I have so far.
import numpy as np
def get_divisions(num, divisor):
n = num / divisor
divisions = math.floor(n)
remainder = n % 1
offset = remainder / divisions
return divisions, offset
def get_tiles(height, width, window_size=1000):
number_of_rows, row_offset = get_divisions(height, window_size)
number_of_columns, col_offet = get_divisions(width, window_size)
tiles = []
for row in range(number_of_rows):
for col in range(number_of_columns):
if row == 0:
row_offset = 0
if col == 0:
col_offset = 0
x = (col * window_size) - (col_offset * window_size)
y = (row * window_size) - (row_offset * window_size)
w = window_size
h = window_size
tiles.append([x, y, h, w])
return np.array(tiles, dtype="uint32")
height = 3837
width = 5126
get_tiles(height, width)
outputs:
array([[ 0, 0, 1000, 1000],
[1000, 0, 1000, 1000],
[2000, 0, 1000, 1000],
[3000, 0, 1000, 1000],
[4000, 0, 1000, 1000],
[ 0, 1000, 1000, 1000],
[1000, 1000, 1000, 1000],
[2000, 1000, 1000, 1000],
[3000, 1000, 1000, 1000],
[4000, 1000, 1000, 1000],
[ 0, 2000, 1000, 1000],
[1000, 2000, 1000, 1000],
[2000, 2000, 1000, 1000],
[3000, 2000, 1000, 1000],
[4000, 2000, 1000, 1000]], dtype=uint32)
As you can see, my matrix of [x, y, h, w] is incorrect because there are some x,y points that are outside of the 3837x5126 pixel image.
Any help would be greatly appreciated!
Edit:
Here is answer @HansHirse gave with some added tests.
import unittest
import math
import numpy as np
def tile(h, w, tile_width=None, tile_height=None, window_size=100):
np.seterr(divide='ignore', invalid='ignore')
if not tile_width:
tile_width = window_size
if not tile_height:
tile_height = window_size
wTile = tile_width
hTile = tile_height
if tile_width > w or tile_height > h:
raise ValueError("tile dimensions cannot be larger than origin dimensions")
# Number of tiles
nTilesX = np.uint8(np.ceil(w / wTile))
nTilesY = np.uint8(np.ceil(h / hTile))
# Total remainders
remainderX = nTilesX * wTile - w
remainderY = nTilesY * hTile - h
# Set up remainders per tile
remaindersX = np.ones((nTilesX-1, 1)) * np.uint16(np.floor(remainderX / (nTilesX-1)))
remaindersY = np.ones((nTilesY-1, 1)) * np.uint16(np.floor(remainderY / (nTilesY-1)))
remaindersX[0:np.remainder(remainderX, np.uint16(nTilesX-1))] += 1
remaindersY[0:np.remainder(remainderY, np.uint16(nTilesY-1))] += 1
# Initialize array of tile boxes
tiles = np.zeros((nTilesX * nTilesY, 4), np.uint16)
k = 0
x = 0
for i in range(nTilesX):
y = 0
for j in range(nTilesY):
tiles[k, :] = (x, y, hTile, wTile)
k += 1
if j < (nTilesY-1):
y = y + hTile - remaindersY[j]
if i < (nTilesX-1):
x = x + wTile - remaindersX[i]
return tiles
class TestTilingWithoutRemainders(unittest.TestCase):
def test_it_returns_tiles_without_overflow(self):
self.assertEqual(tile(500, 500, window_size=500).tolist(), np.array([[0, 0, 500, 500]], dtype="uint16").tolist())
self.assertEqual(tile(250, 500, window_size=250).tolist(), np.array(
[[0, 0, 250, 250], [250, 0, 250, 250]], dtype="uint16"
).tolist())
self.assertEqual(tile(500, 250, window_size=250).tolist(), np.array(
[[0, 0, 250, 250], [0, 250, 250, 250]], dtype="uint16"
).tolist())
class TestTilingWithRemainders(unittest.TestCase):
def test_it_returns_tiles_with_overflow(self):
self.assertEqual(tile(500, 501, window_size=500).tolist(), np.array(
[[0, 0, 500, 500], [1, 0, 500, 500]], dtype="uint16"
).tolist())
self.assertEqual(tile(251, 250, window_size=250).tolist(), np.array(
[[0, 0, 250, 250], [0, 1, 250, 250]], dtype="uint16"
).tolist())
class TestTilingWithInvalidWindowSizes(unittest.TestCase):
def test_it_raises_an_error_with_invalid_tile_height(self):
self.assertRaises(ValueError, tile, 500, 500, tile_height=50000000)
def test_it_raises_an_error_with_invalid_tile_width(self):
self.assertRaises(ValueError, tile, 500, 500, tile_width=50000000)
def test_it_raises_an_error_with_invalid_window_size(self):
self.assertRaises(ValueError, tile, 500, 500, window_size=50000000)
