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.
@@ -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.4"
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()
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