m8chaa commited on
Commit
dc4d307
1 Parent(s): 4ebe34a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +270 -270
app.py CHANGED
@@ -9,12 +9,8 @@ import cv2
9
  import base64
10
  import httpx
11
  import logging
12
- import traceback
13
  import boto3
14
  import base64
15
- import urllib.parse
16
- from google.cloud import firestore
17
- from google.oauth2 import service_account
18
 
19
  app = FastAPI()
20
 
@@ -40,8 +36,277 @@ class ImageData(BaseModel):
40
  class UnprocessedImage(BaseModel):
41
  sheetID: str
42
  s3Url: str
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  # async def failure(sheetid):
46
  # async with httpx.AsyncClient(follow_redirects=True) as client:
47
  # response = await client.post(
@@ -102,21 +367,6 @@ class UnprocessedImage(BaseModel):
102
  # failure(image_data['sheetID'])
103
  # raise HTTPException(status_code=500, detail="Error from external API.")
104
 
105
- @app.post("/redirect")
106
- async def redirect_to_google_apps_script(request: Request):
107
- # Get the body of the incoming request
108
- body = await request.body()
109
-
110
- async with httpx.AsyncClient(timeout=300.0, follow_redirects=True) as client: # Follow redirects
111
- response = await client.post(
112
- 'https://script.google.com/macros/s/AKfycbw9_31pMwQVhJPezJ9qlMRCAamQKRREnun-tfT_TnS5TOwOVcfdgU1Y9xTpAv6bZuiKOA/exec',
113
- data=body
114
- )
115
- if response.status_code != 200:
116
- logging.error(f"Unexpected response from Google Apps Script API: {response.status_code} - {response.text}")
117
- raise HTTPException(status_code=500, detail="Error from external API.")
118
- return response.json()
119
-
120
  # @app.post("/save-to-s3")
121
  # async def save_to_s3(request: Request, background_tasks: BackgroundTasks):
122
  # image_data = await request.json()
@@ -335,256 +585,6 @@ async def redirect_to_google_apps_script(request: Request):
335
 
336
  # finally:
337
  # logging.info("File processing completed")
338
-
339
-
340
-
341
-
342
 
343
  # else:
