nt25 0.1.6__tar.gz → 0.1.8__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.
- {nt25-0.1.6 → nt25-0.1.8}/.gitignore +4 -3
- {nt25-0.1.6 → nt25-0.1.8}/PKG-INFO +5 -3
- {nt25-0.1.6 → nt25-0.1.8}/README.md +2 -2
- {nt25-0.1.6 → nt25-0.1.8}/pyproject.toml +5 -1
- {nt25-0.1.6 → nt25-0.1.8}/src/nt25/lib/et.py +125 -112
- nt25-0.1.8/src/nt25/mt.py +105 -0
- nt25-0.1.8/src/nt25/ttp.py +201 -0
- nt25-0.1.8/tests/mt.csv +3 -0
- nt25-0.1.8/tests/ttp.tif +0 -0
- {nt25-0.1.6 → nt25-0.1.8}/src/nt25/__init__.py +0 -0
- {nt25-0.1.6 → nt25-0.1.8}/src/nt25/data/exif.jpg +0 -0
- {nt25-0.1.6 → nt25-0.1.8}/src/nt25/data/test.xlsx +0 -0
- {nt25-0.1.6 → nt25-0.1.8}/src/nt25/demo.py +0 -0
- {nt25-0.1.6 → nt25-0.1.8}/src/nt25/lib/calc.py +0 -0
- {nt25-0.1.6 → nt25-0.1.8}/src/nt25/lib/draw.py +0 -0
- {nt25-0.1.6 → nt25-0.1.8}/src/nt25/lib/fio.py +0 -0
- {nt25-0.1.6 → nt25-0.1.8}/tests/qr.py +0 -0
- {nt25-0.1.6 → nt25-0.1.8}/tests/test3d.py +0 -0
@@ -1,13 +1,15 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: nt25
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.8
|
4
4
|
Summary: Neo's Tools of Python
|
5
5
|
Requires-Python: >=3.10
|
6
6
|
Requires-Dist: exif>=1.6.1
|
7
7
|
Requires-Dist: matplotlib>=3.10.6
|
8
8
|
Requires-Dist: openpyxl>=3.1.5
|
9
9
|
Requires-Dist: pandas>=2.3.2
|
10
|
+
Requires-Dist: pillow>=11.3.0
|
10
11
|
Requires-Dist: pyinstaller>=6.15.0
|
12
|
+
Requires-Dist: requests>=2.32.5
|
11
13
|
Requires-Dist: scikit-learn>=1.7.1
|
12
14
|
Requires-Dist: sympy>=1.14.0
|
13
15
|
Description-Content-Type: text/markdown
|
@@ -22,10 +24,10 @@ Neo's Tools of Python in 2025
|
|
22
24
|
- [x] calc
|
23
25
|
- [x] draw
|
24
26
|
- [x] et: EXIF tools
|
27
|
+
- [x] mt: MapXYZ Transfer tool
|
28
|
+
- [x] ttp: TIFF to PNG, thread support
|
25
29
|
- [ ] mysql
|
26
30
|
- [ ] redis
|
27
|
-
- [ ] maptrans
|
28
|
-
- [ ] ttp
|
29
31
|
|
30
32
|
## scripts from init
|
31
33
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "nt25"
|
3
|
-
version = "0.1.
|
3
|
+
version = "0.1.8"
|
4
4
|
description = "Neo's Tools of Python"
|
5
5
|
readme = "README.md"
|
6
6
|
requires-python = ">=3.10"
|
@@ -12,11 +12,15 @@ dependencies = [
|
|
12
12
|
"openpyxl>=3.1.5",
|
13
13
|
"scikit-learn>=1.7.1",
|
14
14
|
"sympy>=1.14.0",
|
15
|
+
"pillow>=11.3.0",
|
16
|
+
"requests>=2.32.5",
|
15
17
|
]
|
16
18
|
|
17
19
|
[project.scripts]
|
18
20
|
demo = "nt25.demo:main"
|
19
21
|
et = "nt25.lib.et:main"
|
22
|
+
mt = "nt25.mt:main"
|
23
|
+
ttp = "nt25.ttp:main"
|
20
24
|
|
21
25
|
[build-system]
|
22
26
|
requires = ["hatchling"]
|
@@ -1,14 +1,16 @@
|
|
1
|
-
import
|
2
|
-
from operator import length_hint
|
1
|
+
import os
|
3
2
|
import time
|
4
3
|
import json
|
5
4
|
import struct
|
6
5
|
import argparse
|
7
6
|
|
7
|
+
from PIL import Image as pi
|
8
|
+
|
8
9
|
from datetime import UTC, datetime, timedelta, timezone
|
9
10
|
from exif import Image, DATETIME_STR_FORMAT
|
10
11
|
|
11
|
-
VERSION = "0.1.
|
12
|
+
VERSION = "0.1.4"
|
13
|
+
MAX_WIDTH = 1080
|
12
14
|
COMMENT_SEGMENT = b"\xff\xfe"
|
13
15
|
EPOCH = datetime.fromtimestamp(0, UTC)
|
14
16
|
|
@@ -32,6 +34,27 @@ def gpsDt2Dt(date, time, offset=8):
|
|
32
34
|
return utc.astimezone(timezone(timedelta(hours=offset)))
|
33
35
|
|
34
36
|
|
37
|
+
def optimizeFile(file, q=80, mw=MAX_WIDTH):
|
38
|
+
less = False
|
39
|
+
ofile = file + '.jpg'
|
40
|
+
|
41
|
+
img = pi.open(file)
|
42
|
+
w, h = img.size
|
43
|
+
scale = mw / max(w, h)
|
44
|
+
w = int(scale * w)
|
45
|
+
h = int(scale * h)
|
46
|
+
img.resize((w, h)).save(ofile, quality=q, optimize=True, progression=True)
|
47
|
+
|
48
|
+
if os.path.getsize(ofile) < os.path.getsize(file) * 0.8:
|
49
|
+
less = True
|
50
|
+
transplant(file, ofile)
|
51
|
+
os.replace(ofile, file)
|
52
|
+
else:
|
53
|
+
os.remove(ofile)
|
54
|
+
|
55
|
+
return less
|
56
|
+
|
57
|
+
|
35
58
|
def tryGet(img, key, default):
|
36
59
|
value = default
|
37
60
|
|
@@ -43,9 +66,12 @@ def tryGet(img, key, default):
|
|
43
66
|
return value
|
44
67
|
|
45
68
|
|
46
|
-
def dumpExif(file):
|
69
|
+
def dumpExif(file, optimize=False):
|
47
70
|
result = {}
|
48
71
|
|
72
|
+
if optimize:
|
73
|
+
optimizeFile(file)
|
74
|
+
|
49
75
|
with open(file, 'rb') as f:
|
50
76
|
img = Image(f)
|
51
77
|
for key in img.get_all():
|
@@ -57,58 +83,74 @@ def dumpExif(file):
|
|
57
83
|
return result
|
58
84
|
|
59
85
|
|
60
|
-
def parseExif(file):
|
86
|
+
def parseExif(file, optimize=False):
|
87
|
+
if optimize:
|
88
|
+
optimizeFile(file)
|
89
|
+
|
61
90
|
with open(file, 'rb') as f:
|
62
91
|
try:
|
63
92
|
img = Image(f)
|
64
93
|
except Exception:
|
65
94
|
return {}
|
66
95
|
|
67
|
-
|
68
|
-
|
96
|
+
width = tryGet(img, 'pixel_x_dimension', -1)
|
97
|
+
height = tryGet(img, 'pixel_y_dimension', -1)
|
98
|
+
|
99
|
+
if width < 0:
|
100
|
+
width = tryGet(img, 'image_width', -1)
|
101
|
+
height = tryGet(img, 'image_height', -1)
|
102
|
+
|
103
|
+
create = tryGet(img, 'datetime_original', None)
|
104
|
+
modify = tryGet(img, 'datetime', None)
|
69
105
|
|
70
|
-
|
71
|
-
|
72
|
-
height = tryGet(img, 'image_height', -1)
|
106
|
+
createDt = None if create is None else dtFormatter(create)
|
107
|
+
modifyDt = None if modify is None else dtFormatter(modify)
|
73
108
|
|
74
|
-
|
75
|
-
|
109
|
+
latitude = tryGet(img, 'gps_latitude', None)
|
110
|
+
latitude = None if latitude is None else dms2dec(latitude)
|
76
111
|
|
77
|
-
|
78
|
-
|
112
|
+
latRef = tryGet(img, "gps_latitude_ref", default='N')
|
113
|
+
if latRef != 'N' and latitude:
|
114
|
+
latitude = -latitude
|
79
115
|
|
80
|
-
|
81
|
-
|
116
|
+
longitude = tryGet(img, 'gps_longitude', None)
|
117
|
+
longitude = None if longitude is None else dms2dec(longitude)
|
82
118
|
|
83
|
-
|
84
|
-
|
119
|
+
longRef = tryGet(img, "gps_longitude_ref", default='E')
|
120
|
+
if longRef != 'E' and longitude:
|
121
|
+
longitude = -longitude
|
85
122
|
|
86
|
-
|
87
|
-
|
88
|
-
|
123
|
+
gpsDatetime = None
|
124
|
+
gd = tryGet(img, 'gps_datestamp', None)
|
125
|
+
gt = tryGet(img, 'gps_timestamp', None)
|
89
126
|
|
90
|
-
|
91
|
-
|
92
|
-
|
127
|
+
if gd and gt:
|
128
|
+
offset = int(time.localtime().tm_gmtoff / 3600)
|
129
|
+
gpsDatetime = gpsDt2Dt(gd, gt, offset=offset)
|
93
130
|
|
94
|
-
|
95
|
-
|
96
|
-
|
131
|
+
ts = -1 if createDt is None else int(createDt.timestamp())
|
132
|
+
mTs = -1 if modifyDt is None else int(modifyDt.timestamp())
|
133
|
+
gpsTs = -1 if gpsDatetime is None else int(gpsDatetime.timestamp())
|
134
|
+
|
135
|
+
if ts > 0:
|
97
136
|
offset = max(mTs, gpsTs) - ts
|
98
|
-
offsetDelta = datetime.fromtimestamp(offset, UTC) - EPOCH
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
137
|
+
offsetDelta = str(datetime.fromtimestamp(offset, UTC) - EPOCH)
|
138
|
+
else:
|
139
|
+
offset = None
|
140
|
+
offsetDelta = None
|
141
|
+
|
142
|
+
return {
|
143
|
+
"width": width,
|
144
|
+
"height": height,
|
145
|
+
"latitude": latitude,
|
146
|
+
"longitude": longitude,
|
147
|
+
"datetime.create": dt2str(createDt),
|
148
|
+
"datetime.modify": dt2str(modifyDt),
|
149
|
+
"datetime.gps": dt2str(gpsDatetime),
|
150
|
+
"ts": ts,
|
151
|
+
"offset": offset,
|
152
|
+
"offset.delta": offsetDelta,
|
153
|
+
}
|
112
154
|
|
113
155
|
|
114
156
|
class InvalidImageDataError(ValueError):
|
@@ -174,7 +216,7 @@ def getExif(segments):
|
|
174
216
|
if seg[0:2] == b"\xff\xe1" and seg[4:10] == b"Exif\x00\x00":
|
175
217
|
return seg
|
176
218
|
|
177
|
-
return
|
219
|
+
return b""
|
178
220
|
|
179
221
|
|
180
222
|
def mergeSegments(segments, exif=b""):
|
@@ -210,49 +252,29 @@ def mergeSegments(segments, exif=b""):
|
|
210
252
|
return b"".join(segments)
|
211
253
|
|
212
254
|
|
213
|
-
def removeExif(src,
|
214
|
-
|
215
|
-
|
216
|
-
src_data = src
|
217
|
-
file_type = "jpeg"
|
218
|
-
else:
|
219
|
-
with open(src, 'rb') as f:
|
220
|
-
src_data = f.read()
|
221
|
-
output_is_file = True
|
222
|
-
if src_data[0:2] == b"\xff\xd8":
|
223
|
-
file_type = "jpeg"
|
224
|
-
|
225
|
-
if file_type == "jpeg":
|
226
|
-
segments = genSegments(src_data)
|
227
|
-
segments = list(filter(lambda seg: not (seg[0:2] == b"\xff\xe1"
|
228
|
-
and seg[4:10] == b"Exif\x00\x00"),
|
229
|
-
segments))
|
230
|
-
|
231
|
-
segments = setComment(segments, "nt25.et")
|
232
|
-
new_data = b"".join(segments)
|
233
|
-
|
234
|
-
if isinstance(new_file, io.BytesIO):
|
235
|
-
new_file.write(new_data)
|
236
|
-
new_file.seek(0)
|
237
|
-
elif new_file:
|
238
|
-
with open(new_file, "wb+") as f:
|
239
|
-
f.write(new_data)
|
240
|
-
elif output_is_file:
|
241
|
-
with open(src, "wb+") as f:
|
242
|
-
f.write(new_data)
|
243
|
-
else:
|
244
|
-
raise ValueError("Give a second argument to 'remove' to output file")
|
255
|
+
def removeExif(src, optimize=False):
|
256
|
+
if optimize:
|
257
|
+
optimizeFile(src)
|
245
258
|
|
259
|
+
with open(src, 'rb') as f:
|
260
|
+
src_data = f.read()
|
246
261
|
|
247
|
-
|
248
|
-
""
|
249
|
-
|
262
|
+
segments = genSegments(src_data)
|
263
|
+
segments = list(filter(lambda seg: not (seg[0:2] == b"\xff\xe1"
|
264
|
+
and seg[4:10] == b"Exif\x00\x00"),
|
265
|
+
segments))
|
250
266
|
|
251
|
-
|
267
|
+
segments = setComment(segments, "nt25.et")
|
268
|
+
new_data = b"".join(segments)
|
269
|
+
|
270
|
+
with open(src, "wb+") as f:
|
271
|
+
f.write(new_data)
|
272
|
+
|
273
|
+
|
274
|
+
def transplant(exif_src, image, optimize=False):
|
275
|
+
if optimize:
|
276
|
+
optimizeFile(image)
|
252
277
|
|
253
|
-
:param str filename1: JPEG
|
254
|
-
:param str filename2: JPEG
|
255
|
-
"""
|
256
278
|
if exif_src[0:2] == b"\xff\xd8":
|
257
279
|
src_data = exif_src
|
258
280
|
else:
|
@@ -262,41 +284,29 @@ def transplant(exif_src, image, new_file=None):
|
|
262
284
|
segments = genSegments(src_data)
|
263
285
|
exif = getExif(segments)
|
264
286
|
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
output_file = False
|
269
|
-
if image[0:2] == b"\xff\xd8":
|
270
|
-
image_data = image
|
271
|
-
else:
|
272
|
-
with open(image, 'rb') as f:
|
273
|
-
image_data = f.read()
|
274
|
-
output_file = True
|
287
|
+
with open(image, 'rb') as f:
|
288
|
+
image_data = f.read()
|
275
289
|
|
276
290
|
segments = genSegments(image_data)
|
277
291
|
segments = setComment(segments, "nt25.et")
|
278
292
|
new_data = mergeSegments(segments, exif)
|
279
293
|
|
280
|
-
|
281
|
-
|
282
|
-
new_file.seek(0)
|
283
|
-
elif new_file:
|
284
|
-
with open(new_file, "wb+") as f:
|
285
|
-
f.write(new_data)
|
286
|
-
elif output_file:
|
287
|
-
with open(image, "wb+") as f:
|
288
|
-
f.write(new_data)
|
289
|
-
else:
|
290
|
-
raise ValueError("Give a 3rd argument to 'transplant' to output file")
|
294
|
+
with open(image, "wb+") as f:
|
295
|
+
f.write(new_data)
|
291
296
|
|
292
297
|
|
293
298
|
def main():
|
294
299
|
parser = argparse.ArgumentParser(description="EXIF tool")
|
295
|
-
parser.add_argument('-v', '--version',
|
296
|
-
help='echo version'
|
297
|
-
parser.add_argument('-
|
298
|
-
|
299
|
-
parser.add_argument('-
|
300
|
+
parser.add_argument('-v', '--version', action='store_true',
|
301
|
+
help='echo version')
|
302
|
+
parser.add_argument('-o', '--optimize', action='store_true',
|
303
|
+
help='optimize jpg file, work with -r, -d, -c, -f')
|
304
|
+
parser.add_argument('-r', '--rm', action='store_true',
|
305
|
+
help='remove meta, use: -r -f FILE')
|
306
|
+
parser.add_argument('-d', '--dump', action='store_true',
|
307
|
+
help='dump meta, use: -d -f FILE')
|
308
|
+
parser.add_argument('-c', '--copy', type=str,
|
309
|
+
help='copy meta, use: -c SRC -f DST')
|
300
310
|
parser.add_argument('-f', '--file', type=str, help='image file')
|
301
311
|
|
302
312
|
args = parser.parse_args()
|
@@ -306,17 +316,20 @@ def main():
|
|
306
316
|
return
|
307
317
|
|
308
318
|
if args.file is None:
|
309
|
-
print("usage: et [-h] [-v] [-
|
319
|
+
print("usage: et [-h] [-v] [-o] [-f FILE] [-d -f FILE]\n"
|
320
|
+
"\t\t[-r -f FILE] [-c SRC -f DST]")
|
310
321
|
return
|
311
322
|
|
323
|
+
opt = True if args.optimize else False
|
324
|
+
|
312
325
|
if args.dump:
|
313
|
-
r = dumpExif(args.file)
|
326
|
+
r = dumpExif(args.file, optimize=opt)
|
314
327
|
elif args.rm:
|
315
|
-
r = removeExif(args.file)
|
328
|
+
r = removeExif(args.file, optimize=opt)
|
316
329
|
elif args.copy:
|
317
|
-
r = transplant(args.copy, args.file)
|
330
|
+
r = transplant(args.copy, args.file, optimize=opt)
|
318
331
|
else:
|
319
|
-
r = parseExif(args.file)
|
332
|
+
r = parseExif(args.file, optimize=opt)
|
320
333
|
|
321
334
|
if r is not None:
|
322
335
|
print(json.dumps(r, indent=2, sort_keys=False))
|
@@ -0,0 +1,105 @@
|
|
1
|
+
from nt25 import fio
|
2
|
+
|
3
|
+
import json
|
4
|
+
import requests
|
5
|
+
import argparse
|
6
|
+
|
7
|
+
|
8
|
+
kEncoding = 'gbk'
|
9
|
+
kWGSName = 'WGS-84'
|
10
|
+
|
11
|
+
kMaxTranCount = 50
|
12
|
+
|
13
|
+
kWGSCode = 4326
|
14
|
+
k50NCode = 32650
|
15
|
+
kCGS2000Z20Code = 4498
|
16
|
+
kCGS2000Z21Code = 4499
|
17
|
+
|
18
|
+
kURLFormatter = 'https://api.maptiler.com/coordinates/transform/' + \
|
19
|
+
'{cs}.json?key={key}&s_srs={s}&t_srs={t}'
|
20
|
+
|
21
|
+
|
22
|
+
def genCs4Tran(coordinates):
|
23
|
+
cs = []
|
24
|
+
|
25
|
+
for i in range(min(len(coordinates), kMaxTranCount)):
|
26
|
+
c = coordinates[i]
|
27
|
+
cs.append('{0},{1}'.format(c[0], c[1]))
|
28
|
+
|
29
|
+
return ';'.join(cs)
|
30
|
+
|
31
|
+
|
32
|
+
def fetchResult(url):
|
33
|
+
result = {}
|
34
|
+
res = requests.get(url)
|
35
|
+
|
36
|
+
try:
|
37
|
+
result = json.loads(res.text)
|
38
|
+
except Exception as e:
|
39
|
+
print(e, '\n\t', res.text)
|
40
|
+
|
41
|
+
return result
|
42
|
+
|
43
|
+
|
44
|
+
def transform(key, coordinates, startCode, toCode):
|
45
|
+
results = []
|
46
|
+
|
47
|
+
cc = [coordinates[i:i + kMaxTranCount]
|
48
|
+
for i in range(0, len(coordinates), kMaxTranCount)]
|
49
|
+
|
50
|
+
for c in cc:
|
51
|
+
url = kURLFormatter.format(cs=genCs4Tran(c),
|
52
|
+
key=key, s=startCode, t=toCode)
|
53
|
+
result = fetchResult(url)
|
54
|
+
|
55
|
+
if result and result['results'] and result['results'][0]['x'] is not None:
|
56
|
+
results.extend(result['results'])
|
57
|
+
|
58
|
+
for i in range(len(results)):
|
59
|
+
results[i]['z'] = coordinates[i][2] if len(coordinates[i]) > 2 else 0
|
60
|
+
|
61
|
+
return results
|
62
|
+
|
63
|
+
|
64
|
+
def main():
|
65
|
+
parse = argparse.ArgumentParser(description='Map Transform tool, '
|
66
|
+
'from https://docs.maptiler.com/cloud'
|
67
|
+
'/api/coordinates/#transform-coordinates')
|
68
|
+
|
69
|
+
# default=kKey, required=False)
|
70
|
+
parse.add_argument('-k', '--key', type=str,
|
71
|
+
help='MapTiler key', required=True)
|
72
|
+
parse.add_argument('-i', '--input', type=str, required=True,
|
73
|
+
help='input file')
|
74
|
+
parse.add_argument('-o', '--output', type=str, help='output file',
|
75
|
+
default='out.csv')
|
76
|
+
parse.add_argument('-s', '--start', type=str, default='4498',
|
77
|
+
help='transform start code')
|
78
|
+
parse.add_argument('-t', '--to', type=str, default='4236',
|
79
|
+
help='transform code to')
|
80
|
+
|
81
|
+
args = parse.parse_args()
|
82
|
+
|
83
|
+
key = args.key
|
84
|
+
input = args.input
|
85
|
+
output = args.output
|
86
|
+
start = args.start
|
87
|
+
to = args.to
|
88
|
+
|
89
|
+
csv = fio.getCSV(input, width=3, startLine=1)
|
90
|
+
|
91
|
+
if csv is not None:
|
92
|
+
coordinates = []
|
93
|
+
for i in range(len(csv[0])):
|
94
|
+
coordinates.append((csv[0][i], csv[1][i], csv[2][i]))
|
95
|
+
|
96
|
+
result = transform(key, coordinates, start, to)
|
97
|
+
|
98
|
+
if len(result) > 0:
|
99
|
+
content = list(map(lambda r: (r['x'], r['y'], r['z']), result))
|
100
|
+
fio.saveCSV(content, output, colsInline=False)
|
101
|
+
print(f'Saved {len(result)} rows in {output}')
|
102
|
+
|
103
|
+
|
104
|
+
if __name__ == '__main__':
|
105
|
+
main()
|
@@ -0,0 +1,201 @@
|
|
1
|
+
import re
|
2
|
+
import os
|
3
|
+
import glob
|
4
|
+
import argparse
|
5
|
+
import time
|
6
|
+
|
7
|
+
from threading import Thread, Lock
|
8
|
+
from multiprocessing import cpu_count
|
9
|
+
|
10
|
+
from PIL import Image
|
11
|
+
import numpy as np
|
12
|
+
import matplotlib.colors as mcolors
|
13
|
+
|
14
|
+
gTLock = Lock()
|
15
|
+
gForceQuit = False
|
16
|
+
|
17
|
+
gMaxValue = 0
|
18
|
+
gShadowValue = 0
|
19
|
+
|
20
|
+
gCrop = None
|
21
|
+
gArea = None
|
22
|
+
gLast = None
|
23
|
+
gColors = None
|
24
|
+
|
25
|
+
kTifLevel = 255
|
26
|
+
|
27
|
+
|
28
|
+
def nsort(s):
|
29
|
+
sub = re.split(r'(\d+)', s)
|
30
|
+
sub = [int(c) if c.isdigit() else c for c in sub]
|
31
|
+
return sub
|
32
|
+
|
33
|
+
|
34
|
+
def genGradColors(gradStart, gradStop):
|
35
|
+
# ks = kTifLevel * kTifLevel
|
36
|
+
# yc = np.linspace(mcolors.to_rgba(gradStart),
|
37
|
+
# mcolors.to_rgba(gradStop), ks) * ks
|
38
|
+
# colors = [yc[ks - (i - kTifLevel) * (i - kTifLevel)]
|
39
|
+
# for i in range(0, kTifLevel)]
|
40
|
+
|
41
|
+
colors = np.linspace(mcolors.to_rgba(gradStart),
|
42
|
+
mcolors.to_rgba(gradStop), kTifLevel) * kTifLevel
|
43
|
+
return np.vstack([[0, 0, 0, 0], colors]).astype(np.uint8)
|
44
|
+
|
45
|
+
|
46
|
+
def splitArray(array, times):
|
47
|
+
chunk = len(array) // times
|
48
|
+
left = len(array) % times
|
49
|
+
|
50
|
+
result = []
|
51
|
+
for i in range(times):
|
52
|
+
start = i * chunk + min(i, left)
|
53
|
+
end = start + chunk + (1 if i < left else 0)
|
54
|
+
result.append(array[start:end])
|
55
|
+
|
56
|
+
return result
|
57
|
+
|
58
|
+
|
59
|
+
def dump(array):
|
60
|
+
shp = array.shape
|
61
|
+
for i in range(shp[0]):
|
62
|
+
print(array[i])
|
63
|
+
|
64
|
+
|
65
|
+
def ttp(array, path, maxValue):
|
66
|
+
global gTLock, gForceQuit, gCrop, gArea, gColors, gMaxValue, gLast
|
67
|
+
|
68
|
+
if gCrop is None or gArea is None or gColors is None:
|
69
|
+
return
|
70
|
+
|
71
|
+
for tif in array:
|
72
|
+
name = os.path.splitext(os.path.basename(tif))[0]
|
73
|
+
out = os.path.join(path, name + '.png')
|
74
|
+
|
75
|
+
image = Image.open(tif)
|
76
|
+
ta = np.array(image)
|
77
|
+
ta = ta[gCrop[0]:gCrop[1], gCrop[2]:gCrop[3]]
|
78
|
+
|
79
|
+
ta[ta < 0] = 0
|
80
|
+
|
81
|
+
with gTLock:
|
82
|
+
if ta.max() > gMaxValue:
|
83
|
+
gMaxValue = ta.max()
|
84
|
+
|
85
|
+
if gShadowValue > 0:
|
86
|
+
leap = maxValue / 40
|
87
|
+
ta = np.where(np.logical_and(gArea > 0, ta < gShadowValue),
|
88
|
+
gShadowValue, ta)
|
89
|
+
ta = np.where(gLast > ta + leap, gLast - leap, ta)
|
90
|
+
gLast = np.array(ta)
|
91
|
+
gArea = np.where(ta > gShadowValue, 1, gArea)
|
92
|
+
|
93
|
+
ta[ta > maxValue] = maxValue
|
94
|
+
ta = ta / maxValue * kTifLevel
|
95
|
+
Image.fromarray(gColors[ta.astype(np.uint8)], mode='RGBA').save(out)
|
96
|
+
|
97
|
+
if gForceQuit:
|
98
|
+
break
|
99
|
+
else:
|
100
|
+
print('.', end='', flush=True)
|
101
|
+
|
102
|
+
|
103
|
+
def main():
|
104
|
+
global gForceQuit, gCrop, gArea, gColors, gMaxValue, gLast
|
105
|
+
|
106
|
+
threadCounts = int(cpu_count() / 2)
|
107
|
+
|
108
|
+
parser = argparse.ArgumentParser(description="TIFF -> PNG")
|
109
|
+
parser.add_argument('-d', '--dir', help='Directory to handle',
|
110
|
+
default='.', type=str, required=False,)
|
111
|
+
parser.add_argument('-s', '--gradStart', help='Gradient color to Start',
|
112
|
+
default='#00FFFF', type=str, required=False,)
|
113
|
+
parser.add_argument('-t', '--gradStop', help='Gradient color to Stop',
|
114
|
+
default='#00008B', type=str, required=False,)
|
115
|
+
parser.add_argument('--TB', help='Crop TIFF from Top to Bottom', nargs='+',
|
116
|
+
# default=[1700, 3000], type=int, required=False,)
|
117
|
+
# default=[1350, 4250], type=int, required=False,)
|
118
|
+
default=[1400, 3550], type=int, required=False,)
|
119
|
+
# default=[2890, 2895], type=int, required=False,)
|
120
|
+
parser.add_argument('--LR', help='Crop TIFF from Left to Right', nargs='+',
|
121
|
+
default=[750, 2300], type=int, required=False,)
|
122
|
+
# default=[1650, 1655], type=int, required=False,)
|
123
|
+
parser.add_argument('-m', '--maxValue', help='Map Max Value',
|
124
|
+
default=16, type=int, required=False,)
|
125
|
+
parser.add_argument('-v', '--shadowValue', help='keep Value in Shadow',
|
126
|
+
default=1, type=int, required=False,)
|
127
|
+
parser.add_argument('-c', '--threadCounts', help='Minimum number of Threads',
|
128
|
+
default=threadCounts, type=int, required=False,)
|
129
|
+
|
130
|
+
args = parser.parse_args()
|
131
|
+
folder = args.dir
|
132
|
+
gradStart = args.gradStart
|
133
|
+
gradStop = args.gradStop
|
134
|
+
|
135
|
+
gColors = genGradColors(gradStart, gradStop)
|
136
|
+
|
137
|
+
TB = args.TB
|
138
|
+
LR = args.LR
|
139
|
+
gCrop = [TB[0], TB[1], LR[0], LR[1]]
|
140
|
+
gArea = np.zeros([TB[1] - TB[0], LR[1] - LR[0]], dtype=np.uint8)
|
141
|
+
gLast = np.zeros([TB[1] - TB[0], LR[1] - LR[0]])
|
142
|
+
|
143
|
+
maxValue = args.maxValue
|
144
|
+
threadCounts = args.threadCounts
|
145
|
+
|
146
|
+
gShadowValue = args.shadowValue
|
147
|
+
|
148
|
+
if gShadowValue > 0:
|
149
|
+
print(f'Shadow Value: {gShadowValue}')
|
150
|
+
threadCounts = 1
|
151
|
+
|
152
|
+
if threadCounts > 1:
|
153
|
+
print(f'Multi-threads to work: {threadCounts}T')
|
154
|
+
|
155
|
+
files = glob.glob(os.path.join(folder, '*.tif'))
|
156
|
+
files = sorted(files, key=nsort)
|
157
|
+
|
158
|
+
if len(files) == 0:
|
159
|
+
print("no TIF found")
|
160
|
+
return
|
161
|
+
|
162
|
+
path = os.path.join(folder, 'out')
|
163
|
+
if not os.path.exists(path):
|
164
|
+
os.makedirs(path)
|
165
|
+
|
166
|
+
tfs = splitArray(files, threadCounts)
|
167
|
+
|
168
|
+
td = []
|
169
|
+
for tif in tfs:
|
170
|
+
if tif is not None and len(tif) > 0:
|
171
|
+
t = Thread(target=ttp, args=(tif, path, maxValue,))
|
172
|
+
t.daemon = True
|
173
|
+
t.start()
|
174
|
+
td.append(t)
|
175
|
+
|
176
|
+
while True:
|
177
|
+
working = False
|
178
|
+
for t in td:
|
179
|
+
if t.is_alive():
|
180
|
+
working = True
|
181
|
+
break
|
182
|
+
|
183
|
+
if working:
|
184
|
+
try:
|
185
|
+
time.sleep(0.5)
|
186
|
+
except KeyboardInterrupt:
|
187
|
+
if not gForceQuit:
|
188
|
+
print("\n> WARN: Trying to terminate threads...")
|
189
|
+
|
190
|
+
gForceQuit = True
|
191
|
+
else:
|
192
|
+
break
|
193
|
+
|
194
|
+
if gForceQuit:
|
195
|
+
print("> WARN: Force quit!")
|
196
|
+
else:
|
197
|
+
print(f"\nTotal Max value: {maxValue}/{gMaxValue:.2f}")
|
198
|
+
|
199
|
+
|
200
|
+
if __name__ == '__main__':
|
201
|
+
main()
|
nt25-0.1.8/tests/mt.csv
ADDED
nt25-0.1.8/tests/ttp.tif
ADDED
Binary file
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|