tiledimage 0.3.1__tar.gz → 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,18 +1,21 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: tiledimage
3
- Version: 0.3.1
3
+ Version: 0.6.0
4
4
  Summary: tools for tiled image that can be cached on a filesystem.
5
5
  License: MIT
6
6
  Author: vitroid
7
7
  Author-email: vitroid@gmail.com
8
- Requires-Python: >=3.10,<4.0
8
+ Requires-Python: >=3.11,<4.0
9
9
  Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Programming Language :: Python :: 3
11
- Classifier: Programming Language :: Python :: 3.10
12
11
  Classifier: Programming Language :: Python :: 3.11
13
12
  Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
14
  Requires-Dist: opencv-python-headless (>=4.11.0.86,<5.0.0.0)
15
15
  Requires-Dist: pylru (>=1.2.1,<2.0.0)
16
+ Requires-Dist: pyperbox (>=0.1.1,<0.2.0)
17
+ Requires-Dist: rasterio (>=1.4.3,<2.0.0)
18
+ Requires-Dist: tifffile (>=2025.9.9,<2026.0.0)
16
19
  Description-Content-Type: text/markdown
17
20
 
18
21
  #TiledImage
@@ -1,15 +1,18 @@
1
1
  [tool.poetry]
2
2
  name = "tiledimage"
3
- version = "0.3.1"
3
+ version = "0.6.0"
4
4
  description = "tools for tiled image that can be cached on a filesystem."
5
5
  authors = ["vitroid <vitroid@gmail.com>"]
6
6
  license = "MIT"
7
7
  readme = "README.md"
8
8
 
9
9
  [tool.poetry.dependencies]
10
- python = "^3.10"
10
+ python = "^3.11"
11
11
  pylru = "^1.2.1"
12
12
  opencv-python-headless = "^4.11.0.86"
13
+ tifffile = "^2025.9.9"
14
+ rasterio = "^1.4.3"
15
+ pyperbox = "^0.1.1"
13
16
 
14
17
  [tool.poetry.scripts]
15
18
  pngs2 = "tiledimage.pngs2:main"
@@ -0,0 +1,2 @@
1
+ __version__ = "1.0.0"
2
+
@@ -5,6 +5,7 @@ import numpy as np
5
5
 
6
6
  from tiledimage.tiledimage import TiledImage
7
7
  import tiledimage.tilecache as tilecache
8
+ from pyperbox import Rect, Range
8
9
 
9
10
 
10
11
  class CachedImage(TiledImage):
@@ -33,14 +34,42 @@ class CachedImage(TiledImage):
33
34
  self.modified = False
34
35
  if mode == "inherit":
35
36
  # read the info.txt in the dir.
36
- self.region = [None, None]
37
- with open(f"{dir}/info.json", "r") as file:
38
- info = json.load(file)
39
- self.region[0] = info["xrange"]
40
- self.region[1] = info["yrange"]
41
- self.tilesize = info["tilesize"]
42
- self.bgcolor = info["bgcolor"]
43
- self.fileext = info["filetype"]
37
+ self.rect = None
38
+ try:
39
+ with open(f"{dir}/info.json", "r") as file:
40
+ info = json.load(file)
41
+ self.rect = Rect(
42
+ x_range=Range(
43
+ min_val=info["xrange"][0], max_val=info["xrange"][1]
44
+ ),
45
+ y_range=Range(
46
+ min_val=info["yrange"][0], max_val=info["yrange"][1]
47
+ ),
48
+ )
49
+ self.tilesize = info["tilesize"]
50
+ self.bgcolor = info["bgcolor"]
51
+ self.fileext = info["filetype"]
52
+ except FileNotFoundError:
53
+ # backward compatibility
54
+ xrange = None
55
+ yrange = None
56
+ with open(f"{dir}/info.txt", "r") as file:
57
+ while True:
58
+ line = file.readline().strip()
59
+ if len(line) == 0:
60
+ break
61
+ cols = line.split(" ")
62
+ if cols[-1] == "xrange":
63
+ xrange = Range(min_val=int(cols[0]), max_val=int(cols[1]))
64
+ if cols[-1] == "yrange":
65
+ yrange = Range(min_val=int(cols[0]), max_val=int(cols[1]))
66
+ if cols[-1] == "tilesize":
67
+ self.tilesize = [int(cols[0]), int(cols[1])]
68
+ if cols[-1] == "bgcolor":
69
+ self.bgcolor = [int(cols[0]), int(cols[1]), int(cols[2])]
70
+ if cols[-1] == "filetype":
71
+ self.fileext = cols[-1]
72
+ self.rect = Rect(x_range=xrange, y_range=yrange)
44
73
  defaulttile = np.zeros((self.tilesize[1], self.tilesize[0], 3), dtype=np.uint8)