344
- # raise HTTPException(status_code=400, detail="Email address not provided.")
345
- appsScriptsURL = 'https://script.google.com/macros/s/AKfycbw9_31pMwQVhJPezJ9qlMRCAamQKRREnun-tfT_TnS5TOwOVcfdgU1Y9xTpAv6bZuiKOA/exec'
346
- dezgo_api_key = 'DEZGO-AA84DEB300562DA089E56CEA6081AE7DB601EF2D3EE4C693910E1C25E59A4FF8023D4932'
347
-
348
- dezgo_headers = {
349
- 'X-Dezgo-Key': dezgo_api_key,
350
- }
351
-
352
- # update the google sheet with failure
353
- async def record_failure(s3_original_url, failed_task, sheetID):
354
- body = {
355
- "s3Url": s3_original_url,
356
- "mode": "failed",
357
- "message": failed_task,
358
- "sheetID": sheetID,
359
- }
360
- async with httpx.AsyncClient(follow_redirects=True) as client:
361
- response = await client.post(appsScriptsURL, json=body)
362
- if response.status_code != 200:
363
- logging.error(f"Unexpected response from Google Apps Script API: {response.status_code} - {response.text}")
364
- raise HTTPException(status_code=500, detail="Error from external API.")
365
-
366
- async def post_to_google_apps_script(appsScriptsURL, body, s3_orginal_url):
367
- async with httpx.AsyncClient(follow_redirects=True) as client:
368
- response = await client.post(appsScriptsURL, json=body)
369
- if response.status_code != 200:
370
- logging.error(f"Unexpected response from Google Apps Script API: {response.status_code} - {response.text}")
371
- await record_failure(s3_orginal_url, 'Failed to queue processed image.', body.sheetID)
372
-
373
- async def background_worker(sheeturl, image_contents, filename, content_type, appsScriptsURL):
374
- sheetID = sheeturl.split('/')[-2]
375
- image_data = {
376
- 'sheetID': sheetID,
377
- 'fileName': filename,
378
- 'mimeType': content_type
379
- }
380
-
381
- s3_client = boto3.client('s3')
382
- bucket_name = 're-sheet'
383
- object_name = sheetID + '/' + image_data['fileName']
384
-
385
- s3response = s3_client.put_object(
386
- Bucket=bucket_name,
387
- Key=object_name,
388
- Body=image_contents,
389
- ContentType=image_data['mimeType']
390
- )
391
- cut_images = []
392
- s3_orginal_url = f"https://{bucket_name}.s3.amazonaws.com/{object_name}"
393
- masked_image = await detect_receipts(image_contents, s3_orginal_url, sheetID)
394
- await cutting_image(masked_image, image_contents, cut_images, s3_orginal_url, sheetID)
395
-
396
- queued = []
397
- # save the processed image(s) to s3
398
- if not cut_images:
399
- await record_failure(s3_orginal_url, 'No valid receipts found.', sheetID)
400
- return {"message": "No valid receipts found."}
401
- else:
402
- for i, base64_str in enumerate(cut_images, start=1):
403
- image_bytes = base64.b64decode(base64_str)
404
- object_name = f"{object_name}_processed_{i}.png"
405
- response = s3_client.put_object(
406
- Bucket=bucket_name,
407
- Key=object_name,
408
- Body=image_bytes,
409
- ContentType='image/png'
410
- )
411
- status = response.get("ResponseMetadata", {}).get("HTTPStatusCode")
412
- if status != 200:
413
- await record_failure(s3_orginal_url, 'Failed to upload processed image to S3.', sheetID)
414
- return {"message": "Failed to upload processed image to S3."}
415
- # otherwise put it as queued
416
- object_s3_url = f"https://{bucket_name}.s3.amazonaws.com/{object_name}"
417
- queued.append(object_s3_url)
418
- # queue the image to google apps script web app
419
- body ={
420
- "queued": queued,
421
- "mode": "mobile",
422
- "sheetID": sheetID,
423
- }
424
-
425
- print(body)
426
- logging.info(body)
427
- await post_to_google_apps_script(appsScriptsURL, body, s3_orginal_url)
428
- # receive a sheetID and an image as multipart form data
429
- @app.post('/mobile-process-receipts')
430
- async def mobile_process_receipts(background_tasks: BackgroundTasks, sheeturl: str = Form(...), image: UploadFile = File(...)):
431
- image_contents = await image.read()
432
- uuid = str(uuid4())
433
- timestamp = str(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
434
- message = "영수기 AI가 데이터 분석 중입니다. 잠시만 기다려주세요."
435
- data = [timestamp, uuid, message]
436
- body = {
437
- "sheetID": sheeturl.split('/')[-2],
438
- "data": data,
439
- "mode": "alert-user"
440
- }
441
- async with httpx.AsyncClient(follow_redirects=True) as client:
442
- response = await client.post(appsScriptsURL, json=body)
443
- if response.status_code != 200:
444
- logging.error(f"Unexpected response from Google Apps Script API: {response.status_code} - {response.text}")
445
- raise HTTPException(status_code=500, detail="Error from external API.")
446
-
447
- background_tasks.add_task(background_worker, sheeturl, image_contents, uuid, image.content_type, appsScriptsURL)
448
-
449
- return {"message": "Image processing completed."}
450
-
451
- @app.post('/process-receipts')
452
- async def mobile_process_receipts(background_tasks: BackgroundTasks, request: Request):
453
- data = await request.json()
454
- image_contents = base64.b64decode(data['base64Image'])
455
- content_type = data['mimeType']
456
- sheeturl = data['sheetID']
457
- uuid = str(uuid4())
458
- timestamp = str(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
459
- message = "영수기 AI가 데이터 분석 중입니다. 잠시만 기다려주세요."
460
- data = [timestamp, uuid, message]
461
- body = {
462
- "sheetID": sheeturl.split('/')[-2],
463
- "data": data,
464
- "mode": "alert-user"
465
- }
466
- async with httpx.AsyncClient(follow_redirects=True) as client:
467
- response = await client.post(appsScriptsURL, json=body)
468
- if response.status_code != 200:
469
- logging.error(f"Unexpected response from Google Apps Script API: {response.status_code} - {response.text}")
470
- raise HTTPException(status_code=500, detail="Error from external API.")
471
-
472
- background_tasks.add_task(background_worker, sheeturl, image_contents, uuid, content_type, appsScriptsURL)
473
-
474
- return {"message": "Image processing completed."}
475
-
476
- # send the image to dezgo
477
- async def detect_receipts(image_contents, s3_orginal_url, sheetID):
478
- files = {
479
- 'image': ('filename', image_contents),
480
- 'mode': 'mask',
481
- }
482
- logging.info("Sending image to Dezgo API")
483
- async with httpx.AsyncClient(timeout=timeout) as client:
484
- response = await client.post('https://api.dezgo.com/remove-background', headers=dezgo_headers, files=files)
485
- # if successful, pass the masked image to cutting_image function
486
- if response.status_code == 200:
487
- masked_image = response.content
488
- return masked_image
489
- else:
490
- logging.error(f"Unexpected response from Dezgo API: {response.status_code} - {response.text}")
491
- await record_failure(s3_orginal_url, 'Dezgo API failed.', sheetID)
492
- raise HTTPException(status_code=500, detail="Error from external API.")
493
-
494
- # receive the masked image and process it
495
- async def cutting_image(masked_image, image_contents, cut_images, s3_orginal_url, sheetID):
496
- # Convert the masked_image (which is in bytes format) to a numpy array.
497
- # The dtype of the array is set to np.uint8 because images are usually 8-bit per channel.
498
- nparr_masked = np.frombuffer(masked_image, np.uint8)
499
-
500
- # Check if the size of the numpy array is 0, which means that the conversion failed.
501
- if nparr_masked.size == 0:
502
- # Log an error message indicating that the conversion failed.
503
- logging.error("Failed to convert API response to numpy array.")
504
- # Record the reason for the failure.
505
- record_failure(s3_orginal_url, 'failed to convert API response to numpy array.', sheetID)
506
- # Raise an HTTPException with a 500 status code (Internal Server Error) and a detail message.
507
- raise HTTPException(status_code=500, detail="Error processing API response data.")
508
-
509
- # Decode the numpy array to an image using OpenCV. The flag cv2.IMREAD_COLOR loads the image in color.
510
- masked_image = cv2.imdecode(nparr_masked, cv2.IMREAD_COLOR)
511
-
512
- # Check if the decoding failed, which would result in masked_image being None.
513
- if masked_image is None:
514
- # Log an error message indicating that the decoding failed.
515
- logging.error("Failed to decode image data.")
516
- # Record the reason for the failure.
517
- record_failure(s3_orginal_url, 'failed to decode image data.', sheetID)
518
- # Raise an HTTPException with a 500 status code (Internal Server Error) and a detail message.
519
- raise HTTPException(status_code=500, detail="Error decoding image data.")
520
-
521
- nparr_original = np.frombuffer(image_contents, np.uint8)
522
- original_image = cv2.imdecode(nparr_original, cv2.IMREAD_COLOR)
523
-
524
- original_aspect_ratio = original_image.shape[1] / original_image.shape[0]
525
- masked_aspect_ratio = masked_image.shape[1] / masked_image.shape[0]
526
-
527
- # if the image has been rotated by dezgo, rotate it back
528
- if abs(original_aspect_ratio - masked_aspect_ratio) > 0.1:
529
- # Rotate the masked image 90 degrees clockwise
530
- masked_image = cv2.rotate(masked_image, cv2.ROTATE_90_CLOCKWISE)
531
-
532
- original_image_shape = original_image.shape[:2]
533
-
534
- resized_masked_image = cv2.resize(masked_image, (original_image_shape[1], original_image_shape[0]))
535
-
536
-
537
-
538
- blurred_masked_image = cv2.GaussianBlur(resized_masked_image, (5, 5), 0)
539
- _, thresh_masked_image = cv2.threshold(blurred_masked_image, 175, 255, cv2.THRESH_BINARY)
540
- edges = cv2.Canny(thresh_masked_image, 75, 150)
541
- contours, hierarchy = cv2.findContours(edges.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
542
-
543
- padding = 20
544
- for contour in contours:
545
- perimeter = cv2.arcLength(contour, True)
546
- approximation = cv2.approxPolyDP(contour, 0.02 * perimeter, True)
547
- x, y, w, h = cv2.boundingRect(approximation)
548
-
549
- # Add padding to the bounding box coordinates while ensuring they are within the image boundaries
550
- x_pad = max(x - padding, 0)
551
- y_pad = max(y - padding, 0)
552
- w_pad = min(w + 2 * padding, original_image.shape[1] - x_pad)
553
- h_pad = min(h + 2 * padding, original_image.shape[0] - y_pad)
554
-
555
- # logging.info(f"Bounding box: x={x_pad}, y={y_pad}, w={w_pad}, h={h_pad}")
556
-
557
- # Check if the area of the bounding rectangle is greater than 50px x 50px
558
- if w_pad > 150 or h_pad > 150:
559
- cropped_image = original_image[y_pad:y_pad + h_pad, x_pad:x_pad + w_pad]
560
- _, img_encoded = cv2.imencode('.png', cropped_image)
561
- base64_str = base64.b64encode(img_encoded).decode('utf-8')
562
- cut_images.append(base64_str)
563
- # -> failed or empty: send a response of image and sheetid to the google sheet
564
- # save the processed image(s) to s3
565
- # -> failed or empty: send a response of image and sheetid to the google sheet
566
- # use google ocr to extract text from the image
567
- # -> failed or empty: send a response of image and sheetid to the google sheet
568
- # send the text to Openai
569
- # -> failed or empty: send a response of image and sheetid to the google sheet
570
- # send the response to the google sheet
571
-
572
- @app.post('/test')
573
- async def mobile_process_receipts(background_tasks: BackgroundTasks, sheeturl: str = Form(...), image: UploadFile = File(...)):
574
- image_contents = await image.read()
575
- uuid = str(uuid4())
576
- timestamp = str(datetime.now().strftime("%Y-%m-%d-%H-%M-%S"))
577
- message = "영수기가 데이터 분석 중입니다. 잠시만 기다려주세요."
578
- data = [timestamp, uuid, message]
579
- body = {
580
- "sheetID": sheeturl.split('/')[-2],
581
- "data": data,
582
- "mode": "alert-user"
583
- }
584
- async with httpx.AsyncClient(follow_redirects=True) as client:
585
- response = await client.post(appsScriptsURL, json=body)
586
- if response.status_code != 200:
587
- logging.error(f"Unexpected response from Google Apps Script API: {response.status_code} - {response.text}")
588
- raise HTTPException(status_code=500, detail="Error from external API.")
589
- background_tasks.add_task(background_worker, sheeturl, image_contents, image.filename, image.content_type, appsScriptsURL)
590
- return {"message": "Image processing completed."}
 
9
  import base64
10
  import httpx
11
  import logging
 
12
  import boto3
13
  import base64
 
 
 
14
 
15
  app = FastAPI()
16
 
 
36
  class UnprocessedImage(BaseModel):
37
  sheetID: str
38
  s3Url: str
39
+
40
+ @app.post("/redirect")
41
+ async def redirect_to_google_apps_script(request: Request):
42
+ # Get the body of the incoming request
43
+ body = await request.body()
44
+
45
+ async with httpx.AsyncClient(timeout=300.0, follow_redirects=True) as client: # Follow redirects
46
+ response = await client.post(
47
+ 'https://script.google.com/macros/s/AKfycbw9_31pMwQVhJPezJ9qlMRCAamQKRREnun-tfT_TnS5TOwOVcfdgU1Y9xTpAv6bZuiKOA/exec',
48
+ data=body
49
+ )
50
+ if response.status_code != 200:
51
+ logging.error(f"Unexpected response from Google Apps Script API: {response.status_code} - {response.text}")
52
+ raise HTTPException(status_code=500, detail="Error from external API.")
53
+ return response.json()
54
+
55
+ appsScriptsURL = 'https://script.google.com/macros/s/AKfycbw9_31pMwQVhJPezJ9qlMRCAamQKRREnun-tfT_TnS5TOwOVcfdgU1Y9xTpAv6bZuiKOA/exec'
56
+ dezgo_api_key = 'DEZGO-AA84DEB300562DA089E56CEA6081AE7DB601EF2D3EE4C693910E1C25E59A4FF8023D4932'
57
+
58
+ dezgo_headers = {
59
+ 'X-Dezgo-Key': dezgo_api_key,
60
+ }
61
+
62
+ # update the google sheet with failure
63
+ async def record_failure(s3_original_url, failed_task, sheetID):
64
+ body = {
65
+ "s3Url": s3_original_url,
66
+ "mode": "failed",
67
+ "message": failed_task,
68
+ "sheetID": sheetID,
69
+ }
70
+ async with httpx.AsyncClient(follow_redirects=True) as client:
71
+ response = await client.post(appsScriptsURL, json=body)
72
+ if response.status_code != 200:
73
+ logging.error(f"Unexpected response from Google Apps Script API: {response.status_code} - {response.text}")
74
+ raise HTTPException(status_code=500, detail="Error from external API.")
75
+
76
+ async def post_to_google_apps_script(appsScriptsURL, body, s3_orginal_url):
77
+ async with httpx.AsyncClient(follow_redirects=True) as client:
78
+ response = await client.post(appsScriptsURL, json=body)
79
+ if response.status_code != 200:
80
+ logging.error(f"Unexpected response from Google Apps Script API: {response.status_code} - {response.text}")
81
+ await record_failure(s3_orginal_url, 'Failed to queue processed image.', body.sheetID)
82
+
83
+ async def background_worker(sheeturl, image_contents, filename, content_type, appsScriptsURL):
84
+ sheetID = sheeturl.split('/')[-2]
85
+ image_data = {
86
+ 'sheetID': sheetID,
87
+ 'fileName': filename,
88
+ 'mimeType': content_type
89
+ }
90
+
91
+ s3_client = boto3.client('s3')
92
+ bucket_name = 're-sheet'
93
+ object_name = sheetID + '/' + image_data['fileName']
94
+ cut_images = []
95
+ s3_orginal_url = f"https://{bucket_name}.s3.amazonaws.com/{object_name}"
96
+ masked_image = await detect_receipts(image_contents, s3_orginal_url, sheetID)
97
+ await cutting_image(masked_image, image_contents, cut_images, s3_orginal_url, sheetID)
98
 
99
+ queued = []
100
+ # save the processed image(s) to s3
101
+ if not cut_images:
102
+ await record_failure(s3_orginal_url, 'No valid receipts found.', sheetID)
103
+ return {"message": "No valid receipts found."}
104
+ else:
105
+ for i, base64_str in enumerate(cut_images, start=1):
106
+ image_bytes = base64.b64decode(base64_str)
107
+ object_name = f"{object_name}_processed_{i}.png"
108
+ response = s3_client.put_object(
109
+ Bucket=bucket_name,
110
+ Key=object_name,
111
+ Body=image_bytes,
112
+ ContentType='image/png'
113
+ )
114
+ status = response.get("ResponseMetadata", {}).get("HTTPStatusCode")
115
+ if status != 200:
116
+ await record_failure(s3_orginal_url, 'Failed to upload processed image to S3.', sheetID)
117
+ return {"message": "Failed to upload processed image to S3."}
118
+ # otherwise put it as queued
119
+ object_s3_url = f"https://{bucket_name}.s3.amazonaws.com/{object_name}"
120
+ queued.append(object_s3_url)
121
+ # queue the image to google apps script web app
122
+ body ={
123
+ "queued": queued,
124
+ "mode": "mobile",
125
+ "sheetID": sheetID,
126
+ }
127
+
128
+ print(body)
129
+ logging.info(body)
130
+ await post_to_google_apps_script(appsScriptsURL, body, s3_orginal_url)
131
+ return {"message": "Image processing completed."}
132
 
133
+ # receive a sheetID and an image as multipart form data
134
+ @app.post('/mobile-process-receipts')
135
+ async def mobile_process_receipts(background_tasks: BackgroundTasks, sheeturl: str = Form(...), image: UploadFile = File(...)):
136
+ image_contents = await image.read()
137
+ uuid = str(uuid4())
138
+ timestamp = str(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
139
+ sheetID = sheeturl.split('/')[-2]
140
+
141
+ message = "영수기 AI가 데이터 분석 중입니다. 잠시만 기다려주세요."
142
+ data = [timestamp, uuid, message]
143
+ body = {
144
+ "sheetID": sheetID,
145
+ "data": data,
146
+ "mode": "alert-user"
147
+ }
148
+ async with httpx.AsyncClient(follow_redirects=True) as client:
149
+ response = await client.post(appsScriptsURL, json=body)
150
+ if response.status_code != 200:
151
+ logging.error(f"Unexpected response from Google Apps Script API: {response.status_code} - {response.text}")
152
+ raise HTTPException(status_code=500, detail="Error from external API.")
153
+
154
+ background_tasks.add_task(background_worker, sheeturl, image_contents, uuid, image.content_type, appsScriptsURL, )
155
+
156
+ return {"message": "Image processing completed."}
157
+
158
+
159
+ @app.post('/process-receipts')
160
+ async def mobile_process_receipts(background_tasks: BackgroundTasks, request: Request):
161
+ data = await request.json()
162
+ base64_str = data['base64Image']
163
+ prefix, base64_str = base64_str.split(',', 1) # Split on the first comma
164
+ image_contents = base64.b64decode(base64_str)
165
+ content_type = data['mimeType']
166
+ sheeturl = data['sheetID']
167
+
168
+ checkPhoto = {
169
+ 'sheetID': sheeturl.split('/')[-2],
170
+ 'mode': "check",
171
+ 'photo': image_contents,
172
+ }
173
+ async with httpx.AsyncClient(follow_redirects=True) as client:
174
+ response = await client.post(appsScriptsURL, json=checkPhoto)
175
+ if response.status_code != 200:
176
+ logging.error(f"Unexpected response from Google Apps Script API: {response.status_code} - {response.text}")
177
+ raise HTTPException(status_code=500, detail="Error from external API.")
178
+ if response.json()['status'] == 'invalid':
179
+ return {"message": "Invalid photo."}
180
+
181
+
182
+ uuid = str(uuid4())
183
+ timestamp = str(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
184
+ message = "영수기 AI가 데이터 분석 중입니다. 잠시만 기다려주세요."
185
+ data = [timestamp, uuid, message]
186
+ body = {
187
+ "sheetID": sheeturl.split('/')[-2],
188
+ "data": data,
189
+ "mode": "alert-user"
190
+ }
191
+ async with httpx.AsyncClient(follow_redirects=True) as client:
192
+ response = await client.post(appsScriptsURL, json=body)
193
+ if response.status_code != 200:
194
+ logging.error(f"Unexpected response from Google Apps Script API: {response.status_code} - {response.text}")
195
+ raise HTTPException(status_code=500, detail="Error from external API.")
196
+
197
+ background_tasks.add_task(background_worker, sheeturl, image_contents, uuid, content_type, appsScriptsURL)
198
+
199
+ return {"message": "Image processing completed."}
200
+
201
+ # send the image to dezgo
202
+ async def detect_receipts(image_contents, s3_orginal_url, sheetID):
203
+ files = {
204
+ 'image': ('filename', image_contents),
205
+ 'mode': 'mask',
206
+ }
207
+ logging.info("Sending image to Dezgo API")
208
+ async with httpx.AsyncClient(timeout=timeout) as client:
209
+ response = await client.post('https://api.dezgo.com/remove-background', headers=dezgo_headers, files=files)
210
+ # if successful, pass the masked image to cutting_image function
211
+ if response.status_code == 200:
212
+ masked_image = response.content
213
+ return masked_image
214
+ else:
215
+ logging.error(f"Unexpected response from Dezgo API: {response.status_code} - {response.text}")
216
+ await record_failure(s3_orginal_url, 'Dezgo API failed.', sheetID)
217
+ raise HTTPException(status_code=500, detail="Error from external API.")
218
+
219
+ # receive the masked image and process it
220
+ async def cutting_image(masked_image, image_contents, cut_images, s3_orginal_url, sheetID):
221
+ # Convert the masked_image (which is in bytes format) to a numpy array.
222
+ # The dtype of the array is set to np.uint8 because images are usually 8-bit per channel.
223
+ nparr_masked = np.frombuffer(masked_image, np.uint8)
224
+
225
+ # Check if the size of the numpy array is 0, which means that the conversion failed.
226
+ if nparr_masked.size == 0:
227
+ # Log an error message indicating that the conversion failed.
228
+ logging.error("Failed to convert API response to numpy array.")
229
+ # Record the reason for the failure.
230
+ record_failure(s3_orginal_url, 'failed to convert API response to numpy array.', sheetID)
231
+ # Raise an HTTPException with a 500 status code (Internal Server Error) and a detail message.
232
+ raise HTTPException(status_code=500, detail="Error processing API response data.")
233
+
234
+ # Decode the numpy array to an image using OpenCV. The flag cv2.IMREAD_COLOR loads the image in color.
235
+ masked_image = cv2.imdecode(nparr_masked, cv2.IMREAD_COLOR)
236
+
237
+ # Check if the decoding failed, which would result in masked_image being None.
238
+ if masked_image is None:
239
+ # Log an error message indicating that the decoding failed.
240
+ logging.error("Failed to decode image data.")
241
+ # Record the reason for the failure.
242
+ record_failure(s3_orginal_url, 'failed to decode image data.', sheetID)
243
+ # Raise an HTTPException with a 500 status code (Internal Server Error) and a detail message.
244
+ raise HTTPException(status_code=500, detail="Error decoding image data.")
245
+
246
+ nparr_original = np.frombuffer(image_contents, np.uint8)
247
+ original_image = cv2.imdecode(nparr_original, cv2.IMREAD_COLOR)
248
+
249
+ original_aspect_ratio = original_image.shape[1] / original_image.shape[0]
250
+ masked_aspect_ratio = masked_image.shape[1] / masked_image.shape[0]
251
+
252
+ # if the image has been rotated by dezgo, rotate it back
253
+ if abs(original_aspect_ratio - masked_aspect_ratio) > 0.1:
254
+ # Rotate the masked image 90 degrees clockwise
255
+ masked_image = cv2.rotate(masked_image, cv2.ROTATE_90_CLOCKWISE)
256
+
257
+ original_image_shape = original_image.shape[:2]
258
+
259
+ resized_masked_image = cv2.resize(masked_image, (original_image_shape[1], original_image_shape[0]))
260
+
261
+
262
+
263
+ blurred_masked_image = cv2.GaussianBlur(resized_masked_image, (5, 5), 0)
264
+ _, thresh_masked_image = cv2.threshold(blurred_masked_image, 175, 255, cv2.THRESH_BINARY)
265
+ edges = cv2.Canny(thresh_masked_image, 75, 150)
266
+ contours, hierarchy = cv2.findContours(edges.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
267
+
268
+ padding = 20
269
+ for contour in contours:
270
+ perimeter = cv2.arcLength(contour, True)
271
+ approximation = cv2.approxPolyDP(contour, 0.02 * perimeter, True)
272
+ x, y, w, h = cv2.boundingRect(approximation)
273
+
274
+ # Add padding to the bounding box coordinates while ensuring they are within the image boundaries
275
+ x_pad = max(x - padding, 0)
276
+ y_pad = max(y - padding, 0)
277
+ w_pad = min(w + 2 * padding, original_image.shape[1] - x_pad)
278
+ h_pad = min(h + 2 * padding, original_image.shape[0] - y_pad)
279
+
280
+ # logging.info(f"Bounding box: x={x_pad}, y={y_pad}, w={w_pad}, h={h_pad}")
281
+
282
+ # Check if the area of the bounding rectangle is greater than 50px x 50px
283
+ if w_pad > 150 or h_pad > 150:
284
+ cropped_image = original_image[y_pad:y_pad + h_pad, x_pad:x_pad + w_pad]
285
+ _, img_encoded = cv2.imencode('.png', cropped_image)
286
+ base64_str = base64.b64encode(img_encoded).decode('utf-8')
287
+ cut_images.append(base64_str)
288
+
289
+
290
+ @app.post('/test')
291
+ async def mobile_process_receipts(background_tasks: BackgroundTasks, sheeturl: str = Form(...), image: UploadFile = File(...)):
292
+ image_contents = await image.read()
293
+ uuid = str(uuid4())
294
+ timestamp = str(datetime.now().strftime("%Y-%m-%d-%H-%M-%S"))
295
+ message = "영수기가 데이터 분석 중입니다. 잠시만 기다려주세요."
296
+ data = [timestamp, uuid, message]
297
+ body = {
298
+ "sheetID": sheeturl.split('/')[-2],
299
+ "data": data,
300
+ "mode": "alert-user"
301
+ }
302
+ async with httpx.AsyncClient(follow_redirects=True) as client:
303
+ response = await client.post(appsScriptsURL, json=body)
304
+ if response.status_code != 200:
305
+ logging.error(f"Unexpected response from Google Apps Script API: {response.status_code} - {response.text}")
306
+ raise HTTPException(status_code=500, detail="Error from external API.")
307
+ background_tasks.add_task(background_worker, sheeturl, image_contents, image.filename, image.content_type, appsScriptsURL)
308
+ return {"message": "Image processing completed."}
309
+
310
  # async def failure(sheetid):
311
  # async with httpx.AsyncClient(follow_redirects=True) as client:
312
  # response = await client.post(
 
367
  # failure(image_data['sheetID'])
368
  # raise HTTPException(status_code=500, detail="Error from external API.")
369
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
370
  # @app.post("/save-to-s3")
371
  # async def save_to_s3(request: Request, background_tasks: BackgroundTasks):
372
  # image_data = await request.json()
 
585
 
586
  # finally:
587
  # logging.info("File processing completed")
 
 
 
 
588
 
589
  # else:
590
+ # raise HTTPException(status_code=400, detail="Email address not provided.")