jayllfpt commited on
Commit
ded98ad
·
1 Parent(s): baa6acd

update copy OCR

Browse files
app.py CHANGED
@@ -1,6 +1,115 @@
1
  import streamlit as st
 
 
 
 
 
 
2
 
3
- from st_copy_to_clipboard import st_copy_to_clipboard
4
 
5
- # Render copy to clipboard button
6
- st_copy_to_clipboard("Copy this to clipboard")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
2
+ from akaocr import TextEngine, BoxEngine
3
+ import cv2
4
+ import numpy as np
5
+ from PIL import Image
6
+ import time
7
+ import pyperclip # Import pyperclip for clipboard operations
8
 
9
+ from custom_component import st_copy_to_clipboard
10
 
11
+
12
+ # Initialize the OCR engines
13
+ box_engine = BoxEngine()
14
+ text_engine = TextEngine()
15
+
16
+
17
+ def transform_image(image, box):
18
+ # Get perspective transform image
19
+ assert len(box) == 4, "Shape of points must be 4x2"
20
+ img_crop_width = int(
21
+ max(
22
+ np.linalg.norm(box[0] - box[1]),
23
+ np.linalg.norm(box[2] - box[3])))
24
+ img_crop_height = int(
25
+ max(
26
+ np.linalg.norm(box[0] - box[3]),
27
+ np.linalg.norm(box[1] - box[2])))
28
+ pts_std = np.float32([[0, 0],
29
+ [img_crop_width, 0],
30
+ [img_crop_width, img_crop_height],
31
+ [0, img_crop_height]])
32
+ box = np.array(box, dtype="float32")
33
+ M = cv2.getPerspectiveTransform(box, pts_std)
34
+ dst_img = cv2.warpPerspective(
35
+ image,
36
+ M, (img_crop_width, img_crop_height),
37
+ borderMode=cv2.BORDER_REPLICATE,
38
+ flags=cv2.INTER_CUBIC)
39
+
40
+ img_height, img_width = dst_img.shape[0:2]
41
+ if img_height/img_width >= 1.25:
42
+ dst_img = np.rot90(dst_img, k=3)
43
+
44
+ return dst_img
45
+
46
+
47
+ def two_pts(bounding_box):
48
+ # Extract the x and y coordinates separately
49
+ return (
50
+ (
51
+ round(min([x[0] for x in bounding_box])),
52
+ round(min([x[1] for x in bounding_box]))
53
+ ),
54
+ (
55
+ round(max([x[0] for x in bounding_box])),
56
+ round(max([x[1] for x in bounding_box]))
57
+ )
58
+ )
59
+
60
+
61
+
62
+ def main():
63
+ st.set_page_config(
64
+ page_title="Quick OCR Copy",
65
+ page_icon=":flag-vn:",
66
+ layout="wide"
67
+ )
68
+
69
+ uploaded_file = st.file_uploader(
70
+ "Choose an image...", type=["jpg", "jpeg", "png"])
71
+
72
+ if uploaded_file is not None:
73
+ # Convert the uploaded file to an OpenCV image
74
+ file_bytes = np.asarray(
75
+ bytearray(uploaded_file.read()), dtype=np.uint8)
76
+ org_image = cv2.imdecode(file_bytes, 1)
77
+ # st.image(org_image, channels="BGR", caption='Uploaded Image')
78
+
79
+ images = []
80
+ start = time.perf_counter()
81
+ boxes = box_engine(org_image)
82
+ processing_time = time.perf_counter() - start
83
+ st.write(f"Box detection took {processing_time:.2f} seconds.")
84
+
85
+
86
+ for box in boxes[::-1]:
87
+ org_image = cv2.polylines(org_image, [box.astype(
88
+ np.int32)], isClosed=True, color=(0, 255, 0), thickness=2)
89
+ image = transform_image(org_image, box)
90
+ images.append(image)
91
+
92
+ # Get the texts from the boxes
93
+ texts = text_engine(images)
94
+
95
+ # Convert back to PIL Image for displaying
96
+ output_image = Image.fromarray(
97
+ cv2.cvtColor(org_image, cv2.COLOR_BGR2RGB))
98
+ # st.image(output_image, caption='Detected Text Boxes', use_column_width=True)
99
+
100
+ # button_locations = [(50, 10), (100, 100), (200, 300)]
101
+ # text_list = ["Hello", "Streamlit", "World"]
102
+ button_coords = [two_pts(box) for box in boxes[::-1]]
103
+ text_list = [x[0] for x in texts]
104
+
105
+ # Call the custom component
106
+ st_copy_to_clipboard(
107
+ image=output_image,
108
+ button_coords=button_coords,
109
+ text_list=text_list,
110
+ before_copy_label="",
111
+ after_copy_label=""
112
+ )
113
+
114
+ if __name__ == '__main__':
115
+ main()
custom_component/__init__.py ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # __init__.py
2
+
3
+ from pathlib import Path
4
+ from typing import Optional, Tuple, List
5
+ from PIL import Image
6
+ import streamlit as st
7
+ import streamlit.components.v1 as components
8
+
9
+ frontend_dir = (Path(__file__).parent / "frontend").absolute()
10
+ _component_func = components.declare_component(
11
+ "streamlit_copy_to_clipboard", path=str(frontend_dir)
12
+ )
13
+
14
+ def st_copy_to_clipboard(
15
+ image: Image.Image,
16
+ button_coords: List[Tuple[Tuple[int, int], Tuple[int, int]]],
17
+ text_list: List[str],
18
+ before_copy_label: str = "📋",
19
+ after_copy_label: str = "✅",
20
+ key: Optional[str] = None,
21
+ ):
22
+ """
23
+ Streamlit component to copy text to clipboard with buttons over an image.
24
+
25
+ Parameters
26
+ ----------
27
+ image : PIL.Image
28
+ The image over which the buttons will be placed.
29
+ button_coords : List[Tuple[Tuple[int, int], Tuple[int, int]]]
30
+ List of tuples, each containing top-left and bottom-right coordinates for buttons.
31
+ text_list : List[str]
32
+ List of texts to be copied to the clipboard.
33
+ before_copy_label : str
34
+ Label of the button before text is copied.
35
+ after_copy_label : str
36
+ Label of the button after text is copied.
37
+ key : str or None
38
+ An optional key that uniquely identifies the component.
39
+ """
40
+ # Convert the PIL image to base64 for sending to frontend
41
+ import base64
42
+ from io import BytesIO
43
+
44
+ buffered = BytesIO()
45
+ image.save(buffered, format="PNG")
46
+ img_str = base64.b64encode(buffered.getvalue()).decode("utf-8")
47
+
48
+ # Create the component
49
+ component_value = _component_func(
50
+ key=key,
51
+ image=f"data:image/png;base64,{img_str}",
52
+ button_coords=button_coords,
53
+ text_list=text_list,
54
+ before_copy_label=before_copy_label,
55
+ after_copy_label=after_copy_label,
56
+ )
57
+
58
+ return component_value
59
+
60
+
61
+ def main():
62
+ st.write("## Example: Image with Copy Buttons")
63
+
64
+ # Example image and button coordinates
65
+ img = Image.new('RGB', (1080, 1080), color = 'white')
66
+ button_coords = [((152, 130), (252, 180)), ((400, 500), (500, 550))] # List of top-left and bottom-right points
67
+ texts = ["Copy text 1", "Copy text 2"]
68
+
69
+ st_copy_to_clipboard(img, button_coords, texts)
70
+
71
+
72
+ if __name__ == "__main__":
73
+ main()
custom_component/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (3.23 kB). View file
 