45
74
  self.bgcolor = np.array(self.bgcolor)
46
75
  # logger.info("Color: {0}".format(self.bgcolor))
@@ -74,8 +103,8 @@ class CachedImage(TiledImage):
74
103
  if isinstance(bgcolor, np.ndarray):
75
104
  bgcolor = bgcolor.tolist()
76
105
  info = dict(
77
- xrange=self.region[0],
78
- yrange=self.region[1],
106
+ xrange=self.rect.x_range.as_list(),
107
+ yrange=self.rect.y_range.as_list(),
79
108
  tilesize=self.tilesize,
80
109
  bgcolor=bgcolor,
81
110
  filetype=self.fileext,
@@ -84,15 +113,36 @@ class CachedImage(TiledImage):
84
113
  json.dump(info, file)
85
114
  self.tiles.done() # タイルキャッシュの終了処理を呼び出す
86
115
 
87
- def put_image(self, pos, img, linear_alpha=None):
88
- super(CachedImage, self).put_image(pos, img, linear_alpha)
116
+ def put_image(self, pos, img, linear_alpha=None, full_alpha=None):
117
+ super(CachedImage, self).put_image(pos, img, linear_alpha, full_alpha)
89
118
  self.modified = True
90
119
  logger = getLogger()
91
120
  nmiss, naccess, cachesize = self.tiles.cachemiss()
92
- logger.info(
121
+ logger.debug(
93
122
  "Cache miss {0}% @ {1} tiles".format(nmiss * 100 // naccess, cachesize)
94
123
  )
95
124
  self.tiles.adjust_cache_size()
96
125
 
97
126
  def set_hook(self, hook):
98
127
  self.tiles.set_hook(hook)
128
+
129
+
130
+ def test():
131
+ import sys
132
+ import cv2
133
+
134
+ png = sys.argv[1]
135
+ tilesize = int(sys.argv[2])
136
+ with CachedImage("new", tilesize=tilesize) as tiled_image:
137
+ tiled_image[10:, 20:] = cv2.imread(png)
138
+ tiled_image[-10:, -20:] = cv2.imread(png)
139
+ image = tiled_image[:, :]
140
+
141
+ with CachedImage("inherit", dir="image.pngs") as tiled_image:
142
+ cv2.imshow("image", tiled_image.get_image(width=100))
143
+ cv2.waitKey(0)
144
+ cv2.destroyAllWindows()
145
+
146
+
147
+ if __name__ == "__main__":
148
+ test()
@@ -1,11 +1,7 @@
1
1
  #!/usr/bin/env python
2
2
 
3
- from __future__ import print_function
4
-
5
3
  import sys
6
-
7
4
  import cv2
8
-
9
5
  import tiledimage.cachedimage as ci
10
6
 
11
7
 
@@ -0,0 +1,175 @@
1
+ import logging
2
+
3
+ # external modules
4
+ import numpy as np
5
+ from pyperbox import Rect
6
+
7
+
8
+ class SimpleImage:
9
+
10
+ def __init__(self, bgcolor=(100, 100, 100)):
11
+ self.rect = None
12
+ self.bgcolor = np.array(bgcolor, dtype=np.uint8)
13
+
14
+ def __enter__(self):
15
+ return self
16
+
17
+ def __exit__(self, exc_type, exc_val, exc_tb):
18
+ pass # TiledImageは特別なクリーンアップ処理は必要ありません
19
+
20
+ def _parse_slice(self, key):
21
+ """スライスを解析してRectに変換する
22
+
23
+ Args:
24
+ key: スライスまたはタプル。例: (slice(0, 10), slice(0, 20))
25
+
26
+ Returns:
27
+ Rect: スライスに対応する領域
28
+ """
29
+ if not isinstance(key, tuple) or len(key) != 2:
30
+ raise IndexError("2次元のスライスを指定してください")
31
+
32
+ y_slice, x_slice = key
33
+ if not (isinstance(y_slice, slice) and isinstance(x_slice, slice)):
34
+ raise IndexError("スライスを指定してください")
35
+
36
+ # スライスの開始と終了を取得
37
+ y_start = y_slice.start if y_slice.start is not None else 0
38
+ y_stop = y_slice.stop if y_slice.stop is not None else float("inf")
39
+ x_start = x_slice.start if x_slice.start is not None else 0
40
+ x_stop = x_slice.stop if x_slice.stop is not None else float("inf")
41
+
42
+ # ステップは未対応
43
+ if y_slice.step is not None or x_slice.step is not None:
44
+ raise NotImplementedError("ステップ付きスライスには未対応です")
45
+
46
+ return Rect.from_bounds(x_start, x_stop, y_start, y_stop)
47
+
48
+ def __getitem__(self, key):
49
+ """スライスで領域を取得する
50
+
51
+ Example:
52
+ image = tiled_image[10:20, 30:40] # 10:20行、30:40列の領域を取得
53
+ """
54
+ region = self._parse_slice(key)
55
+ return self.get_region(region)
56
+
57
+ def __setitem__(self, key, value):
58
+ """スライスで領域を設定する
59
+
60
+ Example:
61
+ tiled_image[10:20, 30:40] = image # 10:20行、30:40列の領域に画像を設定
62
+ """
63
+ if not isinstance(value, np.ndarray):
64
+ raise TypeError("NumPy配列を指定してください")
65
+
66
+ region = self._parse_slice(key)
67
+ self.put_image((region.x_range.min_val, region.y_range.min_val), value)
68
+
69
+ def get_region(self, rect: Rect | None = None):
70
+ logger = logging.getLogger()
71
+ if rect is None:
72
+ rect = self.rect
73
+ if rect is None:
74
+ return
75
+ logger.debug(f"get_region region:{rect}")
76
+ # # region.x_rangeが0:や:infの場合には、それぞれself.regionのx_rangeを使用する
77
+ if rect.left == 0:
78
+ rect.x_range.min_val = self.rect.left
79
+ if rect.right == float("inf"):
80
+ rect.x_range.max_val = self.rect.right
81
+ if rect.top == 0:
82
+ rect.y_range.min_val = self.rect.top
83
+ if rect.bottom == float("inf"):
84
+ rect.y_range.max_val = self.rect.bottom
85
+
86
+ dst_width = rect.width
87
+ dst_height = rect.height
88
+
89
+ image = np.zeros((dst_height, dst_width, 3), dtype=np.uint8) + self.bgcolor
90
+ crop = self.rect & rect
91
+ image[
92
+ crop.top - rect.top : crop.bottom - rect.top,
93
+ crop.left - rect.left : crop.right - rect.left,
94
+ ] = self.image[
95
+ crop.top - self.rect.top : crop.bottom - self.rect.top,
96
+ crop.left - self.rect.left : crop.right - self.rect.left,
97
+ ]
98
+ return image
99
+
100
+ def put_image(self, position, image, linear_alpha=None, full_alpha=None):
101
+ """
102
+ split the existent tiles
103
+ and put a big single tile.
104
+ the image must be larger than a single tile.
105
+ otherwise, a different algorithm is required.
106
+ """
107
+ h, w = image.shape[:2]
108
+ rect = Rect.from_bounds(
109
+ position[0], position[0] + w, position[1], position[1] + h
110
+ )
111
+ # expand the canvas
112
+ if self.rect is None:
113
+ self.rect = rect
114
+ self.image = image.copy()
115
+ else:
116
+ newrect = self.rect | rect
117
+ new_image = (
118
+ np.zeros([newrect.height, newrect.width, 3], dtype=np.uint8)
119
+ + self.bgcolor
120
+ )
121
+ new_image[
122
+ self.rect.top - newrect.top : self.rect.bottom - newrect.top,
123
+ self.rect.left - newrect.left : self.rect.right - newrect.left,
124
+ ] = self.image
125
+ self.image = new_image
126
+ self.rect = newrect
127
+
128
+ if linear_alpha is None and full_alpha is None:
129
+ self.image[
130
+ rect.top - self.rect.top : rect.bottom - self.rect.top,
131
+ rect.left - self.rect.left : rect.right - self.rect.left,
132
+ ] = image
133
+ else:
134
+ if full_alpha is not None:
135
+ alpha = full_alpha[:, :, np.newaxis]
136
+ else:
137
+ alpha = linear_alpha[np.newaxis, :, np.newaxis]
138
+ self.image[
139
+ rect.top - self.rect.top : rect.bottom - self.rect.top,
140
+ rect.left - self.rect.left : rect.right - self.rect.left,
141
+ :,
142
+ ] = (
143
+ alpha * image
144
+ + (1 - alpha)
145
+ * self.image[
146
+ rect.top - self.rect.top : rect.bottom - self.rect.top,
147
+ rect.left - self.rect.left : rect.right - self.rect.left,
148
+ :,
149
+ ]
150
+ )
151
+
152
+ def get_image(self):
153
+ # widthを指定すると縮小する。
154
+ return self.get_region(self.rect)
155
+
156
+
157
+ def test():
158
+ import sys
159
+ import cv2
160
+ import logging
161
+
162
+ logging.basicConfig(level=logging.DEBUG)
163
+
164
+ png = sys.argv[1]
165
+ with SimpleImage() as canvas:
166
+ canvas[20:, 40:] = cv2.imread(png)
167
+ canvas[10:, 20:] = cv2.imread(png)
168
+ image = canvas[:, :]
169
+ cv2.imshow("image", image)
170
+ cv2.waitKey(0)
171
+ cv2.destroyAllWindows()
172
+
173
+
174
+ if __name__ == "__main__":
175
+ test()
@@ -47,11 +47,11 @@ class TileCache:
47
47
  self.hook = hook
48
48
 
49
49
  def key_to_filename(self, key):
50
- return "{0}/{1},{2}.{3}".format(self.dir, *key, self.fileext)
50
+ return f"{self.dir}/{key[0]},{key[1]}.{self.fileext}"
51
51
 
52
52
  def __getitem__(self, key):
53
53
  logger = logging.getLogger()
54
- logger.debug("getitem key:{0}".format(key))
54
+ logger.debug(f"getitem key:{key}")
55
55
  self.nget += 1
56
56
  try:
57
57
  modified, value = self.cache[key]
@@ -60,17 +60,15 @@ class TileCache:
60
60
  if os.path.exists(filename):
61
61
  value = cv2.imread(filename)
62
62
  self.nmiss += 1
63
- # logger.info("cache miss key:{0}".format(key))
64
63
  else:
65
- # first access is not a "miss"
66
- logger.info("blank key:{0}".format(key))
64
+ logger.info(f"blank key:{key}")
67
65
  value = self.default
68
66
  self.cache[key] = [False, value]
69
67
  return value
70
68
 
71
69
  def __setitem__(self, key, value):
72
70
  logger = logging.getLogger()
73
- logger.debug("update key:{0}".format(key))
71
+ logger.debug(f"update key:{key}")
74
72
  self.cache[key] = [True, value]
75
73
 
76
74
  def writeback(self, key, value):
@@ -79,20 +77,15 @@ class TileCache:
79
77
  """
80
78
  logger = logging.getLogger()
81
79
  if value[0]:
82
- # logger.info("purge key:{0}".format(key))
83
80
  filename = self.key_to_filename(key)
84
81
  cv2.imwrite(filename, value[1])
85
82
  if self.hook is not None:
86
83
  self.hook(key, value[1])
87
84
 
88
85
  def __contains__(self, key):
89
- # logger = logging.getLogger()
90
- # logger.debug("Query: {0}".format(key))
91
86
  if key in self.cache:
92
- # logger.debug("On cache: {0}".format(key))
93
87
  return True
94
88
  filename = self.key_to_filename(key)
95
- # logger.debug("On file: {0}".format(filename))
96
89
  return os.path.exists(filename)
97
90
 
98
91
  def done(self):
@@ -0,0 +1,207 @@
1
+ import logging
2
+
3
+ # external modules
4
+ import numpy as np
5
+ from pyperbox import Rect, Range
6
+ import cv2
7
+
8
+ from tiledimage.simpleimage import SimpleImage
9
+
10
+
11
+ class TiledImage(SimpleImage):
12
+ """
13
+ it has no size.
14
+ size is determined by the tiles.
15
+ !!! it is better to fix the tile size. (128x128, for example)
16
+ """
17
+
18
+ def __init__(self, tilesize=128, bgcolor=(100, 100, 100)):
19
+ super(TiledImage, self).__init__(bgcolor)
20
+ self.tiles = dict()
21
+ if type(tilesize) is int:
22
+ self.tilesize = (tilesize, tilesize)
23
+ else:
24
+ assert type(tilesize) is tuple
25
+ self.tilesize = tilesize
26
+
27
+ def tiles_containing(self, rect: Rect, includeempty=False):
28
+ """
29
+ return the tiles containing the given region
30
+ """
31
+ logger = logging.getLogger()
32
+ t = []
33
+ xran = (
34
+ rect.x_range.min_val // self.tilesize[0],
35
+ (rect.x_range.max_val + self.tilesize[0] - 1) // self.tilesize[0],
36
+ )
37
+ yran = (
38
+ rect.y_range.min_val // self.tilesize[1],
39
+ (rect.y_range.max_val + self.tilesize[1] - 1) // self.tilesize[1],
40
+ )
41
+ for ix in range(xran[0], xran[1]):
42
+ for iy in range(yran[0], yran[1]):
43
+ tile = (ix * self.tilesize[0], iy * self.tilesize[1])
44
+ logger.debug("Tile: {0}".format(tile))
45
+ if (tile in self.tiles) or includeempty:
46
+ tregion = Rect.from_bounds(
47
+ tile[0],
48
+ tile[0] + self.tilesize[0],
49
+ tile[1],
50
+ tile[1] + self.tilesize[1],
51
+ )
52
+ o = tregion & rect
53
+ t.append((tile, o))
54
+ return t
55
+
56
+ def get_region(self, rect: Rect | None = None, width: int = 0):
57
+ logger = logging.getLogger()
58
+ if rect is None:
59
+ rect = self.rect
60
+ if rect is None:
61
+ return
62
+ logger.debug(f"get_region region:{rect}")
63
+ # # region.x_rangeが0:や:infの場合には、それぞれself.regionのx_rangeを使用する
64
+ if rect.left == 0:
65
+ rect.x_range.min_val = self.rect.left
66
+ if rect.right == float("inf"):
67
+ rect.x_range.max_val = self.rect.right
68
+ if rect.top == 0:
69
+ rect.y_range.min_val = self.rect.top
70
+ if rect.bottom == float("inf"):
71
+ rect.y_range.max_val = self.rect.bottom
72
+
73
+ if width:
74
+ dst_width = int(width)
75
+ dst_height = int(width * rect.height) // rect.width
76
+ scale = width / rect.width
77
+ else:
78
+ dst_width = rect.width
79
+ dst_height = rect.height
80
+ scale = 1.0
81
+
82
+ image = np.zeros((dst_height, dst_width, 3), dtype=np.uint8)
83
+ image[:, :] = self.bgcolor
84
+ for tile, overlap in self.tiles_containing(rect):
85
+ if overlap is None:
86
+ continue
87
+ src = self.tiles[tile]
88
+ if src is None:
89
+ continue
90
+ originx, originy = tile
91
+ src_top = overlap.top - originy
92
+ src_bottom = overlap.bottom - originy
93
+ src_left = overlap.left - originx
94
+ src_right = overlap.right - originx
95
+ src_image = src[src_top:src_bottom, src_left:src_right]
96
+
97
+ dst_top = int((overlap.top - rect.top) * scale)
98
+ dst_bottom = int((overlap.bottom - rect.top) * scale)
99
+ dst_left = int((overlap.left - rect.left) * scale)
100
+ dst_right = int((overlap.right - rect.left) * scale)
101
+
102
+ src_image = cv2.resize(
103
+ src_image, (dst_right - dst_left, dst_bottom - dst_top)
104
+ )
105
+ image[dst_top:dst_bottom, dst_left:dst_right] = src_image
106
+ # dst_bottom = overlap.bottom-rect.top
107
+ # dst_left = overlap.left-rect.left
108
+ # dst_right = overlap.right-rect.left
109
+ # image[
110
+ # overlap.y_range.min_val
111
+ # - rect.y_range.min_val : overlap.y_range.max_val
112
+ # - rect.y_range.min_val,
113
+ # overlap.x_range.min_val
114
+ # - rect.x_range.min_val : overlap.x_range.max_val
115
+ # - rect.x_range.min_val,
116
+ # ] = src[
117
+ # overlap.y_range.min_val - originy : overlap.y_range.max_val - originy,
118
+ # overlap.x_range.min_val - originx : overlap.x_range.max_val - originx,
119
+ # ]
120
+ return image
121
+
122
+ def put_image(self, position, image, linear_alpha=None, full_alpha=None):
123
+ """
124
+ split the existent tiles
125
+ and put a big single tile.
126
+ the image must be larger than a single tile.
127
+ otherwise, a different algorithm is required.
128
+ """
129
+ h, w = image.shape[:2]
130
+ rect = Rect.from_bounds(
131
+ position[0], position[0] + w, position[1], position[1] + h
132
+ )
133
+ # expand the canvas
134
+ if self.rect is None:
135
+ self.rect = rect
136
+ else:
137
+ self.rect |= rect
138
+ for tile, overlap in self.tiles_containing(rect, includeempty=True):
139
+ if overlap is None:
140
+ continue
141
+ if tile not in self.tiles:
142
+ self.tiles[tile] = np.zeros(
143
+ (self.tilesize[1], self.tilesize[0], 3), dtype=np.uint8
144
+ )
145
+ self.tiles[tile][:, :] = self.bgcolor
146
+ src = self.tiles[tile]
147
+ originx, originy = tile
148
+
149
+ dy0 = overlap.top - originy
150
+ dy1 = overlap.bottom - originy
151
+ dx0 = overlap.left - originx
152
+ dx1 = overlap.right - originx
153
+ sx0 = overlap.left - rect.left
154
+ sx1 = overlap.right - rect.left
155
+ sy0 = overlap.top - rect.top
156
+ sy1 = overlap.bottom - rect.top
157
+ if linear_alpha is None and full_alpha is None:
158
+ src[
159
+ dy0:dy1,
160
+ dx0:dx1,
161
+ ] = image[
162
+ sy0:sy1,
163
+ sx0:sx1,
164
+ ]
165
+ else:
166
+ if full_alpha is not None:
167
+ alpha = full_alpha[:, :, np.newaxis]
168
+ src[dy0:dy1, dx0:dx1, :] = (
169
+ alpha[sy0:sy1, sx0:sx1, :] * image[sy0:sy1, sx0:sx1, :]
170
+ + (1 - alpha[sy0:sy1, sx0:sx1, :]) * src[dy0:dy1, dx0:dx1, :]
171
+ )
172
+ else:
173
+ alpha = linear_alpha[np.newaxis, :, np.newaxis]
174
+ src[dy0:dy1, dx0:dx1, :] = (
175
+ alpha[:, sx0:sx1, :] * image[sy0:sy1, sx0:sx1, :]
176
+ + (1 - alpha[:, sx0:sx1, :]) * src[dy0:dy1, dx0:dx1, :]
177
+ )
178
+
179
+ # rewrite the item explicitly (for caching)
180
+ self.tiles[tile] = src
181
+
182
+ def get_image(self, width: int = 0):
183
+ # widthを指定すると縮小する。
184
+ return self.get_region(self.rect, width)
185
+
186
+
187
+ def test():
188
+ import sys
189
+ import cv2
190
+ import logging
191
+
192
+ logging.basicConfig(level=logging.DEBUG)
193
+
194
+ png = sys.argv[1]
195
+ tilesize = int(sys.argv[2])
196
+ with TiledImage(tilesize=tilesize) as tiled_image:
197
+ tiled_image[20:, 40:] = cv2.imread(png)
198
+ tiled_image[10:, 20:] = cv2.imread(png)
199
+ image = tiled_image[:, :]
200
+ image = tiled_image.get_image(width=100)
201
+ cv2.imshow("image", image)
202
+ cv2.waitKey(0)
203
+ cv2.destroyAllWindows()
204
+
205
+
206
+ if __name__ == "__main__":
207
+ test()
@@ -1 +0,0 @@
1
- __version__ = "0.1.1"
@@ -1,177 +0,0 @@
1
- import logging
2
-
3
- # external modules
4
- import numpy as np
5
-
6
- # a range is always spacified with the min and max=min+width
7
- # 2d region consists of two ranges.
8
-
9
-
10
- def overlap(r1, r2):
11
- """
12
- True if the give regions (1D) overlap
13
-
14
- there are 6 possible orders
15
- ( ) [ ] x
16
- ( [ ) ] o
17
- ( [ ] ) o
18
- [ ( ) ] o
19
- [ ( ] ) o
20
- [ ] ( ) x
21
- ! { ) [ | ] ( }
22
- ie [ ) && ( ]
23
- """
24
- if r1[0] < r2[1] and r2[0] < r1[1]:
25
- return max(r1[0], r2[0]), min(r1[1], r2[1])
26
- return None
27
-
28
-
29
- # It should also return the overlapping region
30
- def overlap2D(r1, r2):
31
- x = overlap(r1[0], r2[0])
32
- if x is not None:
33
- y = overlap(r1[1], r2[1])
34
- if y is not None:
35
- return x, y
36
- return None
37
-
38
-
39
- class TiledImage:
40
- """
41
- it has no size.
42
- size is determined by the tiles.
43
- !!! it is better to fix the tile size. (128x128, for example)
44
- """
45
-
46
- def __init__(self, tilesize=128, bgcolor=(100, 100, 100)):
47
- self.tiles = dict()
48
- if type(tilesize) is int:
49
- self.tilesize = (tilesize, tilesize)
50
- else:
51
- assert type(tilesize) is tuple
52
- self.tilesize = tilesize
53
- self.region = None
54
- self.bgcolor = np.array(bgcolor)
55
-
56
- def __enter__(self):
57
- return self
58
-
59
- def __exit__(self, exc_type, exc_val, exc_tb):
60
- pass # TiledImageは特別なクリーンアップ処理は必要ありません
61
-
62
- def tiles_containing(self, region, includeempty=False):
63
- """
64
- return the tiles containing the given region
65
- """
66
- logger = logging.getLogger()
67
- t = []
68
- xran, yran = region
69
- xran = (
70
- xran[0] // self.tilesize[0],
71
- (xran[1] + self.tilesize[0] - 1) // self.tilesize[0],
72
- )
73
- yran = (
74
- yran[0] // self.tilesize[1],
75
- (yran[1] + self.tilesize[1] - 1) // self.tilesize[1],
76
- )
77
- for ix in range(xran[0], xran[1]):
78
- for iy in range(yran[0], yran[1]):
79
- tile = (ix * self.tilesize[0], iy * self.tilesize[1])
80
- logger.debug("Tile: {0}".format(tile))
81
- if (tile in self.tiles) or includeempty:
82
- tregion = (
83
- (tile[0], tile[0] + self.tilesize[0]),
84
- (tile[1], tile[1] + self.tilesize[1]),
85
- )
86
- o = overlap2D(tregion, region)
87
- t.append((tile, o))
88
- return t
89
-
90
- def get_region(self, region=None):
91
- logger = logging.getLogger()
92
- # logger.debug("Get region {0} {1}".format(region,self.tiles))
93
- if region is None:
94
- region = self.region
95
- xrange, yrange = region
96
- image = np.zeros(
97
- (yrange[1] - yrange[0], xrange[1] - xrange[0], 3), dtype=np.uint8
98
- )
99
- image[:, :] = self.bgcolor
100
- for tile, overlap in self.tiles_containing(region):
101
- # logger.debug("Should get a tile at {0} {1}".format(tile,self.tiles))
102
- src = self.tiles[tile]
103
- originx, originy = tile
104
- xr, yr = overlap
105
- image[
106
- yr[0] - yrange[0] : yr[1] - yrange[0],
107
- xr[0] - xrange[0] : xr[1] - xrange[0],
108
- :,
109
- ] = src[
110
- yr[0] - originy : yr[1] - originy, xr[0] - originx : xr[1] - originx, :
111
- ]
112
- return image
113
-
114
- def put_image(self, position, image, linear_alpha=None):
115
- """
116
- split the existent tiles
117
- and put a big single tile.
118
- the image must be larger than a single tile.
119
- otherwise, a different algorithm is required.
120
- """
121
- h, w = image.shape[:2]
122
- xrange, yrange = (position[0], position[0] + w), (position[1], position[1] + h)
123
- region = (xrange, yrange)
124
- for tile, overlap in self.tiles_containing(region, includeempty=True):
125
- if tile not in self.tiles:
126
- self.tiles[tile] = np.zeros(
127
- (self.tilesize[1], self.tilesize[0], 3), dtype=np.uint8
128
- )
129
- self.tiles[tile][:, :] = self.bgcolor
130
- src = self.tiles[tile]
131
- originx, originy = tile
132
- xr, yr = overlap
133
- if linear_alpha is None:
134
- src[
135
- yr[0] - originy : yr[1] - originy,
136
- xr[0] - originx : xr[1] - originx,
137
- :,
138
- ] = image[
139
- yr[0] - yrange[0] : yr[1] - yrange[0],
140
- xr[0] - xrange[0] : xr[1] - xrange[0],
141
- :,
142
- ]
143
- else:
144
- dy0 = yr[0] - originy
145
- dy1 = yr[1] - originy
146
- dx0 = xr[0] - originx
147
- dx1 = xr[1] - originx
148
- sx0 = xr[0] - xrange[0]
149
- sx1 = xr[1] - xrange[0]
150
- sy0 = yr[0] - yrange[0]
151
- sy1 = yr[1] - yrange[0]
152
- src[dy0:dy1, dx0:dx1, :] = (
153
- linear_alpha[sx0:sx1, :] * image[sy0:sy1, sx0:sx1, :]
154
- + (1 - linear_alpha[sx0:sx1, :]) * src[dy0:dy1, dx0:dx1, :]
155
- )
156
-
157
- # rewrite the item explicitly (for caching)
158
- self.tiles[tile] = src
159
- if self.region is None:
160
- self.region = (
161
- (position[0], position[0] + w),
162
- (position[1], position[1] + h),
163
- )
164
- else:
165
- self.region = (
166
- (
167
- min(self.region[0][0], position[0]),
168
- max(self.region[0][1], position[0] + w),
169
- ),
170
- (
171
- min(self.region[1][0], position[1]),
172
- max(self.region[1][1], position[1] + h),
173
- ),
174
- )
175
-
176
- def get_image(self):
177
- return self.get_region(self.region)
File without changes