VascoDVRodrigues commited on
Commit
2b84d47
·
1 Parent(s): 33adf96
__pycache__/flagging.cpython-39.pyc ADDED
File without changes
__pycache__/fo_utils.cpython-39.pyc ADDED
File without changes
__pycache__/yolo_detect.cpython-39.pyc ADDED
File without changes
app.py CHANGED
@@ -1,7 +1,73 @@
 
 
1
  import gradio as gr
 
 
 
 
 
2
 
3
- def greet(name):
4
- return "Hello " + name + "!!"
 
 
 
5
 
6
- iface = gr.Interface(fn=greet, inputs="text", outputs="text")
7
- iface.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, List
2
+ import numpy as np
3
  import gradio as gr
4
+ from PIL import Image, ImageDraw
5
+ import yolo_detect as yod
6
+ import cv2
7
+ import fiftyone as fo
8
+ from flagging import FlaggingCallback, SimpleCSVLogger
9
 
10
+ class NewLogger(FlaggingCallback):
11
+ def __init__(self):
12
+ self.flag_data = None
13
+ self.flag_option = None
14
+ self.flag_index = None
15
 
16
+ super().__init__()
17
+ def flag(
18
+ self,
19
+ flag_data: List[Any],
20
+ flag_option= None,
21
+ flag_index = None,
22
+ username = None,
23
+ ) -> int:
24
+ self.flag_data = flag_data
25
+ self.flag_option = flag_option
26
+ self.flag_index = flag_index
27
+
28
+ if flag_option == "Bad":
29
+ print(flag_option)
30
+
31
+ # log_filepath = Path(flagging_dir) / "log.csv"
32
+
33
+ # csv_data = []
34
+ # for component, sample in zip(self.components, flag_data):
35
+ # save_dir = Path(flagging_dir) / utils.strip_invalid_filename_characters(
36
+ # component.label or ""
37
+ # )
38
+
39
+ return 2
40
+
41
+
42
+ def draw_boxes(input_img, iou_threshold, confidence_threshdol):
43
+ # Convert the input to a PIL Image
44
+ img = cv2.resize(input_img, (640, 640))
45
+ img = Image.fromarray(img)
46
+ draw = ImageDraw.Draw(img)
47
+
48
+ # Example bounding boxes: (x1, y1, x2, y2)
49
+ boxes = yod.identifications(input_img, iou_threshold, confidence_threshdol)
50
+
51
+ # Draw rectangles on the image
52
+ for box in boxes:
53
+ bbox = box[:4]
54
+ draw.rectangle(bbox, outline="red", width=2)
55
+ draw.text((bbox[0], bbox[1] - 16), f"{box[5]} -> {str(round(box[4], 2))}", fill="red")
56
+
57
+ # Convert back to array
58
+ return np.array(img)
59
+
60
+
61
+ # Define the Gradio Interface with a title
62
+ title = "Vic and the boyzzzzzz"
63
+ # Define a slider
64
+ iou_slider = gr.Slider(minimum=0, maximum=1, step=0.01, label="IoU Threshold", value=0.0)
65
+ conf_slider = gr.Slider(minimum=0, maximum=1, step=0.01, label="Confidence Threshold", value=0.0)
66
+
67
+ text_box = gr.Textbox(label="Write a description for bad behavior")
68
+
69
+
70
+ demo = gr.Interface(fn=draw_boxes, inputs=[gr.Image(), iou_slider, conf_slider], outputs=gr.Image(), title=title, flagging_options=["Good", "Bad"], allow_flagging="manual", flagging_callback=SimpleCSVLogger())
71
+
72
+ # Launch the app
73
+ demo.launch()
flagged/0647ac81eec7ca0472cbefb31326eb75bc33941e/tmpgbwsvy4t.png ADDED
flagged/08c2ffbe8e07b0adb6f88bdec9c5052147bec5e7/tmplwqprjl9.png ADDED
flagged/1f349fc84b10da520a20ab6964db933ae8d590ba/tmpgn9ufqfe.jpg ADDED
flagged/2d7e78487e0859e0292372774621cad965bf21e3/tmp86znz1kg.jpg ADDED
flagged/371ce470013964d05995602473339bd202869421/tmpuxklde0o.png ADDED
flagged/45e6cf08d95bc81a68a9e2fe01ff28b8f059aa37/tmplrhlhw89.jpg ADDED
flagged/4ab16a5c5d5c5055b98a90e85059dbbb23d38b7f/tmpr8_73fxf.png ADDED
flagged/6dba27a73fd2a548a0ce3ae1e39ac46b66bb22cd/tmpnwugquz2.jpg ADDED
flagged/afea2b426a3394cca3e20695c7aa0ae94f0956be/tmp1kp13nuh.png ADDED
flagged/c58e510d763082622c59b728d2057d4eeab7e07f/tmpzsm69w45.png ADDED
flagged/e39d6de35cfccd345ee117bd563952d5347a9bfe/tmpp_kcsseh.png ADDED
flagged/e687ba4e209c22a72a92e3add6994bda33807df0/tmpsuaxx26y.png ADDED
flagged/log.csv ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ input_img,output,flag,username,timestamp
2
+ /Users/danielfortunato/Documents/Git/VicAndTheBoys/flagged/e687ba4e209c22a72a92e3add6994bda33807df0/tmpsuaxx26y.png,0,0.49,/Users/danielfortunato/Documents/Git/VicAndTheBoys/flagged/4ab16a5c5d5c5055b98a90e85059dbbb23d38b7f/tmpr8_73fxf.png
3
+ /Users/danielfortunato/Documents/Git/VicAndTheBoys/flagged/1f349fc84b10da520a20ab6964db933ae8d590ba/tmpgn9ufqfe.jpg,0,0.43,/Users/danielfortunato/Documents/Git/VicAndTheBoys/flagged/c58e510d763082622c59b728d2057d4eeab7e07f/tmpzsm69w45.png
4
+ ,,,,2024-01-18 12:46:19.105462
5
+ "{""path"":""flagged/input_img/bd0e0e1ebebb34c3eb44/transferir.jpeg"",""url"":""http://localhost:7860/file=/private/var/folders/0w/531wr_3j4p9d2_zb_mgp020m0000gn/T/gradio/8272ecf51cc787e0b8282d005733b969178a9885/transferir.jpeg"",""size"":9327,""orig_name"":""transferir.jpeg"",""mime_type"":""""}","{""path"":""flagged/output/ad29fe502fe750df8930/image.png"",""url"":null,""size"":null,""orig_name"":""image.png"",""mime_type"":null}",,,2024-01-18 12:47:25.023152
6
+ /Users/danielfortunato/Documents/Git/VicAndTheBoys/flagged/input_img/4f471d893279e3e34d84a5ece1c34bb2480f3f22/tmpqtk_ji2b.jpg,50,50,/Users/danielfortunato/Documents/Git/VicAndTheBoys/flagged/output/e7885bcf6480d6927dc3afb9b2df5b16210f8a71/tmppql5erh5.png,Bad,,2024-01-18 14:20:26.757729
flagging.py ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import csv
4
+ import datetime
5
+ import io
6
+ import json
7
+ import os
8
+ import uuid
9
+ from abc import ABC, abstractmethod
10
+ from pathlib import Path
11
+ from typing import TYPE_CHECKING, Any, List
12
+ import fo_utils as fou
13
+
14
+ import gradio as gr
15
+ from gradio import utils
16
+
17
+ if TYPE_CHECKING:
18
+ from gradio.components import IOComponent
19
+
20
+
21
+
22
+ def _get_dataset_features_info(is_new, components):
23
+ """
24
+ Takes in a list of components and returns a dataset features info
25
+
26
+ Parameters:
27
+ is_new: boolean, whether the dataset is new or not
28
+ components: list of components
29
+
30
+ Returns:
31
+ infos: a dictionary of the dataset features
32
+ file_preview_types: dictionary mapping of gradio components to appropriate string.
33
+ header: list of header strings
34
+
35
+ """
36
+ infos = {"flagged": {"features": {}}}
37
+ # File previews for certain input and output types
38
+ file_preview_types = {gr.Audio: "Audio", gr.Image: "Image"}
39
+ headers = []
40
+
41
+ # Generate the headers and dataset_infos
42
+ if is_new:
43
+
44
+ for component in components:
45
+ headers.append(component.label)
46
+ infos["flagged"]["features"][component.label] = {
47
+ "dtype": "string",
48
+ "_type": "Value",
49
+ }
50
+ if isinstance(component, tuple(file_preview_types)):
51
+ headers.append(component.label + " file")
52
+ for _component, _type in file_preview_types.items():
53
+ if isinstance(component, _component):
54
+ infos["flagged"]["features"][
55
+ (component.label or "") + " file"
56
+ ] = {"_type": _type}
57
+ break
58
+
59
+ headers.append("flag")
60
+ infos["flagged"]["features"]["flag"] = {
61
+ "dtype": "string",
62
+ "_type": "Value",
63
+ }
64
+
65
+ return infos, file_preview_types, headers
66
+
67
+
68
+ class FlaggingCallback(ABC):
69
+ """
70
+ An abstract class for defining the methods that any FlaggingCallback should have.
71
+ """
72
+
73
+ @abstractmethod
74
+ def setup(self, components: List[IOComponent], flagging_dir: str):
75
+ """
76
+ This method should be overridden and ensure that everything is set up correctly for flag().
77
+ This method gets called once at the beginning of the Interface.launch() method.
78
+ Parameters:
79
+ components: Set of components that will provide flagged data.
80
+ flagging_dir: A string, typically containing the path to the directory where the flagging file should be storied (provided as an argument to Interface.__init__()).
81
+ """
82
+ pass
83
+
84
+ @abstractmethod
85
+ def flag(
86
+ self,
87
+ flag_data: List[Any],
88
+ flag_option: str | None = None,
89
+ flag_index: int | None = None,
90
+ username: str | None = None,
91
+ ) -> int:
92
+ """
93
+ This method should be overridden by the FlaggingCallback subclass and may contain optional additional arguments.
94
+ This gets called every time the <flag> button is pressed.
95
+ Parameters:
96
+ interface: The Interface object that is being used to launch the flagging interface.
97
+ flag_data: The data to be flagged.
98
+ flag_option (optional): In the case that flagging_options are provided, the flag option that is being used.
99
+ flag_index (optional): The index of the sample that is being flagged.
100
+ username (optional): The username of the user that is flagging the data, if logged in.
101
+ Returns:
102
+ (int) The total number of samples that have been flagged.
103
+ """
104
+ pass
105
+
106
+
107
+ class SimpleCSVLogger(FlaggingCallback):
108
+ """
109
+ A simplified implementation of the FlaggingCallback abstract class
110
+ provided for illustrative purposes. Each flagged sample (both the input and output data)
111
+ is logged to a CSV file on the machine running the gradio app.
112
+ Example:
113
+ import gradio as gr
114
+ def image_classifier(inp):
115
+ return {'cat': 0.3, 'dog': 0.7}
116
+ demo = gr.Interface(fn=image_classifier, inputs="image", outputs="label",
117
+ flagging_callback=SimpleCSVLogger())
118
+ """
119
+
120
+ def __init__(self):
121
+ pass
122
+
123
+ def setup(self, components: List[IOComponent], flagging_dir: str | Path):
124
+ self.components = components
125
+ self.flagging_dir = flagging_dir
126
+ os.makedirs(flagging_dir, exist_ok=True)
127
+
128
+ def flag(
129
+ self,
130
+ flag_data: List[Any],
131
+ flag_option: str | None = None,
132
+ flag_index: int | None = None,
133
+ username: str | None = None,
134
+ ) -> int:
135
+ flagging_dir = self.flagging_dir
136
+ log_filepath = Path(flagging_dir) / "log.csv"
137
+
138
+ csv_data = []
139
+ for component, sample in zip(self.components, flag_data):
140
+ save_dir = Path(flagging_dir)
141
+ # / utils.strip_invalid_filename_characters(
142
+ # component.label or ""
143
+ # )
144
+ csv_data.append(
145
+ component.deserialize(
146
+ sample,
147
+ save_dir,
148
+ None,
149
+ )
150
+ )
151
+
152
+ with open(log_filepath, "a", newline="") as csvfile:
153
+ writer = csv.writer(csvfile)
154
+ writer.writerow(utils.sanitize_list_for_csv(csv_data))
155
+
156
+ with open(log_filepath, "r") as csvfile:
157
+ line_count = len([None for row in csv.reader(csvfile)]) - 1
158
+
159
+ # if flag_option == "Bad":
160
+ # #get the image path
161
+ # image_path = csv_data
162
+ # #get the image name
163
+ # print(image_path)
164
+ # fou.upload_image_to_cvat(image_path[0])
165
+
166
+ return line_count
167
+
168
+
169
+
170
+ class FlagMethod:
171
+ """
172
+ Helper class that contains the flagging button option and callback
173
+ """
174
+
175
+ def __init__(self, flagging_callback: FlaggingCallback, flag_option=None):
176
+ self.flagging_callback = flagging_callback
177
+ self.flag_option = flag_option
178
+ self.__name__ = "Flag"
179
+
180
+ def __call__(self, *flag_data):
181
+ self.flagging_callback.flag(list(flag_data), flag_option=self.flag_option)
fo_utils.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fiftyone as fo
2
+ import requests
3
+
4
+ DATASET_NAME = "VIC_AND_THE_BOYZ"
5
+ CVAT_PROJECT_NAME = "VIC_AND_THE_BOYZ"
6
+
7
+ # Replace with your CVAT credentials and URL
8
+ cvat_url = "http://app.cvat.ai"
9
+ username = 'daniel.fortunato'
10
+ password = 'Lasanha123'
11
+
12
+ def load_vic_and_boyz_dataset(dataset_name=DATASET_NAME):
13
+ if dataset_name in fo.list_datasets():
14
+ dataset_name = fo.load_dataset(dataset_name)
15
+ else:
16
+ dataset_name = fo.Dataset(name=dataset_name, persistent=True, overwrite=True)
17
+
18
+ def bad_image_upload(dataset: fo.Dataset, image_filepath, description):
19
+ sample = fo.Sample(filepath=image_filepath, description=description)
20
+ dataset.add_sample(sample)
21
+
22
+
23
+ def upload_image_to_cvat(image_path, description="HEY"):
24
+ auth_data = {
25
+ 'username': username,
26
+ 'password': password
27
+ }
28
+
29
+ response = requests.post(f'{cvat_url}/api/auth/login', json=auth_data)
30
+ response.raise_for_status()
31
+ token = response.json()['key']
32
+
33
+ response = requests.get(f'{cvat_url}/api/projects', headers={'Authorization': f'Token {token}'})
34
+ response.raise_for_status()
35
+ projects = response.json()["results"]
36
+
37
+ task_name = description
38
+ project_id = projects[0]['id'] # replace with your specific project ID on the official CVAT server
39
+
40
+ headers = {'Authorization': f'Token {token}'}
41
+ task_data = {
42
+ "name": task_name,
43
+ "project_id": project_id
44
+ }
45
+ task_response = requests.post(f"{cvat_url}/api/tasks", headers=headers, json=task_data)
46
+ task_id = task_response.json()["id"]
47
+ print("TASK ID: ", task_id)
48
+
49
+ with open('/Users/danielfortunato/Documents/Git/VicAndTheBoys/flagged/0647ac81eec7ca0472cbefb31326eb75bc33941e/tmpgbwsvy4t.png', 'rb') as f:
50
+ files = {'image': f}
51
+ upload_response = requests.post(f"{cvat_url}/api/tasks/{task_id}/data", headers=headers, files=files, data={"image_quality": 50})
52
+
53
+ print("UPLOAD RESPONSE: ", upload_response.json())
inference.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from PIL import Image, ImageDraw
3
+ from pathlib import Path
4
+ from yolov5.models.experimental import attempt_load
5
+ from yolov5.utils.general import non_max_suppression, scale_coords
6
+ from yolov5.utils.torch_utils import select_device
7
+
8
+ # Set the device
9
+ device = select_device('')
10
+
11
+ # Load YOLOv5 model
12
+ weights_path = './model/yolov5n6_RGB_D2304-v1_9C.pt'
13
+ model = attempt_load(weights_path, map_location=device)
14
+ stride = int(model.stride.max()) # model stride
15
+
16
+ # Set image size
17
+ img_size = 640
18
+
19
+ # Load the single image
20
+ image_path = 'path/to/single/image.jpg'
21
+ img0 = Image.open(image_path)
22
+ img = img0.convert('RGB')
23
+
24
+ # Inference
25
+ img = torch.from_numpy(img).to(device)
26
+ img = img.float() / 255.0 # 0-255 to 0.0-1.0
27
+ img = img.unsqueeze(0) # add batch dimension
28
+ img = img.permute(0, 3, 1, 2) # BGR to RGB, to 4D tensor (NCHW)
29
+
30
+ # Inference
31
+ pred = model(img)[0]
32
+
33
+ # Non-maximum suppression
34
+ pred = non_max_suppression(pred, conf_thres=0.25, iou_thres=0.45)[0]
35
+
36
+ # Draw bounding boxes on the image
37
+ for det in pred:
38
+ det[:, :4] = scale_coords(img.shape[2:], det[:, :4], img0.size).round()
39
+ for *xyxy, conf, cls in det:
40
+ xyxy = [int(x) for x in xyxy]
41
+ label = f'{model.names[int(cls)]} {conf:.2f}'
42
+ img0 = ImageDraw.Draw(img0)
43
+ img0.rectangle(xyxy, outline='red', width=3)
44
+ img0.text((xyxy[0], xyxy[1]), label, fill='red')
45
+
46
+ # Save the result
47
+ output_path = 'output/result.jpg'
48
+ img0.save(output_path)
49
+
50
+ print(f"Inference completed. Result saved at {output_path}")
model/yolov5n6_RGB_D2304-v1_9C.pt ADDED
File without changes
try_import.py ADDED
@@ -0,0 +1 @@
 
 
1
+ import torch
yolo_detect.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import torch
3
+ import numpy as np
4
+ from yolov5.models.experimental import attempt_load
5
+ from yolov5.utils.general import non_max_suppression
6
+ import sys
7
+
8
+ from yolov5.models.common import DetectMultiBackend
9
+
10
+ torch.cuda.empty_cache()
11
+
12
+ weights = "./model/yolov5n6_RGB_D2304-v1_9C.pt"
13
+ model = DetectMultiBackend(weights)
14
+
15
+ model.eval()
16
+
17
+ colors = np.random.randint(0, 256, (32, 3)).tolist()
18
+
19
+ # Set device
20
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
21
+
22
+ # Define class labels
23
+ #class_names = ['person', 'bicycle', 'car', 'motorbike', 'aeroplane', 'bus', 'train', 'truck', 'boat', 'traffic light', 'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'sofa', 'pottedplant', 'bed', 'diningtable', 'toilet', 'tvmonitor', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors', 'teddy bear', 'hair drier', 'toothbrush']
24
+ class_names = ["ANIMAL", "BOAT", "BUOY", "FAR_AWAY_OBJECT", "FLOTSAM", "HUMAN", "LEISURE_VEHICLE", "SAILING_BOAT", "SHIP"]
25
+ # Set detection threshold
26
+ #conf_thres = 0.2
27
+
28
+ # Define the classes we want to detect
29
+ classes_of_interest = class_names
30
+
31
+ def identifications(frame, iou_threshold, conf_thres):
32
+ # Resize frame
33
+ img = cv2.resize(frame, (640, 640))
34
+ # Convert color format
35
+ img = img[:, :, ::-1].transpose(2, 0, 1)
36
+ img = np.ascontiguousarray(img)
37
+
38
+ # Convert to torch tensor
39
+ img = torch.from_numpy(img).float().to(device)
40
+ img /= 255.0
41
+ if img.ndimension() == 3:
42
+ img = img.unsqueeze(0)
43
+ # [1,3,502,848]
44
+ # Detect objects in the image
45
+ pred = model(img)[0]
46
+ pred = non_max_suppression(pred, conf_thres, iou_threshold)
47
+ ret_preds = []
48
+ # Draw bounding boxes around detected objects
49
+ for det in pred[0]:
50
+ x1, y1, x2, y2, conf, cls = det.cpu().numpy()
51
+ label = class_names[int(cls)]
52
+ if label not in classes_of_interest:
53
+ continue
54
+ x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)
55
+ #ret_preds.append((x1, y1, x2, y2, conf, class_names[int(cls)]))
56
+ ret_preds.append((x1, y1, x2, y2, conf, class_names[int(cls)]))
57
+
58
+ return ret_preds
59
+
60
+
61
+ # img = cv2.imread('download.jpeg')
62
+
63
+ # # perform object identification on the frame
64
+ # preds = identifications(img)
65
+
66
+ # print(preds)
yolov5n.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ <��r�{�Z��d�PYe�Ye�Ye�Yf�ަ�!���z�~+gz�*5y�u�jrjz[2�Z �ץ�������)��j��z׫���Y^rب�ëu��t8���j_��}����Q���z�"�ק���}������?����ا�����F��m�$����(�ʡF����ߺYm�$����(��W��+����(�ʡF��~��j������$��Q�쵫^�؜��h�K*���Zw�-j׿v'-����(�ʡF����n��*��r�+r皇n���{Z�w�I�z{bjZ��aF�aF�a��j�j��aF�aF�aF�aF����ү���^��(�j'
2
+ �絛��aF�a������j�Q��M�j�Q��aF�a����r���h��g�j�W�r��
3
+ ����