nt25 0.1.7__tar.gz → 0.1.9__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.7 → nt25-0.1.9}/.gitignore +5 -3
- {nt25-0.1.7 → nt25-0.1.9}/PKG-INFO +4 -3
- {nt25-0.1.7 → nt25-0.1.9}/README.md +2 -2
- {nt25-0.1.7 → nt25-0.1.9}/pyproject.toml +4 -1
- {nt25-0.1.7 → nt25-0.1.9}/src/nt25/lib/et.py +16 -5
- nt25-0.1.9/src/nt25/mt.py +105 -0
- nt25-0.1.9/src/nt25/ttp.py +201 -0
- nt25-0.1.9/tests/mt.csv +3 -0
- nt25-0.1.9/tests/ttp.tif +0 -0
- {nt25-0.1.7 → nt25-0.1.9}/src/nt25/__init__.py +0 -0
- {nt25-0.1.7 → nt25-0.1.9}/src/nt25/data/exif.jpg +0 -0
- {nt25-0.1.7 → nt25-0.1.9}/src/nt25/data/test.xlsx +0 -0
- {nt25-0.1.7 → nt25-0.1.9}/src/nt25/demo.py +0 -0
- {nt25-0.1.7 → nt25-0.1.9}/src/nt25/lib/calc.py +0 -0
- {nt25-0.1.7 → nt25-0.1.9}/src/nt25/lib/draw.py +0 -0
- {nt25-0.1.7 → nt25-0.1.9}/src/nt25/lib/fio.py +0 -0
- {nt25-0.1.7 → nt25-0.1.9}/tests/qr.py +0 -0
- {nt25-0.1.7 → nt25-0.1.9}/tests/test3d.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: nt25
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.9
|
4
4
|
Summary: Neo's Tools of Python
|
5
5
|
Requires-Python: >=3.10
|
6
6
|
Requires-Dist: exif>=1.6.1
|
@@ -9,6 +9,7 @@ Requires-Dist: openpyxl>=3.1.5
|
|
9
9
|
Requires-Dist: pandas>=2.3.2
|
10
10
|
Requires-Dist: pillow>=11.3.0
|
11
11
|
Requires-Dist: pyinstaller>=6.15.0
|
12
|
+
Requires-Dist: requests>=2.32.5
|
12
13
|
Requires-Dist: scikit-learn>=1.7.1
|
13
14
|
Requires-Dist: sympy>=1.14.0
|
14
15
|
Description-Content-Type: text/markdown
|
@@ -23,10 +24,10 @@ Neo's Tools of Python in 2025
|
|
23
24
|
- [x] calc
|
24
25
|
- [x] draw
|
25
26
|
- [x] et: EXIF tools
|
27
|
+
- [x] mt: MapXYZ Transfer tool
|
28
|
+
- [x] ttp: TIFF to PNG, thread support
|
26
29
|
- [ ] mysql
|
27
30
|
- [ ] redis
|
28
|
-
- [ ] maptrans
|
29
|
-
- [ ] ttp
|
30
31
|
|
31
32
|
## scripts from init
|
32
33
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "nt25"
|
3
|
-
version = "0.1.
|
3
|
+
version = "0.1.9"
|
4
4
|
description = "Neo's Tools of Python"
|
5
5
|
readme = "README.md"
|
6
6
|
requires-python = ">=3.10"
|
@@ -13,11 +13,14 @@ dependencies = [
|
|
13
13
|
"scikit-learn>=1.7.1",
|
14
14
|
"sympy>=1.14.0",
|
15
15
|
"pillow>=11.3.0",
|
16
|
+
"requests>=2.32.5",
|
16
17
|
]
|
17
18
|
|
18
19
|
[project.scripts]
|
19
20
|
demo = "nt25.demo:main"
|
20
21
|
et = "nt25.lib.et:main"
|
22
|
+
mt = "nt25.mt:main"
|
23
|
+
ttp = "nt25.ttp:main"
|
21
24
|
|
22
25
|
[build-system]
|
23
26
|
requires = ["hatchling"]
|
@@ -1,4 +1,3 @@
|
|
1
|
-
import io
|
2
1
|
import os
|
3
2
|
import time
|
4
3
|
import json
|
@@ -10,7 +9,8 @@ from PIL import Image as pi
|
|
10
9
|
from datetime import UTC, datetime, timedelta, timezone
|
11
10
|
from exif import Image, DATETIME_STR_FORMAT
|
12
11
|
|
13
|
-
VERSION = "0.1.
|
12
|
+
VERSION = "0.1.4"
|
13
|
+
MAX_WIDTH = 1080
|
14
14
|
COMMENT_SEGMENT = b"\xff\xfe"
|
15
15
|
EPOCH = datetime.fromtimestamp(0, UTC)
|
16
16
|
|
@@ -34,12 +34,23 @@ def gpsDt2Dt(date, time, offset=8):
|
|
34
34
|
return utc.astimezone(timezone(timedelta(hours=offset)))
|
35
35
|
|
36
36
|
|
37
|
-
def optimizeFile(file, q=80):
|
37
|
+
def optimizeFile(file, q=80, mw=MAX_WIDTH):
|
38
38
|
less = False
|
39
39
|
ofile = file + '.jpg'
|
40
|
-
pi.open(file).save(ofile, quality=q, optimize=True, progression=True)
|
41
40
|
|
42
|
-
|
41
|
+
img = pi.open(file)
|
42
|
+
w, h = img.size
|
43
|
+
m = max(w, h)
|
44
|
+
|
45
|
+
if m > mw:
|
46
|
+
scale = mw / m
|
47
|
+
w = int(scale * w)
|
48
|
+
h = int(scale * h)
|
49
|
+
img = img.resize((w, h))
|
50
|
+
|
51
|
+
img.save(ofile, quality=q, optimize=True, progression=True)
|
52
|
+
|
53
|
+
if os.path.getsize(ofile) < os.path.getsize(file) * 0.8:
|
43
54
|
less = True
|
44
55
|
transplant(file, ofile)
|
45
56
|
os.replace(ofile, file)
|
@@ -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.9/tests/mt.csv
ADDED
nt25-0.1.9/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
|