Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
parkpow
GitHub Repository: parkpow/deep-license-plate-recognition
Path: blob/master/transfer.py
1072 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
payload = {"results": json.dumps(results), "camera": camera}
138
files = {"image": (filename, open(src_path, "rb"), "application/octet-stream")}
139
response = api_request(args, payload, files)
140
if not response:
141
return
142
else:
143
with jsonlines.open(args.output_file, mode="a") as json_file:
144
json_file.write(results)
145
response = results
146
147
# Move to archive
148
archive_dir = "{0}/{1}/{2:%Y}/{2:%m}/{2:%d}".format(
149
args.archive, camera, datetime.now()
150
)
151
152
destination = f"{archive_dir}/{uuid.uuid4()}={filename}"
153
try:
154
Path(archive_dir).mkdir(parents=True, exist_ok=True)
155
os.rename(src_path, destination)
156
except (PermissionError, OSError):
157
print(f"{src_path} could not be moved to archive folder.")
158
return dict(dest=destination, response=response)
159
160
161
def alpr(path, args):
162
print(f"Sending {path}")
163
try:
164
if "localhost" in args.alpr_api:
165
time.sleep(1) # Wait for the whole image to arrive
166
with open(path, "rb") as fp:
167
response = requests.post(
168
args.alpr_api, files=dict(upload=fp), timeout=10
169
)
170
else:
171
time.sleep(1) # Wait for the whole image to arrive
172
filename = os.path.basename(path)
173
response = requests.post(
174
args.alpr_api,
175
files=dict(
176
upload=(filename, open(path, "rb"), "application/octet-stream")
177
),
178
headers={"Authorization": "Token " + args.platerec_token},
179
)
180
181
except requests.exceptions.Timeout:
182
print("SDK: Timeout")
183
return
184
except ConnectionError:
185
print("SDK: ConnectionError")
186
return
187
except PermissionError:
188
print(f"SDK: {path} could not be read.")
189
return
190
except Exception as e:
191
print(e)
192
return
193
data = response.json()
194
# TODO: skip data if there is no change
195
if "results" not in data:
196
print(data)
197
return []
198
return data["results"]
199
200
201
def api_request(args, payload, files):
202
api_url = "https://app.parkpow.com/api/v1/log-vehicle"
203
headers = {"Authorization": f"Token {args.parkpow_token}"}
204
try:
205
response = requests.post(
206
api_url, data=payload, headers=headers, files=files, timeout=20
207
)
208
except ConnectionError:
209
print("ParkPow API: ConnectionError")
210
return
211
except requests.exceptions.Timeout:
212
print("ParkPow API: Timeout")
213
return
214
return response
215
216
217
###################
218
# File monitoring #
219
###################
220
221
222
def worker(args):
223
while True:
224
image_transfer(_queue.get(), args)
225
_queue.task_done()
226
227
228
class Handler(PatternMatchingEventHandler):
229
def on_created(self, event):
230
try:
231
_queue.put(event.src_path)
232
except queue.Full:
233
print(f"Queue is full. Skipping {event.src_path}.")
234
235
236
def main(args, debug=False):
237
if args.source in args.archive:
238
print("Archive argument should not be in source directory.")
239
return exit(1)
240
observer = Observer()
241
observer.schedule(
242
Handler(ignore_directories=True, patterns="*.jpg *.jpeg".split()),
243
args.source,
244
recursive=True,
245
)
246
observer.start()
247
for _ in range(args.workers):
248
t = threading.Thread(target=worker, args=(args,))
249
t.daemon = True
250
t.start()
251
252
print("Monitoring source directory.")
253
try:
254
while True:
255
time.sleep(1 if debug else 0.25)
256
if debug:
257
break
258
except KeyboardInterrupt:
259
pass
260
print("Closing...")
261
observer.stop()
262
observer.join()
263
_queue.join()
264
265
266
def validate_env(args):
267
messages = []
268
Path(args.archive).mkdir(parents=True, exist_ok=True)
269
if not Path(args.archive).exists():
270
messages.append(f"{args.archive} does not exist.")
271
if not Path(args.source).exists():
272
messages.append(f"{args.source} does not exist.")
273
274
if not args.use_parkpow and not args.output_file:
275
messages.append("Pass argument --use-parkpow or the argument --output-file")
276
if "http" not in args.alpr_api:
277
messages.append("--alpr-api is not a valid URL")
278
if "api.platerecognizer.com" in args.alpr_api and not args.platerec_token:
279
messages.append("Missing argument --platerec-token or SDK argument --alpr-api")
280
281
elif "api.platerecognizer.com" not in args.alpr_api:
282
try:
283
response = requests.get(args.alpr_api.rsplit("/v1", 1)[0], timeout=2)
284
except Exception:
285
response = None
286
if not response or response.status_code != 200:
287
messages.append(
288
f"Make sure that the SDK is up and running ({args.alpr_api})."
289
)
290
if args.use_parkpow:
291
api_url = "https://app.parkpow.com/api/v1/log-vehicle"
292
try:
293
response = requests.get(
294
api_url.rsplit("/", 1)[0] + "/parking-list",
295
headers={"Authorization": f"Token {args.parkpow_token}"},
296
timeout=2,
297
)
298
except Exception:
299
response = None
300
if not response or response.status_code != 200:
301
messages.append(
302
response.json() if response else "Parkpow server could not be reached."
303
)
304
if len(messages) > 0:
305
print("Script initialization failed:")
306
print("\n".join(messages))
307
print("Exiting...")
308
exit(1)
309
310
311
if __name__ == "__main__":
312
args = parse_arguments()
313
validate_env(args)
314
main(args)
315
316