Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
parkpow
GitHub Repository: parkpow/deep-license-plate-recognition
Path: blob/master/transfer.py
640 views
1
import argparse
2
import json
3
import os
4
import queue
5
import threading
6
import time
7
import uuid
8
from argparse import RawTextHelpFormatter
9
from datetime import datetime
10
from pathlib import Path
11
12
try:
13
from watchdog.events import PatternMatchingEventHandler
14
from watchdog.observers import Observer
15
except ImportError:
16
print(
17
"A dependency is missing. Please install: "
18
"https://pythonhosted.org/watchdog/installation.html"
19
)
20
exit(1)
21
22
try:
23
import requests
24
except ImportError:
25
print(
26
"A dependency is missing. Please install: "
27
"https://2.python-requests.org/en/master/user/install/#install"
28
)
29
exit(1)
30
31
try:
32
import jsonlines
33
except ImportError:
34
print(
35
"A dependency is missing. Please install: "
36
"https://jsonlines.readthedocs.io/en/latest/#installation"
37
)
38
exit(1)
39
40
_queue = queue.Queue(256) # type: ignore
41
42
##########################
43
# Command line arguments #
44
##########################
45
46
47
def parse_arguments():
48
parser = argparse.ArgumentParser(
49
description="""
50
Important! Before starting the image transfer:
51
1) Make sure to start the SDK.
52
2) Get your ParkPow API token from: https://app.parkpow.com/accounts/token (optional if using parkpow)
53
3) Get your PlateRecognizer API token from: https://app.platerecognizer.com/start/ (optional if using Cloud instead of local SDK)
54
55
56
Here is an example of how to call this script if using Local SDK:
57
python transfer.py --source /home/alpr/camera-images/ --archive /home/alpr/archived-images/ --alpr-api http://localhost:8080/v1/plate-reader/ --parkpow-token MY_TOKEN --cam-pos 2
58
59
Example of how to call this script is using the Cloud Api
60
python transfer.py --source /home/alpr/camera-images/ --archive /home/alpr/archived-images/ --alpr-api https://api.platerecognizer.com/v1/plate-reader/ --platerec-token MY_PLATEREC_TOKEN --parkpow-token MY_TOKEN --cam-pos 2
61
62
63
The path of each image must contain a directory with the camera name.
64
It is specified with the --cam-pos argument.
65
Once processed, images are moved to the archive directory.
66
If it the --api-url is not defined, the results will be saved in the output file --output-file
67
""",
68
formatter_class=RawTextHelpFormatter,
69
)
70
71
parser.add_argument(
72
"--source", help="Where camera images are saved.", type=str, required=True
73
)
74
parser.add_argument(
75
"--archive",
76
help="Where images are moved to archive after being processed.",
77
type=str,
78
required=True,
79
)
80
parser.add_argument(
81
"--parkpow-token", help="API token for ParkPow.", type=str, required=False
82
)
83
84
parser.add_argument(
85
"--platerec-token",
86
help="API token for PlateRecognizer.",
87
type=str,
88
required=False,
89
)
90
parser.add_argument(
91
"--cam-pos",
92
help="Position of the directory with camera name (.../4/3/2/1/image.jpg).\n"
93
"For example, with /home/export/parking/camera/july/image.jpg, set --cam-pos=2",
94
type=int,
95
required=True,
96
)
97
98
parser.add_argument(
99
"--workers", help="Number of worker threads.", type=int, default=2
100
)
101
parser.add_argument(
102
"--alpr-api",
103
help="URL of Cloud/SDK API.",
104
default="https://api.platerecognizer.com/v1/plate-reader",
105
)
106
107
parser.add_argument(
108
"--use-parkpow", help="Upload results to ParkPow", action="store_true"
109
)
110
111
parser.add_argument(
112
"--output-file", help="Json file with response", type=str, required=False
113
)
114
115
return parser.parse_args()
116
117
118
##################
119
# Process images #
120
##################
121
122
123
def image_transfer(src_path, args):
124
split = Path(src_path).parts
125
# make this better
126
if args.cam_pos >= len(split):
127
print("Image path does not match template. Call with -h to see help.")
128
return
129
130
filename = split[-1]
131
camera = split[-args.cam_pos - 1]
132
results = alpr(src_path, args)
133
if not results:
134
return
135
136
if not args.output_file:
137
138
payload = {"results": json.dumps(results), "camera": camera}
139
files = {"image": (filename, open(src_path, "rb"), "application/octet-stream")}
140
response = api_request(args, payload, files)
141
if not response:
142
return
143
else:
144
145
with jsonlines.open(args.output_file, mode="a") as json_file:
146
json_file.write(results)
147
response = results
148
149
# Move to archive
150
archive_dir = "{0}/{1}/{2:%Y}/{2:%m}/{2:%d}".format(
151
args.archive, camera, datetime.now()
152
)
153
154
destination = f"{archive_dir}/{uuid.uuid4()}={filename}"
155
try:
156
Path(archive_dir).mkdir(parents=True, exist_ok=True)
157
os.rename(src_path, destination)
158
except (PermissionError, OSError):
159
print("%s could not be moved to archive folder." % src_path)
160
return dict(dest=destination, response=response)
161
162
163
def alpr(path, args):
164
print("Sending %s" % path)
165
try:
166
if "localhost" in args.alpr_api:
167
time.sleep(1) # Wait for the whole image to arrive
168
with open(path, "rb") as fp:
169
response = requests.post(
170
args.alpr_api, files=dict(upload=fp), timeout=10
171
)
172
else:
173
time.sleep(1) # Wait for the whole image to arrive
174
filename = os.path.basename(path)
175
response = requests.post(
176
args.alpr_api,
177
files=dict(
178
upload=(filename, open(path, "rb"), "application/octet-stream")
179
),
180
headers={"Authorization": "Token " + args.platerec_token},
181
)
182
183
except requests.exceptions.Timeout:
184
print("SDK: Timeout")
185
return
186
except ConnectionError:
187
print("SDK: ConnectionError")
188
return
189
except PermissionError:
190
print("SDK: %s could not be read." % path)
191
return
192
except Exception as e:
193
print(e)
194
return
195
data = response.json()
196
# TODO: skip data if there is no change
197
if "results" not in data:
198
print(data)
199
return []
200
return data["results"]
201
202
203
def api_request(args, payload, files):
204
api_url = "https://app.parkpow.com/api/v1/log-vehicle"
205
headers = {"Authorization": f"Token {args.parkpow_token}"}
206
try:
207
response = requests.post(
208
api_url, data=payload, headers=headers, files=files, timeout=20
209
)
210
except ConnectionError:
211
print("ParkPow API: ConnectionError")
212
return
213
except requests.exceptions.Timeout:
214
print("ParkPow API: Timeout")
215
return
216
return response
217
218
219
###################
220
# File monitoring #
221
###################
222
223
224
def worker(args):
225
while True:
226
image_transfer(_queue.get(), args)
227
_queue.task_done()
228
229
230
class Handler(PatternMatchingEventHandler):
231
def on_created(self, event):
232
try:
233
_queue.put(event.src_path)
234
except queue.Full:
235
print("Queue is full. Skipping %s." % event.scr_path)
236
237
238
def main(args, debug=False):
239
if args.source in args.archive:
240
print("Archive argument should not be in source directory.")
241
return exit(1)
242
observer = Observer()
243
observer.schedule(
244
Handler(ignore_directories=True, patterns="*.jpg *.jpeg".split()),
245
args.source,
246
recursive=True,
247
)
248
observer.start()
249
for _ in range(args.workers):
250
t = threading.Thread(target=worker, args=(args,))
251
t.daemon = True
252
t.start()
253
254
print("Monitoring source directory.")
255
try:
256
while True:
257
time.sleep(1 if debug else 0.25)
258
if debug:
259
break
260
except KeyboardInterrupt:
261
pass
262
print("Closing...")
263
observer.stop()
264
observer.join()
265
_queue.join()
266
267
268
def validate_env(args):
269
messages = []
270
Path(args.archive).mkdir(parents=True, exist_ok=True)
271
if not Path(args.archive).exists():
272
messages.append("%s does not exist." % args.archive)
273
if not Path(args.source).exists():
274
messages.append("%s does not exist." % args.source)
275
276
if not args.use_parkpow and not args.output_file:
277
messages.append("Pass argument --use-parkpow or the argument --output-file")
278
if "http" not in args.alpr_api:
279
messages.append("--alpr-api is not a valid URL")
280
if "api.platerecognizer.com" in args.alpr_api and not args.platerec_token:
281
messages.append("Missing argument --platerec-token or SDK argument --alpr-api")
282
283
elif "api.platerecognizer.com" not in args.alpr_api:
284
try:
285
response = requests.get(args.alpr_api.rsplit("/v1", 1)[0], timeout=2)
286
except Exception:
287
response = None
288
if not response or response.status_code != 200:
289
messages.append(
290
"Make sure that the SDK is up and running (%s)." % args.alpr_api
291
)
292
if args.use_parkpow:
293
api_url = "https://app.parkpow.com/api/v1/log-vehicle"
294
try:
295
response = requests.get(
296
api_url.rsplit("/", 1)[0] + "/parking-list",
297
headers={"Authorization": f"Token {args.parkpow_token}"},
298
timeout=2,
299
)
300
except Exception:
301
response = None
302
if not response or response.status_code != 200:
303
messages.append(
304
response.json() if response else "Parkpow server could not be reached."
305
)
306
if len(messages) > 0:
307
print("Script initialization failed:")
308
print("\n".join(messages))
309
print("Exiting...")
310
exit(1)
311
312
313
if __name__ == "__main__":
314
args = parse_arguments()
315
validate_env(args)
316
main(args)
317
318