large-image-source-multi 1.32.12.dev11__tar.gz → 1.32.12.dev14__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.
- {large_image_source_multi-1.32.12.dev11/large_image_source_multi.egg-info → large_image_source_multi-1.32.12.dev14}/PKG-INFO +3 -3
- {large_image_source_multi-1.32.12.dev11 → large_image_source_multi-1.32.12.dev14}/large_image_source_multi/__init__.py +254 -50
- {large_image_source_multi-1.32.12.dev11 → large_image_source_multi-1.32.12.dev14/large_image_source_multi.egg-info}/PKG-INFO +3 -3
- large_image_source_multi-1.32.12.dev14/large_image_source_multi.egg-info/requires.txt +9 -0
- large_image_source_multi-1.32.12.dev11/large_image_source_multi.egg-info/requires.txt +0 -9
- {large_image_source_multi-1.32.12.dev11 → large_image_source_multi-1.32.12.dev14}/LICENSE +0 -0
- {large_image_source_multi-1.32.12.dev11 → large_image_source_multi-1.32.12.dev14}/README.rst +0 -0
- {large_image_source_multi-1.32.12.dev11 → large_image_source_multi-1.32.12.dev14}/docs/specification.rst +0 -0
- {large_image_source_multi-1.32.12.dev11 → large_image_source_multi-1.32.12.dev14}/large_image_source_multi/girder_source.py +0 -0
- {large_image_source_multi-1.32.12.dev11 → large_image_source_multi-1.32.12.dev14}/large_image_source_multi.egg-info/SOURCES.txt +0 -0
- {large_image_source_multi-1.32.12.dev11 → large_image_source_multi-1.32.12.dev14}/large_image_source_multi.egg-info/dependency_links.txt +0 -0
- {large_image_source_multi-1.32.12.dev11 → large_image_source_multi-1.32.12.dev14}/large_image_source_multi.egg-info/entry_points.txt +0 -0
- {large_image_source_multi-1.32.12.dev11 → large_image_source_multi-1.32.12.dev14}/large_image_source_multi.egg-info/top_level.txt +0 -0
- {large_image_source_multi-1.32.12.dev11 → large_image_source_multi-1.32.12.dev14}/pyproject.toml +0 -0
- {large_image_source_multi-1.32.12.dev11 → large_image_source_multi-1.32.12.dev14}/setup.cfg +0 -0
- {large_image_source_multi-1.32.12.dev11 → large_image_source_multi-1.32.12.dev14}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: large-image-source-multi
|
|
3
|
-
Version: 1.32.12.
|
|
3
|
+
Version: 1.32.12.dev14
|
|
4
4
|
Summary: A tilesource for large_image to composite other tile sources
|
|
5
5
|
Home-page: https://github.com/girder/large_image
|
|
6
6
|
Author: Kitware, Inc.
|
|
@@ -17,12 +17,12 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
17
17
|
Description-Content-Type: text/x-rst
|
|
18
18
|
License-File: LICENSE
|
|
19
19
|
Requires-Dist: jsonschema
|
|
20
|
-
Requires-Dist: large-image>=1.32.12.
|
|
20
|
+
Requires-Dist: large-image>=1.32.12.dev14
|
|
21
21
|
Requires-Dist: pyyaml
|
|
22
22
|
Provides-Extra: all
|
|
23
23
|
Requires-Dist: scikit-image; extra == "all"
|
|
24
24
|
Provides-Extra: girder
|
|
25
|
-
Requires-Dist: girder-large-image>=1.32.12.
|
|
25
|
+
Requires-Dist: girder-large-image>=1.32.12.dev14; extra == "girder"
|
|
26
26
|
Dynamic: author
|
|
27
27
|
Dynamic: author-email
|
|
28
28
|
Dynamic: classifier
|
|
@@ -6,6 +6,7 @@ import math
|
|
|
6
6
|
import os
|
|
7
7
|
import re
|
|
8
8
|
import threading
|
|
9
|
+
import warnings
|
|
9
10
|
from importlib.metadata import PackageNotFoundError
|
|
10
11
|
from importlib.metadata import version as _importlib_version
|
|
11
12
|
from pathlib import Path
|
|
@@ -28,6 +29,7 @@ except PackageNotFoundError:
|
|
|
28
29
|
|
|
29
30
|
jsonschema = None
|
|
30
31
|
_validator = None
|
|
32
|
+
skimage_transform = None
|
|
31
33
|
|
|
32
34
|
|
|
33
35
|
def _lazyImport():
|
|
@@ -47,6 +49,20 @@ def _lazyImport():
|
|
|
47
49
|
raise TileSourceError(msg)
|
|
48
50
|
|
|
49
51
|
|
|
52
|
+
def _lazyImportSkimageTransform():
|
|
53
|
+
"""
|
|
54
|
+
Import the skimage.transform module. This is only needed when a TPS warp is used.
|
|
55
|
+
"""
|
|
56
|
+
global skimage_transform
|
|
57
|
+
|
|
58
|
+
if skimage_transform is None:
|
|
59
|
+
try:
|
|
60
|
+
import skimage.transform as skimage_transform
|
|
61
|
+
except ImportError:
|
|
62
|
+
msg = 'scikit-image transform module not found.'
|
|
63
|
+
raise TileSourceError(msg)
|
|
64
|
+
|
|
65
|
+
|
|
50
66
|
SourceEntrySchema = {
|
|
51
67
|
'type': 'object',
|
|
52
68
|
'additionalProperties': True,
|
|
@@ -270,6 +286,54 @@ SourceEntrySchema = {
|
|
|
270
286
|
# TODO: Add polygon option
|
|
271
287
|
# TODO: Add postTransform option
|
|
272
288
|
},
|
|
289
|
+
'warp': {
|
|
290
|
+
'description':
|
|
291
|
+
'An object describing a series of landmarks which have both '
|
|
292
|
+
'a source location and a destination location. These sets of points '
|
|
293
|
+
'define a warp (thin plate spline or affine transform) that will '
|
|
294
|
+
'be applied to the source image.',
|
|
295
|
+
'type': 'object',
|
|
296
|
+
'properties': {
|
|
297
|
+
'src': {
|
|
298
|
+
'description':
|
|
299
|
+
'The set of source locations for landmarks defining a warp. '
|
|
300
|
+
'This can be described by a list of [x, y] points or a mapping of '
|
|
301
|
+
'unique marker IDs to [x, y] points.',
|
|
302
|
+
'type': ['array', 'object'],
|
|
303
|
+
'items': {
|
|
304
|
+
'type': 'array',
|
|
305
|
+
'items': {'type': 'number'},
|
|
306
|
+
'minItems': 2,
|
|
307
|
+
'maxItems': 2,
|
|
308
|
+
},
|
|
309
|
+
'additionalProperties': {
|
|
310
|
+
'type': 'array',
|
|
311
|
+
'items': {'type': 'number'},
|
|
312
|
+
'minItems': 2,
|
|
313
|
+
'maxItems': 2,
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
'dst': {
|
|
317
|
+
'description':
|
|
318
|
+
'The set of destination locations for landmarks defining a warp. '
|
|
319
|
+
'This can be described by a list of [x, y] points or a mapping of '
|
|
320
|
+
'unique marker IDs to [x, y] points.',
|
|
321
|
+
'type': ['array', 'object'],
|
|
322
|
+
'items': {
|
|
323
|
+
'type': 'array',
|
|
324
|
+
'items': {'type': 'number'},
|
|
325
|
+
'minItems': 2,
|
|
326
|
+
'maxItems': 2,
|
|
327
|
+
},
|
|
328
|
+
'additionalProperties': {
|
|
329
|
+
'type': 'array',
|
|
330
|
+
'items': {'type': 'number'},
|
|
331
|
+
'minItems': 2,
|
|
332
|
+
'maxItems': 2,
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
},
|
|
273
337
|
'scale': {
|
|
274
338
|
'description':
|
|
275
339
|
'Values less than 1 will downsample the source. '
|
|
@@ -580,6 +644,62 @@ class MultiFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
580
644
|
source['path'] = source['path'].resolve(False)
|
|
581
645
|
return sources
|
|
582
646
|
|
|
647
|
+
def _getWarp(self, warp, m):
|
|
648
|
+
"""
|
|
649
|
+
Preprocess a warp specification and transformation matrix prior to applying the warp.
|
|
650
|
+
|
|
651
|
+
:param warp: A dictionary with a warp specification,
|
|
652
|
+
adhering to the schema for ``MultiSource.SourceEntry.position.warp``.
|
|
653
|
+
:param m: An ndarray representing a transformation matrix,
|
|
654
|
+
that may be modified according to the warp specification.
|
|
655
|
+
"""
|
|
656
|
+
ret = None
|
|
657
|
+
warp_src = warp.get('src')
|
|
658
|
+
warp_dst = warp.get('dst')
|
|
659
|
+
if isinstance(warp_src, dict) and isinstance(warp_dst, dict):
|
|
660
|
+
src_key_set = set(warp_src.keys())
|
|
661
|
+
dst_key_set = set(warp_dst.keys())
|
|
662
|
+
keys = list(src_key_set & dst_key_set)
|
|
663
|
+
warp_src = [warp_src[key] for key in keys]
|
|
664
|
+
warp_dst = [warp_dst[key] for key in keys]
|
|
665
|
+
unused = (src_key_set | dst_key_set) - set(keys)
|
|
666
|
+
if len(unused):
|
|
667
|
+
msg = (
|
|
668
|
+
'The following keys did not have a value in both src and dst, '
|
|
669
|
+
f'so they were dropped: {unused}.'
|
|
670
|
+
)
|
|
671
|
+
warnings.warn(msg, stacklevel=2)
|
|
672
|
+
elif isinstance(warp_src, list) and isinstance(warp_dst, list):
|
|
673
|
+
pass
|
|
674
|
+
else:
|
|
675
|
+
msg = 'warp src and warp dst must either be both dicts or both lists.'
|
|
676
|
+
raise TileSourceError(msg)
|
|
677
|
+
if len(warp_src) != len(warp_dst):
|
|
678
|
+
msg = 'warp src and warp dst must have the same number of points.'
|
|
679
|
+
raise TileSourceError(msg)
|
|
680
|
+
if len(warp_src) < 1:
|
|
681
|
+
msg = 'warp src and warp dst must have at least one point.'
|
|
682
|
+
raise TileSourceError(msg)
|
|
683
|
+
|
|
684
|
+
warp_src = np.array(warp_src or []).astype(float)
|
|
685
|
+
warp_dst = np.array(warp_dst or []).astype(float)
|
|
686
|
+
warp_src = warp_src[:min(warp_src.shape[0], warp_dst.shape[0]), :]
|
|
687
|
+
warp_dst = warp_dst[:warp_src.shape[0], :]
|
|
688
|
+
if warp_src.shape[0] < 1:
|
|
689
|
+
pass
|
|
690
|
+
elif warp_src.shape[0] == 1:
|
|
691
|
+
m[0][2] += warp_dst[0][0] - warp_src[0][0]
|
|
692
|
+
m[1][2] += warp_dst[0][1] - warp_src[0][1]
|
|
693
|
+
elif warp_src.shape[0] <= 3:
|
|
694
|
+
transformer = skimage_transform.AffineTransform()
|
|
695
|
+
transformer.estimate(warp_src, warp_dst)
|
|
696
|
+
m = np.dot(transformer.params, m)
|
|
697
|
+
else:
|
|
698
|
+
warp_dst = np.dot(m, np.hstack([warp_dst, np.ones((len(warp_dst), 1))]).T).T[:, :2]
|
|
699
|
+
ret = {'src': warp_src, 'dst': warp_dst}
|
|
700
|
+
m = np.identity(3)
|
|
701
|
+
return ret, m
|
|
702
|
+
|
|
583
703
|
def _sourceBoundingBox(self, source, width, height):
|
|
584
704
|
"""
|
|
585
705
|
Given a source with a possible transform and an image width and height,
|
|
@@ -597,6 +717,8 @@ class MultiFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
597
717
|
bbox = {'left': 0, 'top': 0, 'right': width, 'bottom': height}
|
|
598
718
|
if not pos:
|
|
599
719
|
return bbox
|
|
720
|
+
if 'warp' in pos:
|
|
721
|
+
_lazyImportSkimageTransform()
|
|
600
722
|
x0, y0, x1, y1 = 0, 0, width, height
|
|
601
723
|
if 'crop' in pos:
|
|
602
724
|
x0 = min(max(pos['crop'].get('left', x0), 0), width)
|
|
@@ -612,6 +734,8 @@ class MultiFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
612
734
|
m[1][0] = pos.get('s21', 0) * pos.get('scale', 1)
|
|
613
735
|
m[1][1] = pos.get('s22', 1) * pos.get('scale', 1)
|
|
614
736
|
m[1][2] = pos.get('y', 0)
|
|
737
|
+
if 'warp' in pos and skimage_transform is not None:
|
|
738
|
+
bbox['warp'], m = self._getWarp(pos.get('warp'), m)
|
|
615
739
|
if not np.array_equal(m, np.identity(3)):
|
|
616
740
|
bbox['transform'] = m
|
|
617
741
|
try:
|
|
@@ -619,11 +743,23 @@ class MultiFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
619
743
|
except np.linalg.LinAlgError:
|
|
620
744
|
msg = 'The position for a source is not invertable (%r)'
|
|
621
745
|
raise TileSourceError(msg, pos)
|
|
622
|
-
|
|
746
|
+
warp = bbox.get('warp')
|
|
747
|
+
if warp is None:
|
|
748
|
+
transcorners = np.dot(m, corners.T)
|
|
749
|
+
elif skimage_transform is not None:
|
|
750
|
+
transformer = skimage_transform.ThinPlateSplineTransform()
|
|
751
|
+
transformer.estimate(warp.get('src'), warp.get('dst'))
|
|
752
|
+
# We might want to adjust the number of points based on some
|
|
753
|
+
# criteria such as source image size or number of warp points
|
|
754
|
+
corners = self._perimeterPoints(x0, y0, x1, y1, 8)
|
|
755
|
+
transcorners = transformer(corners).T
|
|
623
756
|
bbox['left'] = min(transcorners[0])
|
|
624
757
|
bbox['top'] = min(transcorners[1])
|
|
625
758
|
bbox['right'] = max(transcorners[0])
|
|
626
759
|
bbox['bottom'] = max(transcorners[1])
|
|
760
|
+
# TODO: Maybe inflate this a bit for warp because of edge effects?
|
|
761
|
+
# That is, it can warp outside of the specified box because we don't
|
|
762
|
+
# have infinite perimeter sampling
|
|
627
763
|
return bbox
|
|
628
764
|
|
|
629
765
|
def _axisKey(self, source, value, key):
|
|
@@ -1044,8 +1180,34 @@ class MultiFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
1044
1180
|
base[y:y + tile.shape[0], x:x + tile.shape[1], :] = tile
|
|
1045
1181
|
return base
|
|
1046
1182
|
|
|
1047
|
-
def
|
|
1048
|
-
|
|
1183
|
+
def _perimeterPoints(self, x0, y0, x1, y1, sample):
|
|
1184
|
+
return np.vstack([
|
|
1185
|
+
np.column_stack([np.linspace(x0, x1, sample, endpoint=False), np.full(sample, y0)]),
|
|
1186
|
+
np.column_stack([np.full(sample, x1), np.linspace(y0, y1, sample, endpoint=False)]),
|
|
1187
|
+
np.column_stack([np.linspace(x1, x0, sample, endpoint=False), np.full(sample, y1)]),
|
|
1188
|
+
np.column_stack([np.full(sample, x0), np.linspace(y1, y0, sample, endpoint=False)]),
|
|
1189
|
+
])
|
|
1190
|
+
|
|
1191
|
+
def _smallestSpacingRatio(self, srcpts, destpts):
|
|
1192
|
+
"""
|
|
1193
|
+
Find the smallest ratio of the distance between two adjacent source
|
|
1194
|
+
perimeter points (srccorners) and two destination perimeter points
|
|
1195
|
+
|
|
1196
|
+
:param srcpts: an ndarray of points that correspond with the
|
|
1197
|
+
destination points.
|
|
1198
|
+
:param dstpts: an ndarray of corresponding points.
|
|
1199
|
+
:returns: the minimum ratio of the distance between a source section
|
|
1200
|
+
and a destination section.
|
|
1201
|
+
"""
|
|
1202
|
+
srcdist = np.linalg.norm(srcpts - np.roll(srcpts, 1, axis=0), axis=1)
|
|
1203
|
+
destdist = np.linalg.norm(destpts - np.roll(destpts, 1, axis=0), axis=1)
|
|
1204
|
+
if np.all(destdist == 0):
|
|
1205
|
+
return 1
|
|
1206
|
+
minratio = np.min(srcdist[destdist != 0] / destdist[destdist != 0])
|
|
1207
|
+
return minratio or 1
|
|
1208
|
+
|
|
1209
|
+
def _getTransformedTile(self, ts, transform, corners, scale, frame, # noqa
|
|
1210
|
+
warp=None, crop=None, firstMerge=False):
|
|
1049
1211
|
"""
|
|
1050
1212
|
Determine where the target tile's corners are located on the source.
|
|
1051
1213
|
Fetch that so that we have at least sqrt(2) more resolution, then use
|
|
@@ -1059,6 +1221,11 @@ class MultiFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
1059
1221
|
corner 0 must be the upper left, 2 must be the lower right.
|
|
1060
1222
|
:param scale: scaling factor from full res to the target resolution.
|
|
1061
1223
|
:param frame: frame number of the source image.
|
|
1224
|
+
:param warp: an optional dictionary to specify a thin plate spline
|
|
1225
|
+
transformation to apply to the source image. This dictionary must
|
|
1226
|
+
contain keys 'src' and 'dst', each with a list of [x, y] points.
|
|
1227
|
+
The length of the 'src' list of points must equal the length of the
|
|
1228
|
+
'dst' list of points (both must have at least 3 points).
|
|
1062
1229
|
:param crop: an optional dictionary to crop the source image in full
|
|
1063
1230
|
resolution, untransformed coordinates. This may contain left, top,
|
|
1064
1231
|
right, and bottom values in pixels.
|
|
@@ -1068,30 +1235,52 @@ class MultiFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
1068
1235
|
:returns: a numpy array tile or None, x, y coordinates within the
|
|
1069
1236
|
target tile for the placement of the numpy tile array.
|
|
1070
1237
|
"""
|
|
1071
|
-
try:
|
|
1072
|
-
import skimage.transform
|
|
1073
|
-
except ImportError:
|
|
1074
|
-
msg = 'scikit-image is required for affine transforms.'
|
|
1075
|
-
raise TileSourceError(msg)
|
|
1076
1238
|
# From full res source to full res destination
|
|
1077
1239
|
transform = transform.copy() if transform is not None else np.identity(3)
|
|
1240
|
+
warp_src = warp_dst = None
|
|
1241
|
+
_lazyImportSkimageTransform()
|
|
1242
|
+
if warp is not None:
|
|
1243
|
+
warp_src = warp['src'].copy()
|
|
1244
|
+
warp_dst = warp['dst'].copy()
|
|
1078
1245
|
# Scale dest corners to actual size; adjust transform for the same
|
|
1079
1246
|
corners = np.array(corners)
|
|
1080
1247
|
corners[:, :2] //= scale
|
|
1081
|
-
transform[:2, :] /= scale
|
|
1082
1248
|
# Offset so our target is the actual destination array we use
|
|
1083
|
-
|
|
1084
|
-
|
|
1249
|
+
if warp is None:
|
|
1250
|
+
transform[:2, :] /= scale
|
|
1251
|
+
transform[0][2] -= corners[0][0]
|
|
1252
|
+
transform[1][2] -= corners[0][1]
|
|
1253
|
+
else:
|
|
1254
|
+
warp_dst /= scale
|
|
1255
|
+
warp_dst[:, 0] -= corners[0][0]
|
|
1256
|
+
warp_dst[:, 1] -= corners[0][1]
|
|
1085
1257
|
corners[:, :2] -= corners[0, :2]
|
|
1086
1258
|
outw, outh = corners[2][0], corners[2][1]
|
|
1087
1259
|
if not outh or not outw:
|
|
1088
1260
|
return None, 0, 0
|
|
1089
|
-
|
|
1261
|
+
if warp is None:
|
|
1262
|
+
dstcorners = corners
|
|
1263
|
+
srccorners = np.dot(np.linalg.inv(transform), np.array(corners).T).T.tolist()
|
|
1264
|
+
else:
|
|
1265
|
+
transformer = skimage_transform.ThinPlateSplineTransform()
|
|
1266
|
+
transformer.estimate(warp_dst, warp_src)
|
|
1267
|
+
# Is there any way to be smarter about the sampling?
|
|
1268
|
+
dstcorners = self._perimeterPoints(0, 0, outw, outh, 8)
|
|
1269
|
+
srccorners = transformer(dstcorners)
|
|
1090
1270
|
minx = min(c[0] for c in srccorners)
|
|
1091
1271
|
maxx = max(c[0] for c in srccorners)
|
|
1092
1272
|
miny = min(c[1] for c in srccorners)
|
|
1093
1273
|
maxy = max(c[1] for c in srccorners)
|
|
1094
|
-
|
|
1274
|
+
if warp is None:
|
|
1275
|
+
srcscale = max((maxx - minx) / outw, (maxy - miny) / outh)
|
|
1276
|
+
else:
|
|
1277
|
+
# Use the half spacing for better interpolation. If the warped
|
|
1278
|
+
# image were uniform, this could be a factor of sqrt(2), since the
|
|
1279
|
+
# image could be rotated and we might need to interpolate from
|
|
1280
|
+
# that; using a bigger factor can ensure we have enough pixels for
|
|
1281
|
+
# warps that are no more than another factor of sqrt(2) in
|
|
1282
|
+
# variation from the sampled perimeter.
|
|
1283
|
+
srcscale = self._smallestSpacingRatio(srccorners, dstcorners) / 2
|
|
1095
1284
|
# we only need every 1/srcscale pixel.
|
|
1096
1285
|
srcscale = int(2 ** math.log2(max(1, srcscale)))
|
|
1097
1286
|
# Pad to reduce edge effects at tile boundaries
|
|
@@ -1123,54 +1312,68 @@ class MultiFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
1123
1312
|
[region['right'] // srcscale, region['top'] // srcscale, 1],
|
|
1124
1313
|
[region['right'] // srcscale, region['bottom'] // srcscale, 1],
|
|
1125
1314
|
[region['left'] // srcscale, region['bottom'] // srcscale, 1]], dtype=float)
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
x
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1315
|
+
if warp is None:
|
|
1316
|
+
# adjust our transform if we took a low res version of the source
|
|
1317
|
+
transform[:2, :2] *= srcscale
|
|
1318
|
+
else:
|
|
1319
|
+
warp_src /= srcscale
|
|
1320
|
+
warp_src -= regioncorners[0, :2]
|
|
1321
|
+
if warp is None:
|
|
1322
|
+
# Find where the source corners land on the destination.
|
|
1323
|
+
preshiftcorners = (np.dot(transform, regioncorners.T).T).tolist()
|
|
1324
|
+
regioncorners[:, :2] -= regioncorners[0, :2]
|
|
1325
|
+
destcorners = (np.dot(transform, regioncorners.T).T).tolist()
|
|
1326
|
+
offsetx, offsety = None, None
|
|
1327
|
+
for idx in range(4):
|
|
1328
|
+
if offsetx is None or destcorners[idx][0] < offsetx:
|
|
1329
|
+
x = preshiftcorners[idx][0]
|
|
1330
|
+
offsetx = destcorners[idx][0] - (x - math.floor(x))
|
|
1331
|
+
if offsety is None or destcorners[idx][1] < offsety:
|
|
1332
|
+
y = preshiftcorners[idx][1]
|
|
1333
|
+
offsety = destcorners[idx][1] - (y - math.floor(y))
|
|
1334
|
+
transform[0][2] -= offsetx
|
|
1335
|
+
transform[1][2] -= offsety
|
|
1336
|
+
x, y = int(math.floor(x)), int(math.floor(y))
|
|
1337
|
+
# Recompute where the source corners will land
|
|
1338
|
+
destcorners = (np.dot(transform, regioncorners.T).T).tolist()
|
|
1339
|
+
destShape = [
|
|
1340
|
+
max(max(math.ceil(c[1]) for c in destcorners), srcImage.shape[0]),
|
|
1341
|
+
max(max(math.ceil(c[0]) for c in destcorners), srcImage.shape[1]),
|
|
1342
|
+
]
|
|
1343
|
+
if max(0, -x) or max(0, -y):
|
|
1344
|
+
transform[0][2] -= max(0, -x)
|
|
1345
|
+
transform[1][2] -= max(0, -y)
|
|
1346
|
+
destShape[0] -= max(0, -y)
|
|
1347
|
+
destShape[1] -= max(0, -x)
|
|
1348
|
+
x += max(0, -x)
|
|
1349
|
+
y += max(0, -y)
|
|
1350
|
+
destShape = [min(destShape[0], outh - y), min(destShape[1], outw - x)]
|
|
1351
|
+
if destShape[0] <= 0 or destShape[1] <= 0:
|
|
1352
|
+
return None, None, None
|
|
1353
|
+
else:
|
|
1354
|
+
x = y = 0
|
|
1355
|
+
destShape = [outh, outw]
|
|
1159
1356
|
# Add an alpha band if needed. This has to be done before the
|
|
1160
1357
|
# transform if it isn't the first tile, since the unused transformed
|
|
1161
1358
|
# areas need to have a zero alpha value
|
|
1162
1359
|
if srcImage.shape[2] in {1, 3}:
|
|
1163
1360
|
_, srcImage = _makeSameChannelDepth(np.zeros((1, 1, srcImage.shape[2] + 1)), srcImage)
|
|
1164
1361
|
useNearest = srcImage.shape[2] in {2, 4} and not firstMerge
|
|
1362
|
+
|
|
1363
|
+
if warp is None:
|
|
1364
|
+
transformer = skimage_transform.AffineTransform(np.linalg.inv(transform))
|
|
1365
|
+
else:
|
|
1366
|
+
transformer = skimage_transform.ThinPlateSplineTransform()
|
|
1367
|
+
transformer.estimate(warp_dst, warp_src)
|
|
1165
1368
|
# skimage.transform.warp is faster and has less artifacts than
|
|
1166
1369
|
# scipy.ndimage.affine_transform. It is faster than using cupy's
|
|
1167
1370
|
# version of scipy's affine_transform when the source and destination
|
|
1168
1371
|
# images are converted from numpy to cupy and back in this method.
|
|
1169
|
-
destImage =
|
|
1372
|
+
destImage = skimage_transform.warp(
|
|
1170
1373
|
# Although using np.float32 could reduce memory use, it doesn't
|
|
1171
1374
|
# provide any speed improvement
|
|
1172
1375
|
srcImage.astype(float),
|
|
1173
|
-
|
|
1376
|
+
transformer,
|
|
1174
1377
|
order=0 if useNearest else 3,
|
|
1175
1378
|
output_shape=(destShape[0], destShape[1], srcImage.shape[2]),
|
|
1176
1379
|
).astype(srcImage.dtype)
|
|
@@ -1200,13 +1403,14 @@ class MultiFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
1200
1403
|
return tile
|
|
1201
1404
|
ts = self._openSource(source, sourceEntry['kwargs'])
|
|
1202
1405
|
transform = bbox.get('transform')
|
|
1406
|
+
warp = bbox.get('warp')
|
|
1203
1407
|
x = y = 0
|
|
1204
1408
|
# If there is no transform or the diagonals are positive and there is
|
|
1205
1409
|
# no sheer and integer pixel alignment, use getRegion with an
|
|
1206
1410
|
# appropriate size
|
|
1207
1411
|
scaleX = transform[0][0] if transform is not None else 1
|
|
1208
1412
|
scaleY = transform[1][1] if transform is not None else 1
|
|
1209
|
-
if ((transform is None or (
|
|
1413
|
+
if (warp is None and (transform is None or (
|
|
1210
1414
|
transform[0][0] > 0 and transform[0][1] == 0 and
|
|
1211
1415
|
transform[1][0] == 0 and transform[1][1] > 0 and
|
|
1212
1416
|
transform[0][2] % scaleX == 0 and transform[1][2] % scaleY == 0)) and
|
|
@@ -1247,7 +1451,7 @@ class MultiFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
|
|
|
1247
1451
|
resample=None, format=TILE_FORMAT_NUMPY)
|
|
1248
1452
|
else:
|
|
1249
1453
|
sourceTile, x, y = self._getTransformedTile(
|
|
1250
|
-
ts, transform, corners, scale, sourceEntry.get('frame', 0),
|
|
1454
|
+
ts, transform, corners, scale, sourceEntry.get('frame', 0), warp,
|
|
1251
1455
|
source.get('position', {}).get('crop'),
|
|
1252
1456
|
firstMerge=tile is None)
|
|
1253
1457
|
if sourceTile is not None and all(dim > 0 for dim in sourceTile.shape):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: large-image-source-multi
|
|
3
|
-
Version: 1.32.12.
|
|
3
|
+
Version: 1.32.12.dev14
|
|
4
4
|
Summary: A tilesource for large_image to composite other tile sources
|
|
5
5
|
Home-page: https://github.com/girder/large_image
|
|
6
6
|
Author: Kitware, Inc.
|
|
@@ -17,12 +17,12 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
17
17
|
Description-Content-Type: text/x-rst
|
|
18
18
|
License-File: LICENSE
|
|
19
19
|
Requires-Dist: jsonschema
|
|
20
|
-
Requires-Dist: large-image>=1.32.12.
|
|
20
|
+
Requires-Dist: large-image>=1.32.12.dev14
|
|
21
21
|
Requires-Dist: pyyaml
|
|
22
22
|
Provides-Extra: all
|
|
23
23
|
Requires-Dist: scikit-image; extra == "all"
|
|
24
24
|
Provides-Extra: girder
|
|
25
|
-
Requires-Dist: girder-large-image>=1.32.12.
|
|
25
|
+
Requires-Dist: girder-large-image>=1.32.12.dev14; extra == "girder"
|
|
26
26
|
Dynamic: author
|
|
27
27
|
Dynamic: author-email
|
|
28
28
|
Dynamic: classifier
|
|
File without changes
|
{large_image_source_multi-1.32.12.dev11 → large_image_source_multi-1.32.12.dev14}/README.rst
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{large_image_source_multi-1.32.12.dev11 → large_image_source_multi-1.32.12.dev14}/pyproject.toml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|