custom_component/frontend/index.html ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!-- index.html -->
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8
+ <title>st-copy-to-clipboard</title>
9
+ <script src="./streamlit-component-lib.js"></script>
10
+ <script src="./main.js"></script>
11
+ <link rel="stylesheet" href="./style.css" />
12
+ </head>
13
+
14
+ <body>
15
+ <div id="root">
16
+ <!-- Container for the image and buttons -->
17
+ <div id="image-container" class="image-container">
18
+ <img id="image-element" class="copy-image" src="" alt="Image with copy buttons" />
19
+ </div>
20
+ </div>
21
+ </body>
22
+ </html>
custom_component/frontend/main.js ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // main.js
2
+
3
+ function sendValue(value) {
4
+ Streamlit.setComponentValue(value);
5
+ }
6
+
7
+ function onRender(event) {
8
+ if (!window.rendered) {
9
+ const { image, button_coords, text_list, before_copy_label, after_copy_label } = event.detail.args;
10
+
11
+ // Set the image source
12
+ const imageElement = document.getElementById("image-element");
13
+ imageElement.src = image;
14
+
15
+ // Wait for the image to load to adjust frame height and width
16
+ imageElement.onload = function () {
17
+ const imageWidth = imageElement.naturalWidth;
18
+ const imageHeight = imageElement.naturalHeight;
19
+
20
+ // Set the container size to match the image
21
+ const imageContainer = document.getElementById("image-container");
22
+ imageContainer.style.width = `${imageWidth}px`;
23
+ imageContainer.style.height = `${imageHeight}px`;
24
+
25
+ // Dynamically set the frame height based on the image size
26
+ Streamlit.setFrameHeight(imageHeight + 50); // Add some extra space for buttons
27
+
28
+ // Place buttons based on provided coordinates and sizes
29
+ button_coords.forEach((coords, index) => {
30
+ const [topLeft, bottomRight] = coords;
31
+ const button = document.createElement("button");
32
+ button.textContent = before_copy_label;
33
+ button.className = "st-copy-to-clipboard-btn";
34
+
35
+ // Calculate button position and size
36
+ const buttonWidth = bottomRight[0] - topLeft[0];
37
+ const buttonHeight = bottomRight[1] - topLeft[1];
38
+ button.style.position = "absolute";
39
+ button.style.left = `${topLeft[0]}px`;
40
+ button.style.top = `${topLeft[1]}px`;
41
+ button.style.width = `${buttonWidth}px`;
42
+ button.style.height = `${buttonHeight}px`;
43
+
44
+ // Define copy function for each button
45
+ button.addEventListener("click", () => {
46
+ navigator.clipboard.writeText(text_list[index]);
47
+ button.textContent = after_copy_label;
48
+ setTimeout(() => {
49
+ button.textContent = before_copy_label;
50
+ }, 1000);
51
+ });
52
+
53
+ imageContainer.appendChild(button);
54
+ });
55
+ };
56
+
57
+ window.rendered = true;
58
+ }
59
+ }
60
+
61
+ // Register Streamlit event listeners
62
+ Streamlit.events.addEventListener(Streamlit.RENDER_EVENT, onRender);
63
+ Streamlit.setComponentReady();
custom_component/frontend/streamlit-component-lib.js ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ // Borrowed minimalistic Streamlit API from Thiago
3
+ // https://discuss.streamlit.io/t/code-snippet-create-components-without-any-frontend-tooling-no-react-babel-webpack-etc/13064
4
+ function sendMessageToStreamlitClient(type, data) {
5
+ console.log(type, data)
6
+ const outData = Object.assign({
7
+ isStreamlitMessage: true,
8
+ type: type,
9
+ }, data);
10
+ window.parent.postMessage(outData, "*");
11
+ }
12
+
13
+ const Streamlit = {
14
+ setComponentReady: function() {
15
+ sendMessageToStreamlitClient("streamlit:componentReady", {apiVersion: 1});
16
+ },
17
+ setFrameHeight: function(height) {
18
+ sendMessageToStreamlitClient("streamlit:setFrameHeight", {height: height});
19
+ },
20
+ setComponentValue: function(value) {
21
+ sendMessageToStreamlitClient("streamlit:setComponentValue", {value: value});
22
+ },
23
+ RENDER_EVENT: "streamlit:render",
24
+ events: {
25
+ addEventListener: function(type, callback) {
26
+ window.addEventListener("message", function(event) {
27
+ if (event.data.type === type) {
28
+ event.detail = event.data
29
+ callback(event);
30
+ }
31
+ });
32
+ }
33
+ }
34
+ }
custom_component/frontend/style.css ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* style.css */
2
+
3
+ .image-container {
4
+ position: relative; /* Allows for absolute positioning of buttons */
5
+ display: inline-block;
6
+ }
7
+
8
+ .copy-image {
9
+ width: 100%; /* Ensures the image scales properly */
10
+ height: auto; /* Maintains aspect ratio */
11
+ display: block; /* Prevents any inline space issues */
12
+ }
13
+
14
+ .st-copy-to-clipboard-btn {
15
+ display: inline-flex;
16
+ align-items: center;
17
+ justify-content: center;
18
+ font-weight: 400;
19
+ border-radius: 0.5rem;
20
+ margin: 0;
21
+ line-height: 1.6;
22
+ color: inherit;
23
+ user-select: none;
24
+ background-color: transparent;
25
+ border: transparent;
26
+ cursor: copy;
27
+ position: absolute; /* Absolute position for placement over the image */
28
+ }
29
+
30
+ .st-copy-to-clipboard-btn:hover {
31
+ border-color: rgb(255, 75, 75);
32
+ color: transparent;
33
+ }
34
+
35
+ .st-copy-to-clipboard-btn:active {
36
+ border-color: rgb(255, 75, 75);
37
+ background-color: rgb(255, 75, 75);
38
+ color: rgb(255, 255, 255);
39
+ }