tiledimage 0.4__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.
- tiledimage-0.6.0/PKG-INFO +26 -0
- tiledimage-0.6.0/README.md +5 -0
- {tiledimage-0.4 → tiledimage-0.6.0}/pyproject.toml +5 -2
- tiledimage-0.6.0/tiledimage/__init__.py +2 -0
- {tiledimage-0.4 → tiledimage-0.6.0}/tiledimage/cachedimage.py +63 -13
- {tiledimage-0.4 → tiledimage-0.6.0}/tiledimage/pngs2.py +0 -4
- tiledimage-0.6.0/tiledimage/simpleimage.py +175 -0
- {tiledimage-0.4 → tiledimage-0.6.0}/tiledimage/tilecache.py +4 -11
- tiledimage-0.6.0/tiledimage/tiledimage.py +207 -0
- tiledimage-0.4/PKG-INFO +0 -149
- tiledimage-0.4/README.md +0 -131
- tiledimage-0.4/tiledimage/__init__.py +0 -1
- tiledimage-0.4/tiledimage/tiledimage.py +0 -177
- {tiledimage-0.4 → tiledimage-0.6.0}/tiledimage/2pngs.py +0 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: tiledimage
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: tools for tiled image that can be cached on a filesystem.
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: vitroid
|
|
7
|
+
Author-email: vitroid@gmail.com
|
|
8
|
+
Requires-Python: >=3.11,<4.0
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Requires-Dist: opencv-python-headless (>=4.11.0.86,<5.0.0.0)
|
|
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)
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
#TiledImage
|
|
22
|
+
This provides tools for tiled image that can be cached on a filesystem.
|
|
23
|
+
|
|
24
|
+
It is developed to reduce the memory usage when handling an image that is too huge to show all at a time.
|
|
25
|
+
It is developed for the [TrainScanner](https://github.com/vitroid/TrainScanner).
|
|
26
|
+
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
#TiledImage
|
|
2
|
+
This provides tools for tiled image that can be cached on a filesystem.
|
|
3
|
+
|
|
4
|
+
It is developed to reduce the memory usage when handling an image that is too huge to show all at a time.
|
|
5
|
+
It is developed for the [TrainScanner](https://github.com/vitroid/TrainScanner).
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "tiledimage"
|
|
3
|
-
version = "0.
|
|
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
|
+
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"
|
|
@@ -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.
|
|
37
|
-
|
|
38
|
-
info
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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.
|
|
78
|
-
yrange=self.
|
|
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.
|
|
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()
|
|
@@ -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 "{
|
|
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:{
|
|
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
|
-
|
|
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:{
|
|
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()
|
tiledimage-0.4/PKG-INFO
DELETED
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: tiledimage
|
|
3
|
-
Version: 0.4
|
|
4
|
-
Summary: tools for tiled image that can be cached on a filesystem.
|
|
5
|
-
License: MIT
|
|
6
|
-
Author: vitroid
|
|
7
|
-
Author-email: vitroid@gmail.com
|
|
8
|
-
Requires-Python: >=3.10,<4.0
|
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
-
Classifier: Programming Language :: Python :: 3
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
-
Requires-Dist: opencv-python-headless (>=4.11.0.86,<5.0.0.0)
|
|
15
|
-
Requires-Dist: pylru (>=1.2.1,<2.0.0)
|
|
16
|
-
Description-Content-Type: text/markdown
|
|
17
|
-
|
|
18
|
-
# TiledImage
|
|
19
|
-
|
|
20
|
-
大きな画像を効率的に扱うための Python ライブラリです。メモリ使用量を抑えながら、大きな画像をタイル(小さな断片)に分割して管理します。
|
|
21
|
-
|
|
22
|
-
## 特徴
|
|
23
|
-
|
|
24
|
-
- 大きな画像をタイルに分割して管理
|
|
25
|
-
- ファイルシステム上でのキャッシュ機能
|
|
26
|
-
- メモリ効率の良い画像処理
|
|
27
|
-
- コンテキストマネージャ(`with`文)による簡単な使用
|
|
28
|
-
|
|
29
|
-
## インストール
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
pip install tiledimage
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## 基本的な使い方
|
|
36
|
-
|
|
37
|
-
### 画像の分割(PNG → PNGs)
|
|
38
|
-
|
|
39
|
-
```python
|
|
40
|
-
from tiledimage import CachedImage
|
|
41
|
-
import cv2
|
|
42
|
-
|
|
43
|
-
# 画像を読み込み
|
|
44
|
-
img = cv2.imread("large_image.png")
|
|
45
|
-
|
|
46
|
-
# タイル化して保存
|
|
47
|
-
with CachedImage(
|
|
48
|
-
mode="new",
|
|
49
|
-
dir="output.pngs",
|
|
50
|
-
tilesize=(64, 64),
|
|
51
|
-
cachesize=10,
|
|
52
|
-
bgcolor=(255, 255, 255), # 背景色(白)
|
|
53
|
-
fileext="jpg"
|
|
54
|
-
) as tiled:
|
|
55
|
-
tiled.put_image((0, 0), img) # 画像を配置
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
### 画像の結合(PNGs → PNG)
|
|
59
|
-
|
|
60
|
-
```python
|
|
61
|
-
from tiledimage import CachedImage
|
|
62
|
-
import cv2
|
|
63
|
-
|
|
64
|
-
# タイル化された画像を読み込み
|
|
65
|
-
with CachedImage(mode="inherit", dir="input.pngs") as tiled:
|
|
66
|
-
# 全体の画像を取得
|
|
67
|
-
full_image = tiled.get_image()
|
|
68
|
-
# 保存
|
|
69
|
-
cv2.imwrite("combined_image.png", full_image)
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
### コマンドラインツール
|
|
73
|
-
|
|
74
|
-
画像の分割:
|
|
75
|
-
|
|
76
|
-
```bash
|
|
77
|
-
pngs2 input.png output.pngs
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
画像の結合:
|
|
81
|
-
|
|
82
|
-
```bash
|
|
83
|
-
2pngs input.pngs output.png
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
## API リファレンス
|
|
87
|
-
|
|
88
|
-
### CachedImage
|
|
89
|
-
|
|
90
|
-
メインのクラス。タイル化された画像を管理します。
|
|
91
|
-
|
|
92
|
-
```python
|
|
93
|
-
CachedImage(
|
|
94
|
-
mode, # "new" または "inherit"
|
|
95
|
-
dir="image.pngs", # タイルの保存ディレクトリ
|
|
96
|
-
tilesize=128, # タイルのサイズ(整数またはタプル)
|
|
97
|
-
cachesize=10, # キャッシュサイズ
|
|
98
|
-
fileext="png", # タイルのファイル形式
|
|
99
|
-
bgcolor=(0,0,0), # 背景色
|
|
100
|
-
hook=None, # タイル書き換え時のフック関数
|
|
101
|
-
disposal=False # 終了時にディレクトリを削除するか
|
|
102
|
-
)
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
#### 主要メソッド
|
|
106
|
-
|
|
107
|
-
- `put_image(pos, img, linear_alpha=None)`: 画像を配置
|
|
108
|
-
- `get_image()`: 全体の画像を取得
|
|
109
|
-
- `write_info()`: 情報を保存(通常は自動的に呼ばれる)
|
|
110
|
-
|
|
111
|
-
### TiledImage
|
|
112
|
-
|
|
113
|
-
基本的なタイル画像クラス。キャッシュ機能はありません。
|
|
114
|
-
|
|
115
|
-
```python
|
|
116
|
-
TiledImage(
|
|
117
|
-
tilesize=128, # タイルのサイズ
|
|
118
|
-
bgcolor=(100,100,100) # 背景色
|
|
119
|
-
)
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
## 開発者向け情報
|
|
123
|
-
|
|
124
|
-
### テスト
|
|
125
|
-
|
|
126
|
-
```bash
|
|
127
|
-
make test
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
### ビルド
|
|
131
|
-
|
|
132
|
-
```bash
|
|
133
|
-
make build
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
### デプロイ
|
|
137
|
-
|
|
138
|
-
```bash
|
|
139
|
-
make deploy
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
## ライセンス
|
|
143
|
-
|
|
144
|
-
MIT License
|
|
145
|
-
|
|
146
|
-
## 作者
|
|
147
|
-
|
|
148
|
-
Masakazu Matsumoto (vitroid@gmail.com)
|
|
149
|
-
|
tiledimage-0.4/README.md
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
# TiledImage
|
|
2
|
-
|
|
3
|
-
大きな画像を効率的に扱うための Python ライブラリです。メモリ使用量を抑えながら、大きな画像をタイル(小さな断片)に分割して管理します。
|
|
4
|
-
|
|
5
|
-
## 特徴
|
|
6
|
-
|
|
7
|
-
- 大きな画像をタイルに分割して管理
|
|
8
|
-
- ファイルシステム上でのキャッシュ機能
|
|
9
|
-
- メモリ効率の良い画像処理
|
|
10
|
-
- コンテキストマネージャ(`with`文)による簡単な使用
|
|
11
|
-
|
|
12
|
-
## インストール
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
pip install tiledimage
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
## 基本的な使い方
|
|
19
|
-
|
|
20
|
-
### 画像の分割(PNG → PNGs)
|
|
21
|
-
|
|
22
|
-
```python
|
|
23
|
-
from tiledimage import CachedImage
|
|
24
|
-
import cv2
|
|
25
|
-
|
|
26
|
-
# 画像を読み込み
|
|
27
|
-
img = cv2.imread("large_image.png")
|
|
28
|
-
|
|
29
|
-
# タイル化して保存
|
|
30
|
-
with CachedImage(
|
|
31
|
-
mode="new",
|
|
32
|
-
dir="output.pngs",
|
|
33
|
-
tilesize=(64, 64),
|
|
34
|
-
cachesize=10,
|
|
35
|
-
bgcolor=(255, 255, 255), # 背景色(白)
|
|
36
|
-
fileext="jpg"
|
|
37
|
-
) as tiled:
|
|
38
|
-
tiled.put_image((0, 0), img) # 画像を配置
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
### 画像の結合(PNGs → PNG)
|
|
42
|
-
|
|
43
|
-
```python
|
|
44
|
-
from tiledimage import CachedImage
|
|
45
|
-
import cv2
|
|
46
|
-
|
|
47
|
-
# タイル化された画像を読み込み
|
|
48
|
-
with CachedImage(mode="inherit", dir="input.pngs") as tiled:
|
|
49
|
-
# 全体の画像を取得
|
|
50
|
-
full_image = tiled.get_image()
|
|
51
|
-
# 保存
|
|
52
|
-
cv2.imwrite("combined_image.png", full_image)
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
### コマンドラインツール
|
|
56
|
-
|
|
57
|
-
画像の分割:
|
|
58
|
-
|
|
59
|
-
```bash
|
|
60
|
-
pngs2 input.png output.pngs
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
画像の結合:
|
|
64
|
-
|
|
65
|
-
```bash
|
|
66
|
-
2pngs input.pngs output.png
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
## API リファレンス
|
|
70
|
-
|
|
71
|
-
### CachedImage
|
|
72
|
-
|
|
73
|
-
メインのクラス。タイル化された画像を管理します。
|
|
74
|
-
|
|
75
|
-
```python
|
|
76
|
-
CachedImage(
|
|
77
|
-
mode, # "new" または "inherit"
|
|
78
|
-
dir="image.pngs", # タイルの保存ディレクトリ
|
|
79
|
-
tilesize=128, # タイルのサイズ(整数またはタプル)
|
|
80
|
-
cachesize=10, # キャッシュサイズ
|
|
81
|
-
fileext="png", # タイルのファイル形式
|
|
82
|
-
bgcolor=(0,0,0), # 背景色
|
|
83
|
-
hook=None, # タイル書き換え時のフック関数
|
|
84
|
-
disposal=False # 終了時にディレクトリを削除するか
|
|
85
|
-
)
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
#### 主要メソッド
|
|
89
|
-
|
|
90
|
-
- `put_image(pos, img, linear_alpha=None)`: 画像を配置
|
|
91
|
-
- `get_image()`: 全体の画像を取得
|
|
92
|
-
- `write_info()`: 情報を保存(通常は自動的に呼ばれる)
|
|
93
|
-
|
|
94
|
-
### TiledImage
|
|
95
|
-
|
|
96
|
-
基本的なタイル画像クラス。キャッシュ機能はありません。
|
|
97
|
-
|
|
98
|
-
```python
|
|
99
|
-
TiledImage(
|
|
100
|
-
tilesize=128, # タイルのサイズ
|
|
101
|
-
bgcolor=(100,100,100) # 背景色
|
|
102
|
-
)
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
## 開発者向け情報
|
|
106
|
-
|
|
107
|
-
### テスト
|
|
108
|
-
|
|
109
|
-
```bash
|
|
110
|
-
make test
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
### ビルド
|
|
114
|
-
|
|
115
|
-
```bash
|
|
116
|
-
make build
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
### デプロイ
|
|
120
|
-
|
|
121
|
-
```bash
|
|
122
|
-
make deploy
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
## ライセンス
|
|
126
|
-
|
|
127
|
-
MIT License
|
|
128
|
-
|
|
129
|
-
## 作者
|
|
130
|
-
|
|
131
|
-
Masakazu Matsumoto (vitroid@gmail.com)
|
|
@@ -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
|