Skip to content

Instantly share code, notes, and snippets.

@alexeygrigorev
Created June 18, 2022 06:47
Show Gist options
  • Save alexeygrigorev/aad66b3347498fa6cc553e7936e5bb4d to your computer and use it in GitHub Desktop.
Save alexeygrigorev/aad66b3347498fa6cc553e7936e5bb4d to your computer and use it in GitHub Desktop.
Toloka
import random
import functools
from IPython.display import display, clear_output
from ipywidgets import Button, Dropdown, HTML, HBox, IntSlider, FloatSlider, Textarea, Output
def annotate(examples,
options=None,
shuffle=False,
include_skip=True,
display_fn=display):
"""
Build an interactive widget for annotating a list of input examples.
Parameters
----------
examples: list(any), list of items to annotate
options: list(any) or tuple(start, end, [step]) or None
if list: list of labels for binary classification task (Dropdown or Buttons)
if tuple: range for regression task (IntSlider or FloatSlider)
if None: arbitrary text input (TextArea)
shuffle: bool, shuffle the examples before annotating
include_skip: bool, include option to skip example while annotating
display_fn: func, function for displaying an example to the user
Returns
-------
annotations : list of tuples, list of annotated examples (example, label)
"""
examples = list(examples)
if shuffle:
random.shuffle(examples)
annotations = []
current_index = -1
def set_label_text():
nonlocal count_label
count_label.value = '{} examples annotated, {} examples left'.format(
len(annotations), len(examples) - current_index
)
def show_next():
nonlocal current_index
current_index += 1
set_label_text()
if current_index >= len(examples):
for btn in buttons:
btn.disabled = True
print('Annotation done.')
return
with out:
clear_output(wait=True)
display_fn(examples[current_index])
def add_annotation(annotation):
annotations.append((examples[current_index], annotation))
show_next()
def skip(btn):
show_next()
count_label = HTML()
set_label_text()
display(count_label)
if type(options) == list:
task_type = 'classification'
elif type(options) == tuple and len(options) in [2, 3]:
task_type = 'regression'
elif options is None:
task_type = 'captioning'
else:
raise Exception('Invalid options')
buttons = []
if task_type == 'classification':
use_dropdown = len(options) > 15
if use_dropdown:
dd = Dropdown(options=options)
display(dd)
btn = Button(description='submit')
def on_click(btn):
add_annotation(dd.value)
btn.on_click(on_click)
buttons.append(btn)
else:
for label in options:
btn = Button(description=label)
def on_click(label, btn):
add_annotation(label)
btn.on_click(functools.partial(on_click, label))
buttons.append(btn)
elif task_type == 'regression':
target_type = type(options[0])
if target_type == int:
cls = IntSlider
else:
cls = FloatSlider
if len(options) == 2:
min_val, max_val = options
slider = cls(min=min_val, max=max_val)
else:
min_val, max_val, step_val = options
slider = cls(min=min_val, max=max_val, step=step_val)
display(slider)
btn = Button(description='submit')
def on_click(btn):
add_annotation(slider.value)
btn.on_click(on_click)
buttons.append(btn)
else:
ta = Textarea()
display(ta)
btn = Button(description='submit')
def on_click(btn):
add_annotation(ta.value)
btn.on_click(on_click)
buttons.append(btn)
if include_skip:
btn = Button(description='skip')
btn.on_click(skip)
buttons.append(btn)
box = HBox(buttons)
display(box)
out = Output()
display(out)
show_next()
return annotations
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"import hashlib\n",
"import shutil\n",
"from glob import glob\n",
"from collections import Counter\n",
"from io import BytesIO\n",
"from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor\n",
"\n",
"import requests\n",
"import imagehash\n",
"from PIL import Image\n",
"\n",
"from IPython.display import display\n",
"from IPython.display import HTML\n",
"\n",
"from tqdm import tqdm_notebook as tqdm\n",
"\n",
"\n",
"from annotate1 import annotate"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"def map_progress(pool, seq, f):\n",
" results = []\n",
"\n",
" with tqdm(total=len(seq)) as progress:\n",
" futures = []\n",
"\n",
" for el in seq:\n",
" future = pool.submit(f, el)\n",
" future.add_done_callback(lambda p: progress.update())\n",
" futures.append(future)\n",
"\n",
" for future in futures:\n",
" result = future.result()\n",
" results.append(result)\n",
"\n",
" return results"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"def calculate_phashes(image_stream):\n",
" img = Image.open(image_stream)\n",
"\n",
" dhash = str(imagehash.dhash(img))\n",
" phash = str(imagehash.phash(img))\n",
" whash = str(imagehash.whash(img))\n",
"\n",
" return {\n",
" \"dhash\": dhash,\n",
" \"phash\": phash,\n",
" \"whash\": whash,\n",
" }\n",
"\n",
"\n",
"def calculate_hashes(image_bytes):\n",
" stream = BytesIO(image_bytes)\n",
" hashes = calculate_phashes(stream)\n",
"\n",
" md5 = hashlib.md5(image_bytes).hexdigest()\n",
" hashes[\"md5\"] = md5\n",
"\n",
" return hashes"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"images = sorted(glob('images/*'))"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"def read_and_calculate_hashes(filename):\n",
" with open(filename, 'rb') as f_in:\n",
" content = f_in.read()\n",
" h = calculate_hashes(content)\n",
" return (filename, h)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "f90d91d4c037487996c8a5d8df565577",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"HBox(children=(IntProgress(value=0, max=140), HTML(value='')))"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n"
]
}
],
"source": [
"with ProcessPoolExecutor() as pool:\n",
" hashes = map_progress(pool, images, read_and_calculate_hashes)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"md5s = {h['md5']: k for (k, h) in hashes}\n",
"phashes = {h['phash']: k for (k, h) in hashes}"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"#blocked = set()"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'0873d3d620c0fb495123becc1b268577',\n",
" '3d42b63db037403a6d23c56eb5cab58a',\n",
" '46844488a4bcd0e95e1904802c17b2df',\n",
" '52b86953b64fc8211c4eaea60248a40a',\n",
" '5dca9a7885ac7045033131738bee2ba8',\n",
" '6c9914ff507fa4b9063128d7cf3dd2f9',\n",
" 'aab7f12adc1f880129f2819ac27df532',\n",
" 'abb4dd8c8ac3c80a3c65d3dbf568b729',\n",
" 'c79879f38641bacbc6d89359af043a0b'}"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"blocked"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"token = os.environ['TOLOKA_TOKEN']\n",
"\n",
"headers = {\n",
" 'Authorization': 'OAuth %s' % token\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
"restrict_user_url_template = '%s/api/v1/user-restrictions'\n",
"restrict_user_url = restrict_user_url_template % host"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
"def download_image_and_calc_hashes(attachment):\n",
" try:\n",
" filename = 'images/%s' % attachment\n",
"\n",
" if os.path.exists(filename):\n",
" print('%s already exist' % filename)\n",
" _, h = read_and_calculate_hashes(filename)\n",
" return attachment, h\n",
"\n",
" print('downloading %s...' % filename)\n",
"\n",
" image_url_template = '%s/api/v1/attachments/%s/download'\n",
" image_url = image_url_template % (host, attachment)\n",
" res = requests.get(image_url, headers=headers)\n",
"\n",
" with open(filename, 'wb') as f_out:\n",
" f_out.write(res.content)\n",
"\n",
" h = calculate_hashes(res.content)\n",
" return (attachment, h)\n",
" except OSError:\n",
" return (attachment, None)"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
"def show_item(item_id):\n",
" item = items[item_id]\n",
" output = item['solutions'][0]['output_values']\n",
" attachment = output['image']\n",
" result_type = output.get('type', '<NOT PROVIDED>')\n",
" \n",
" filename = 'images/%s' % attachment\n",
" print(filename)\n",
"\n",
" size = os.path.getsize(filename) / (1024 * 1024)\n",
" \n",
" print('size, mb: %.3f' % size)\n",
" html = '<img src=\"%s\" style=\"max-height: 500px\" />' % filename\n",
" display(HTML(html))\n",
" print(result_type)"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [],
"source": [
"def block_user(user_id, reason):\n",
" if user_id in blocked:\n",
" print('%s already blocked' % user_id)\n",
" return\n",
" \n",
" block_request = {\n",
" \"scope\": \"ALL_PROJECTS\",\n",
" \"user_id\": user_id,\n",
" \"private_comment\": reason # \"\u041c\u043d\u043e\u0433\u043e \u043e\u0448\u0438\u0431\u043e\u043a\"\n",
" } \n",
" print(block_request)\n",
" resp = requests.put(restrict_user_url, headers=headers, json=block_request)\n",
"\n",
" print(resp.json())\n",
" print('blocked %s' % user_id)\n",
" blocked.add(user_id)"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['accept',\n",
" 'stock',\n",
" 'wrong type',\n",
" 'multiple_items',\n",
" 'bad_quality',\n",
" 'not clothes',\n",
" 'duplicate',\n",
" 'not_owned',\n",
" 'screenshot',\n",
" 'human',\n",
" 'broken_file']"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"rejection_reasons = {\n",
" 'stock': '\u0441\u0442\u043e\u043a\u043e\u0432\u043e\u0435 \u0444\u043e\u0442\u043e',\n",
" 'wrong type': '\u043e\u0434\u0435\u0436\u0434\u0430 \u043d\u0435\u0432\u0435\u0440\u043d\u043e\u0433\u043e \u0442\u0438\u043f\u0430 (\u043d\u0435 \u0438\u0437 \u0441\u043f\u0438\u0441\u043a\u0430)',\n",
" 'multiple_items': '\u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u0435\u0434\u043c\u0435\u0442\u043e\u0432 \u043d\u0430 \u0444\u043e\u0442\u043e',\n",
" 'bad_quality': '\u043f\u043b\u043e\u0445\u043e\u0435 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u043e, \u043f\u043b\u043e\u0445\u043e \u0432\u0438\u0434\u043d\u043e \u043e\u0434\u0435\u0436\u0434\u0443',\n",
" 'not clothes': '\u043d\u0435 \u043e\u0434\u0435\u0436\u0434\u0430',\n",
" 'duplicate': '\u0434\u0443\u0431\u043b\u0438\u043a\u0430\u0442',\n",
" 'not_owned': '\u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u044e \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0439\u0442\u0438 \u0432 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0435, \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u043e\u043d\u0430 \u043d\u0435 \u0432\u0430\u0448\u0430',\n",
" 'screenshot': '\u0441\u043a\u0440\u0438\u043d\u0448\u043e\u0442 \u0432\u043c\u0435\u0441\u0442\u043e \u0444\u043e\u0442\u043e',\n",
" 'human': '\u043e\u0434\u0435\u0436\u0434\u0430 \u043d\u0435 \u0434\u043e\u043b\u0436\u043d\u0430 \u0431\u044b\u0442\u044c \u043d\u0430\u0434\u0435\u0442\u0430',\n",
" 'broken_file': '\u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u043f\u043e\u0432\u0440\u0435\u0436\u0434\u0435\u043d\u043e',\n",
" \n",
"}\n",
"\n",
"options = ['accept'] + list(rejection_reasons.keys())\n",
"options"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
"host = 'https://toloka.yandex.ru'\n",
"pool_id = '15582326'\n",
"URL = '%s/api/v1/assignments?status=SUBMITTED&pool_id=%s&sort=submitted' % (host, pool_id)"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [],
"source": [
"def reject(id, verdict):\n",
" if verdict == 'accept':\n",
" status = 'ACCEPTED'\n",
" comment = '\u043e\u0442\u043b\u0438\u0447\u043d\u043e, \u0441\u043f\u0430\u0441\u0438\u0431\u043e'\n",
" else:\n",
" status = 'REJECTED'\n",
" comment = rejection_reasons[verdict]\n",
"\n",
" verdict_url_template = '%s/api/v1/assignments/%s'\n",
" verdict_url = verdict_url_template % (host, id)\n",
"\n",
" request_body = {\n",
" 'status': status,\n",
" 'public_comment': comment\n",
" }\n",
" \n",
" resp = requests.patch(verdict_url, headers=headers, json=request_body)\n",
" print(id, status, comment)\n",
"\n",
"def reject_tuple(expand):\n",
" id, verdict = expand\n",
" reject(id, verdict)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Start here"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"False\n",
"9\n"
]
}
],
"source": [
"resp = requests.get(URL, headers=headers).json()\n",
"print(resp['has_more'])\n",
"print(len(resp['items']))\n",
"\n",
"items = {d['id']: d for d in resp['items']}\n",
"users = {d['id']: d['user_id'] for d in resp['items']}"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "546b0f21389c47cd912b364086544631",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"HBox(children=(IntProgress(value=0, max=9), HTML(value='')))"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"downloading images/b6793b19-d070-4879-938a-e754549e5501...\n",
"downloading images/d0b0fc4d-cc2c-4abe-97cb-353423dc07d1...downloading images/167f23be-4615-498a-ace4-d0b0f64cc95f...\n",
"\n",
"downloading images/78334dc5-2c60-48f8-a665-5d33e357c2d5...\n",
"downloading images/16740066-3329-4165-884a-25c42814d590...\n",
"downloading images/df545cf1-5461-4745-8e32-c7e6e1247d2e...\n",
"downloading images/d66fbc59-e189-47ff-b487-05d7716d75c0...\n",
"downloading images/a18f1511-770a-4900-b748-691c6021a3b4...\n",
"downloading images/03103065-f445-44a5-b707-53b73534f57d...\n",
"\n"
]
}
],
"source": [
"attachments = {v['solutions'][0]['output_values']['image']: k for k, v in items.items()}\n",
"with ThreadPoolExecutor() as pool:\n",
" results = map_progress(pool, list(attachments), download_image_and_calc_hashes)"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [],
"source": [
"duplicates = []\n",
"\n",
"for a, h in results:\n",
" if h is None:\n",
" continue\n",
" md5 = h['md5']\n",
" if md5 in md5s:\n",
" item_id = attachments[a]\n",
" print('duplicate!', item_id, a)\n",
" print('original:', md5s[md5])\n",
" duplicates.append((item_id, a))\n",
" continue \n",
"\n",
" md5s[md5] = a\n",
"\n",
"duplicate_items = {i for (i, _) in duplicates}\n",
"duplicate_attachments = {i for (_, i) in duplicates}\n",
"duplicates_results = [(d, 'duplicate') for d in duplicate_items]"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [],
"source": [
"for a, h in results:\n",
" if h is None:\n",
" continue\n",
" phash = h['phash']\n",
" if a in duplicate_attachments:\n",
" print('already md5 duplicate, skipping')\n",
" continue\n",
"\n",
" if phash in phashes:\n",
" item_id = attachments[a]\n",
" print('phash duplicate!', item_id, a)\n",
" print('candidate:', phashes[phash])\n",
" print()\n",
" else:\n",
" phashes[phash] = a\n",
"# duplicate_items.add('0000edc476--5f2e3d353c75de6c2dcf53c5')\n",
"# duplicates_results.append(('0000edc476--5f2e3d353c75de6c2dcf53c5', 'duplicate'))"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"9 9\n"
]
}
],
"source": [
"items_pool = []\n",
"for item_id, item in items.items():\n",
" if item_id not in duplicate_items:\n",
" items_pool.append(item_id)\n",
"print(len(items_pool), len(items))"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "01df0ae7c755402a9202dd6932e465e1",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"HTML(value='0 examples annotated, 10 examples left')"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "fe4e91e8312e44e597b633d6fdb1fdc5",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"HBox(children=(Button(description='accept', style=ButtonStyle()), Button(description='stock', style=ButtonStyl\u2026"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "8c168b4d48074b089ea8c48c87aff978",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Output()"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Annotation done.\n"
]
}
],
"source": [
"results = annotate(items_pool,\n",
" options=options,\n",
" include_skip=False,\n",
" display_fn=show_item)"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Counter({'accept': 8, 'bad_quality': 1})"
]
},
"execution_count": 24,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"all_results = duplicates_results + results\n",
"Counter([v for (_, v) in all_results])"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "8d8aa2baf204456d90c8a36da952fd37",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"HBox(children=(IntProgress(value=0, max=9), HTML(value='')))"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"0000edc476--5f2fe2ac64fc8d11aa14d064 ACCEPTED \u043e\u0442\u043b\u0438\u0447\u043d\u043e, \u0441\u043f\u0430\u0441\u0438\u0431\u043e\n",
"0000edc476--5f2fda9f17028d7448a50a4f ACCEPTED \u043e\u0442\u043b\u0438\u0447\u043d\u043e, \u0441\u043f\u0430\u0441\u0438\u0431\u043e\n",
"0000edc476--5f2fe1712a01ac6dd718ca37 ACCEPTED \u043e\u0442\u043b\u0438\u0447\u043d\u043e, \u0441\u043f\u0430\u0441\u0438\u0431\u043e\n",
"0000edc476--5f2fe08007d73947a61890fe ACCEPTED \u043e\u0442\u043b\u0438\u0447\u043d\u043e, \u0441\u043f\u0430\u0441\u0438\u0431\u043e\n",
"0000edc476--5f2fd9f094ee1b7a5d5cadb0 ACCEPTED \u043e\u0442\u043b\u0438\u0447\u043d\u043e, \u0441\u043f\u0430\u0441\u0438\u0431\u043e\n",
"0000edc476--5f2fd9067b8dbe5e5bff8fac ACCEPTED \u043e\u0442\u043b\u0438\u0447\u043d\u043e, \u0441\u043f\u0430\u0441\u0438\u0431\u043e\n",
"0000edc476--5f2fdc9122d6df43d6d3bfb6 ACCEPTED \u043e\u0442\u043b\u0438\u0447\u043d\u043e, \u0441\u043f\u0430\u0441\u0438\u0431\u043e\n",
"0000edc476--5f2fdab64c55035903c5ccca REJECTED \u043f\u043b\u043e\u0445\u043e\u0435 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u043e, \u043f\u043b\u043e\u0445\u043e \u0432\u0438\u0434\u043d\u043e \u043e\u0434\u0435\u0436\u0434\u0443\n",
"0000edc476--5f2fe30fb6f8735169f96416 ACCEPTED \u043e\u0442\u043b\u0438\u0447\u043d\u043e, \u0441\u043f\u0430\u0441\u0438\u0431\u043e\n",
"\n"
]
}
],
"source": [
"with ThreadPoolExecutor() as pool:\n",
" map_progress(pool, all_results, reject_tuple)"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "c96bc02062f040d1a937da5246a5d85f",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"HBox(children=(IntProgress(value=0, max=9), HTML(value='')))"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n"
]
}
],
"source": [
"for id, verdict in tqdm(all_results):\n",
" if verdict in {'stock', 'not clothes', 'duplicate'}:\n",
" user_id = users[id]\n",
" block_user(user_id, rejection_reasons[verdict])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" for id, verdict in results:\n",
" if verdict != 'accept':\n",
" continue\n",
"\n",
" item = items[id]\n",
" output = item['solutions'][0]['output_values']\n",
" attachment = output['image']\n",
"\n",
" src_file = 'images/%s' % attachment\n",
" dest_file = 'accepted/%s' % attachment\n",
"\n",
" shutil.copyfile(src_file, dest_file)\n",
" print('copied %s to %s, verdict: %s' % (src_file, dest_file, verdict))"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.7"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment